diff --git a/.brazil.json b/.brazil.json index bce97f55c9f9..97308182f8e2 100644 --- a/.brazil.json +++ b/.brazil.json @@ -32,6 +32,14 @@ "utils": { "packageName": "AwsJavaSdk-Core-Utils" }, "imds": { "packageName": "AwsJavaSdk-Imds" }, "crt-core": { "packageName": "AwsJavaSdk-Core-CrtCore" }, + "checksums-spi": { "packageName": "AwsJavaSdk-Core-ChecksumsSpi" }, + "checksums": { "packageName": "AwsJavaSdk-Core-Checksums" }, + "identity-spi": { "packageName": "AwsJavaSdk-Core-IdentitySpi" }, + "http-auth-spi": { "packageName": "AwsJavaSdk-Core-HttpAuthSpi" }, + "http-auth": { "packageName": "AwsJavaSdk-Core-HttpAuth" }, + "http-auth-aws": { "packageName": "AwsJavaSdk-Core-HttpAuthAws" }, + "http-auth-aws-crt": { "packageName": "AwsJavaSdk-Core-HttpAuthAwsCrt" }, + "http-auth-aws-eventstream": { "packageName": "AwsJavaSdk-Core-HttpAuthAwsEventStream" }, "dynamodb": { "packageName": "AwsJavaSdk-DynamoDb" }, "waf": { "packageName": "AwsJavaSdk-Waf" }, @@ -74,6 +82,7 @@ "http-clients": { "skipImport": true }, "metric-publishers": { "skipImport": true }, "module-path-tests": { "skipImport": true }, + "old-client-version-compatibility-test": { "skipImport": true }, "protocol-tests": { "skipImport": true }, "protocol-tests-core": { "skipImport": true }, "protocols": { "skipImport": true }, diff --git a/.changes/next-release/feature-AWSSDKforJavav2-07cf633.json b/.changes/next-release/feature-AWSSDKforJavav2-07cf633.json new file mode 100644 index 000000000000..b4994388b26c --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-07cf633.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "This release contains a major internal refactor of authentication. The refactor is part of moving the SDK to a standardized AWS SDK architecture.\n\nIt paves the way for new features to be released over the coming months: SDK plugins to provide a unified interface to add third-party functionality to the SDK and the ability to define additional ways of authenticating with services. \n\nWith this release, custom 3rd party credential providers can be created with fewer SDK dependencies (with the new `identity-spi` module), and AWS signers can be used with fewer SDK dependencies (with the new `http-auth-aws` module).\n\nBecause this is a major refactor that may result in backwards-compatible behavior changes, this release bumps the minor version of the SDK to 2.21." +} diff --git a/archetypes/archetype-app-quickstart/pom.xml b/archetypes/archetype-app-quickstart/pom.xml index 02f8537a971e..685e6db11b2a 100644 --- a/archetypes/archetype-app-quickstart/pom.xml +++ b/archetypes/archetype-app-quickstart/pom.xml @@ -20,7 +20,7 @@ archetypes software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/archetypes/archetype-lambda/pom.xml b/archetypes/archetype-lambda/pom.xml index 77f0e0dc3674..f5a431a895b3 100644 --- a/archetypes/archetype-lambda/pom.xml +++ b/archetypes/archetype-lambda/pom.xml @@ -20,7 +20,7 @@ archetypes software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 archetype-lambda diff --git a/archetypes/archetype-tools/pom.xml b/archetypes/archetype-tools/pom.xml index 0fb8a03af5ac..ebe38138055e 100644 --- a/archetypes/archetype-tools/pom.xml +++ b/archetypes/archetype-tools/pom.xml @@ -20,7 +20,7 @@ archetypes software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/archetypes/pom.xml b/archetypes/pom.xml index 11453fc46f46..257174e1ce14 100644 --- a/archetypes/pom.xml +++ b/archetypes/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 archetypes diff --git a/aws-sdk-java/pom.xml b/aws-sdk-java/pom.xml index 6ea6049fe4b5..cee67ce87c05 100644 --- a/aws-sdk-java/pom.xml +++ b/aws-sdk-java/pom.xml @@ -17,7 +17,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../pom.xml aws-sdk-java diff --git a/bom-internal/pom.xml b/bom-internal/pom.xml index c00a69f98a33..0b53dac783f6 100644 --- a/bom-internal/pom.xml +++ b/bom-internal/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/bom/pom.xml b/bom/pom.xml index 3d5bdd49fcba..3af1c5525b0a 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -17,7 +17,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../pom.xml bom @@ -127,6 +127,46 @@ sdk-core ${awsjavasdk.version} + + software.amazon.awssdk + checksums-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + checksums + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-aws-crt + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-aws-eventstream + ${awsjavasdk.version} + + + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + software.amazon.awssdk http-client-spi diff --git a/build-tools/src/main/resources/messages.xml b/build-tools/src/main/resources/messages.xml index b93d9fd33179..2ec9a220fd67 100644 --- a/build-tools/src/main/resources/messages.xml +++ b/build-tools/src/main/resources/messages.xml @@ -43,11 +43,12 @@ ]]> + Bad method call + Bad toBuilder implementation Bad toBuilder implementation. See the SpotBugs logs for problem details.
Bad toBuilder implementation. See the SpotBugs logs for problem details.
- Bad toBuilder implementation - \ No newline at end of file + diff --git a/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml b/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml index bb57f0e7fc81..85fb5754cd6f 100644 --- a/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml +++ b/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml @@ -135,6 +135,17 @@ + + + + + + + + + + + @@ -186,6 +197,12 @@ + + + + + + @@ -267,6 +284,14 @@ + + + + + + + + diff --git a/buildspecs/release-javadoc.yml b/buildspecs/release-javadoc.yml index 6b91d572f71d..9fe8f8e66985 100644 --- a/buildspecs/release-javadoc.yml +++ b/buildspecs/release-javadoc.yml @@ -18,7 +18,7 @@ phases: commands: - python ./scripts/doc_crosslinks/generate_cross_link_data.py --apiDefinitionsBasePath ./services/ --apiDefinitionsRelativeFilePath src/main/resources/codegen-resources/service-2.json --templateFilePath ./scripts/doc_crosslinks/crosslink_redirect.html --outputFilePath ./scripts/crosslink_redirect.html - mvn install -P quick -T1C - - mvn clean install javadoc:aggregate -B -Ppublic-javadoc -Dcheckstyle.skip -Dspotbugs.skip -DskipTests -Ddoclint=none -pl '!:protocol-tests,!:protocol-tests-core,!:codegen-generated-classes-test,!:sdk-benchmarks,!:s3-benchmarks,!:module-path-tests,!:test-utils,!:http-client-tests,!:tests-coverage-reporting,!:sdk-native-image-test,!:ruleset-testing-core' + - mvn clean install javadoc:aggregate -B -Ppublic-javadoc -Dcheckstyle.skip -Dspotbugs.skip -DskipTests -Ddoclint=none -pl '!:protocol-tests,!:protocol-tests-core,!:codegen-generated-classes-test,!:sdk-benchmarks,!:s3-benchmarks,!:module-path-tests,!:test-utils,!:http-client-tests,!:tests-coverage-reporting,!:sdk-native-image-test,!:ruleset-testing-core!:old-client-version-compatibility-testing' - RELEASE_VERSION=`mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec` - - aws s3 sync target/site/apidocs/ $DOC_PATH/$RELEASE_VERSION/ diff --git a/buildspecs/release-to-maven.yml b/buildspecs/release-to-maven.yml index 7554c01d6461..1004d594923d 100644 --- a/buildspecs/release-to-maven.yml +++ b/buildspecs/release-to-maven.yml @@ -34,7 +34,7 @@ phases: awk 'BEGIN { var=ENVIRON["SDK_SIGNING_GPG_KEYNAME"] } { gsub("\\$SDK_SIGNING_GPG_KEYNAME", var, $0); print }' > \ $SETTINGS_XML - mvn clean deploy -B -s $SETTINGS_XML -Ppublishing -DperformRelease -Dspotbugs.skip -DskipTests -Dcheckstyle.skip -Djapicmp.skip -Ddoclint=none -pl !:protocol-tests,!:protocol-tests-core,!:codegen-generated-classes-test,!:sdk-benchmarks,!:module-path-tests,!:tests-coverage-reporting,!:stability-tests,!:sdk-native-image-test,!:auth-tests,!:s3-benchmarks,!:region-testing -DautoReleaseAfterClose=true -DstagingProgressTimeoutMinutes=30 + mvn clean deploy -B -s $SETTINGS_XML -Ppublishing -DperformRelease -Dspotbugs.skip -DskipTests -Dcheckstyle.skip -Djapicmp.skip -Ddoclint=none -pl !:protocol-tests,!:protocol-tests-core,!:codegen-generated-classes-test,!:sdk-benchmarks,!:module-path-tests,!:tests-coverage-reporting,!:stability-tests,!:sdk-native-image-test,!:auth-tests,!:s3-benchmarks,!:region-testing!:old-client-version-compatibility-testing -DautoReleaseAfterClose=true -DstagingProgressTimeoutMinutes=30 else echo "This version was already released." fi diff --git a/bundle/pom.xml b/bundle/pom.xml index 15c7523d9299..bed04f6e7b4e 100644 --- a/bundle/pom.xml +++ b/bundle/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT bundle jar diff --git a/codegen-lite-maven-plugin/pom.xml b/codegen-lite-maven-plugin/pom.xml index 52e92fceb826..1d63f7746b49 100644 --- a/codegen-lite-maven-plugin/pom.xml +++ b/codegen-lite-maven-plugin/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../pom.xml codegen-lite-maven-plugin diff --git a/codegen-lite/pom.xml b/codegen-lite/pom.xml index 65b759bab363..de239e1baa3e 100644 --- a/codegen-lite/pom.xml +++ b/codegen-lite/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codegen-lite AWS Java SDK :: Code Generator Lite diff --git a/codegen-maven-plugin/pom.xml b/codegen-maven-plugin/pom.xml index 08195fa66c15..21654ce1a4cd 100644 --- a/codegen-maven-plugin/pom.xml +++ b/codegen-maven-plugin/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../pom.xml codegen-maven-plugin diff --git a/codegen/pom.xml b/codegen/pom.xml index 2a5cfc0bfbf1..e3ed5e188e26 100644 --- a/codegen/pom.xml +++ b/codegen/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codegen AWS Java SDK :: Code Generator @@ -77,6 +77,26 @@ auth ${awsjavasdk.version}
+ + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + software.amazon.awssdk arns diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/AddMetadata.java b/codegen/src/main/java/software/amazon/awssdk/codegen/AddMetadata.java index 7442ae662324..6832035e5cbe 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/AddMetadata.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/AddMetadata.java @@ -15,6 +15,9 @@ package software.amazon.awssdk.codegen; +import java.util.Collections; +import java.util.Optional; +import java.util.stream.Collectors; import software.amazon.awssdk.codegen.internal.Constant; import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig; import software.amazon.awssdk.codegen.model.intermediate.Metadata; @@ -59,6 +62,7 @@ public static Metadata constructMetadata(ServiceModel serviceModel, .withPaginatorsPackageName(namingStrategy.getPaginatorsPackageName(serviceName)) .withWaitersPackageName(namingStrategy.getWaitersPackageName(serviceName)) .withEndpointRulesPackageName(namingStrategy.getEndpointRulesPackageName(serviceName)) + .withAuthSchemePackageName(namingStrategy.getAuthSchemePackageName(serviceName)) .withServiceAbbreviation(serviceMetadata.getServiceAbbreviation()) .withServiceFullName(serviceMetadata.getServiceFullName()) .withServiceName(serviceName) @@ -78,7 +82,12 @@ public static Metadata constructMetadata(ServiceModel serviceModel, .withServiceId(serviceMetadata.getServiceId()) .withSupportsH2(supportsH2(serviceMetadata)) .withJsonVersion(getJsonVersion(metadata, serviceMetadata)) - .withAwsQueryCompatible(serviceMetadata.getAwsQueryCompatible()); + .withAwsQueryCompatible(serviceMetadata.getAwsQueryCompatible()) + .withAuth(Optional.ofNullable(serviceMetadata.getAuth()) + .orElseGet(() -> Collections.singletonList(serviceMetadata.getSignatureVersion())) + .stream() + .map(AuthType::fromValue) + .collect(Collectors.toList())); return metadata; } 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 1b9f73858e00..6b332e015a12 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java @@ -17,9 +17,11 @@ import static software.amazon.awssdk.codegen.internal.Utils.unCapitalize; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.stream.Collectors; import software.amazon.awssdk.codegen.model.intermediate.ExceptionModel; import software.amazon.awssdk.codegen.model.intermediate.OperationModel; import software.amazon.awssdk.codegen.model.intermediate.ReturnTypeModel; @@ -70,6 +72,9 @@ private static boolean isBlobShape(Shape shape) { } /** + * If there is a member in the output shape that is explicitly marked as the payload (with the payload trait) this method + * returns the target shape of that member. Otherwise this method returns null. + * * @return True if shape is a String type. False otherwise */ private static boolean isStringShape(Shape shape) { @@ -81,10 +86,8 @@ private static boolean isStringShape(Shape shape) { * payload trait) this method returns the target shape of that member. Otherwise this method * returns null. * - * @param c2jShapes - * All C2J shapes - * @param outputShape - * Output shape of operation that may contain a member designated as the payload + * @param c2jShapes All C2J shapes + * @param outputShape Output shape of operation that may contain a member designated as the payload */ public static Shape getPayloadShape(Map c2jShapes, Shape outputShape) { if (outputShape.getPayload() == null) { @@ -174,6 +177,7 @@ public Map constructOperations() { operationModel.setHttpChecksum(op.getHttpChecksum()); operationModel.setRequestCompression(op.getRequestCompression()); operationModel.setStaticContextParams(op.getStaticContextParams()); + operationModel.setAuth(getAuthFromOperation(op)); Input input = op.getInput(); if (input != null) { @@ -183,7 +187,7 @@ public Map constructOperations() { c2jShapes.get(originalShapeName).getDocumentation(); operationModel.setInput(new VariableModel(unCapitalize(inputShape), inputShape) - .withDocumentation(documentation)); + .withDocumentation(documentation)); } @@ -195,7 +199,7 @@ public Map constructOperations() { String documentation = getOperationDocumentation(output, outputShape); operationModel.setReturnType( - new ReturnTypeModel(responseClassName).withDocumentation(documentation)); + new ReturnTypeModel(responseClassName).withDocumentation(documentation)); if (isBlobShape(getPayloadShape(c2jShapes, outputShape))) { operationModel.setHasBlobMemberAsPayload(true); } @@ -208,8 +212,8 @@ public Map constructOperations() { for (ErrorMap error : op.getErrors()) { String documentation = - error.getDocumentation() != null ? error.getDocumentation() : - c2jShapes.get(error.getShape()).getDocumentation(); + error.getDocumentation() != null ? error.getDocumentation() : + c2jShapes.get(error.getShape()).getDocumentation(); Integer httpStatusCode = getHttpStatusCode(error, c2jShapes.get(error.getShape())); @@ -228,6 +232,22 @@ public Map constructOperations() { return javaOperationModels; } + /** + * Returns the list of authTypes defined for an operation. If the new auth member is defined we use it, otherwise we retrofit + * the list with the value of the authType member if present or return an empty list if not. + */ + private List getAuthFromOperation(Operation op) { + List opAuth = op.getAuth(); + if (opAuth != null) { + return opAuth.stream().map(AuthType::fromValue).collect(Collectors.toList()); + } + AuthType legacyAuthType = op.getAuthtype(); + if (legacyAuthType != null) { + return Collections.singletonList(legacyAuthType); + } + return Collections.emptyList(); + } + /** * Get HTTP status code either from error trait on the operation reference or the error trait on the shape. * diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/GeneratorPathProvider.java b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/GeneratorPathProvider.java index 5e680b97b320..8cc7b1f36026 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/GeneratorPathProvider.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/GeneratorPathProvider.java @@ -95,4 +95,13 @@ public String getEndpointRulesInternalResourcesDirectory() { public String getEndpointRulesTestDirectory() { return testDirectory + "/" + Utils.packageToDirectory(model.getMetadata().getFullEndpointRulesPackageName()); } + + public String getAuthSchemeDirectory() { + return sourceDirectory + "/" + Utils.packageToDirectory(model.getMetadata().getFullAuthSchemePackageName()); + } + + public String getAuthSchemeInternalDirectory() { + return sourceDirectory + "/" + Utils.packageToDirectory(model.getMetadata().getFullInternalAuthSchemePackageName()); + } + } 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 new file mode 100644 index 000000000000..6f6ed28820a2 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AuthSchemeGeneratorTasks.java @@ -0,0 +1,86 @@ +/* + * 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.emitters.tasks; + +import java.util.ArrayList; +import java.util.List; +import software.amazon.awssdk.codegen.emitters.GeneratorTask; +import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams; +import software.amazon.awssdk.codegen.emitters.PoetGeneratorTask; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeInterceptorSpec; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeParamsSpec; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeProviderSpec; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils; +import software.amazon.awssdk.codegen.poet.auth.scheme.DefaultAuthSchemeParamsSpec; +import software.amazon.awssdk.codegen.poet.auth.scheme.EndpointBasedAuthSchemeProviderSpec; +import software.amazon.awssdk.codegen.poet.auth.scheme.ModelBasedAuthSchemeProviderSpec; + +public final class AuthSchemeGeneratorTasks extends BaseGeneratorTasks { + private final GeneratorTaskParams generatorTaskParams; + + public AuthSchemeGeneratorTasks(GeneratorTaskParams dependencies) { + super(dependencies); + this.generatorTaskParams = dependencies; + } + + @Override + protected List createTasks() { + AuthSchemeSpecUtils authSchemeSpecUtils = new AuthSchemeSpecUtils(model); + List tasks = new ArrayList<>(); + tasks.add(generateParamsInterface()); + tasks.add(generateProviderInterface()); + tasks.add(generateDefaultParamsImpl()); + tasks.add(generateModelBasedProvider()); + tasks.add(generateAuthSchemeInterceptor()); + if (authSchemeSpecUtils.useEndpointBasedAuthProvider()) { + tasks.add(generateEndpointBasedProvider()); + } + return tasks; + } + + private GeneratorTask generateParamsInterface() { + return new PoetGeneratorTask(authSchemeDir(), model.getFileHeader(), new AuthSchemeParamsSpec(model)); + } + + private GeneratorTask generateDefaultParamsImpl() { + return new PoetGeneratorTask(authSchemeInternalDir(), model.getFileHeader(), new DefaultAuthSchemeParamsSpec(model)); + } + + private GeneratorTask generateProviderInterface() { + return new PoetGeneratorTask(authSchemeDir(), model.getFileHeader(), new AuthSchemeProviderSpec(model)); + } + + private GeneratorTask generateModelBasedProvider() { + return new PoetGeneratorTask(authSchemeInternalDir(), model.getFileHeader(), new ModelBasedAuthSchemeProviderSpec(model)); + } + + private GeneratorTask generateEndpointBasedProvider() { + return new PoetGeneratorTask(authSchemeInternalDir(), model.getFileHeader(), + new EndpointBasedAuthSchemeProviderSpec(model)); + } + + private GeneratorTask generateAuthSchemeInterceptor() { + return new PoetGeneratorTask(authSchemeInternalDir(), model.getFileHeader(), new AuthSchemeInterceptorSpec(model)); + } + + private String authSchemeDir() { + return generatorTaskParams.getPathProvider().getAuthSchemeDirectory(); + } + + private String authSchemeInternalDir() { + return generatorTaskParams.getPathProvider().getAuthSchemeInternalDirectory(); + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AwsGeneratorTasks.java b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AwsGeneratorTasks.java index 197b35f9effa..a6b3b75e4fcc 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AwsGeneratorTasks.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AwsGeneratorTasks.java @@ -27,6 +27,7 @@ public AwsGeneratorTasks(GeneratorTaskParams params) { new PaginatorsGeneratorTasks(params), new EventStreamGeneratorTasks(params), new WaitersGeneratorTasks(params), - new EndpointProviderTasks(params)); + new EndpointProviderTasks(params), + new AuthSchemeGeneratorTasks(params)); } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/CommonClientGeneratorTasks.java b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/CommonClientGeneratorTasks.java index ee772e46040b..96036eb1ee08 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/CommonClientGeneratorTasks.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/CommonClientGeneratorTasks.java @@ -22,6 +22,7 @@ import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams; import software.amazon.awssdk.codegen.poet.builder.BaseClientBuilderClass; import software.amazon.awssdk.codegen.poet.builder.BaseClientBuilderInterface; +import software.amazon.awssdk.codegen.poet.model.ServiceClientConfigurationBuilderClass; import software.amazon.awssdk.codegen.poet.model.ServiceClientConfigurationClass; /** @@ -36,7 +37,8 @@ public CommonClientGeneratorTasks(GeneratorTaskParams dependencies) { protected List createTasks() throws Exception { return Arrays.asList(createBaseBuilderTask(), createBaseBuilderInterfaceTask(), - createServiceClientConfigurationTask()); + createServiceClientConfigurationTask(), + createServiceClientConfigurationBuilderTask()); } private GeneratorTask createBaseBuilderTask() throws IOException { @@ -50,4 +52,8 @@ private GeneratorTask createBaseBuilderInterfaceTask() throws IOException { private GeneratorTask createServiceClientConfigurationTask() throws IOException { return createPoetGeneratorTask(new ServiceClientConfigurationClass(model)); } + + private GeneratorTask createServiceClientConfigurationBuilderTask() throws IOException { + return createPoetGeneratorTask(new ServiceClientConfigurationBuilderClass(model)); + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/CommonGeneratorTasks.java b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/CommonGeneratorTasks.java index 42a06f582542..b312634b9ddc 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/CommonGeneratorTasks.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/CommonGeneratorTasks.java @@ -16,6 +16,7 @@ package software.amazon.awssdk.codegen.emitters.tasks; import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams; +import software.amazon.awssdk.codegen.poet.model.SdkClientConfigurationUtilGeneratorTask; /** * Common generator tasks. @@ -23,6 +24,7 @@ class CommonGeneratorTasks extends CompositeGeneratorTask { CommonGeneratorTasks(GeneratorTaskParams params) { super(new CommonClientGeneratorTasks(params), + new SdkClientConfigurationUtilGeneratorTask(params), new SyncClientGeneratorTasks(params), new MarshallerGeneratorTasks(params), new ModelClassGeneratorTasks(params), diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/EndpointProviderTasks.java b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/EndpointProviderTasks.java index d6a91a967380..b83ff74375b7 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/EndpointProviderTasks.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/EndpointProviderTasks.java @@ -27,7 +27,6 @@ import software.amazon.awssdk.codegen.model.service.ClientContextParam; import software.amazon.awssdk.codegen.poet.rules.ClientContextParamsClassSpec; import software.amazon.awssdk.codegen.poet.rules.DefaultPartitionDataProviderSpec; -import software.amazon.awssdk.codegen.poet.rules.EndpointAuthSchemeInterceptorClassSpec; import software.amazon.awssdk.codegen.poet.rules.EndpointParametersClassSpec; import software.amazon.awssdk.codegen.poet.rules.EndpointProviderInterfaceSpec; import software.amazon.awssdk.codegen.poet.rules.EndpointProviderSpec; @@ -85,9 +84,7 @@ private GeneratorTask generateDefaultPartitionsProvider() { private Collection generateInterceptors() { return Arrays.asList( new PoetGeneratorTask(endpointRulesInternalDir(), model.getFileHeader(), new EndpointResolverInterceptorSpec(model)), - new PoetGeneratorTask(endpointRulesInternalDir(), model.getFileHeader(), new RequestEndpointInterceptorSpec(model)), - new PoetGeneratorTask(endpointRulesInternalDir(), model.getFileHeader(), - new EndpointAuthSchemeInterceptorClassSpec(model))); + new PoetGeneratorTask(endpointRulesInternalDir(), model.getFileHeader(), new RequestEndpointInterceptorSpec(model))); } private GeneratorTask generateClientTests() { diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/internal/Constant.java b/codegen/src/main/java/software/amazon/awssdk/codegen/internal/Constant.java index 538fd383782a..03871f237178 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/internal/Constant.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/internal/Constant.java @@ -70,6 +70,8 @@ public final class Constant { public static final String PACKAGE_NAME_RULES_PATTERN = "%s.endpoints"; + public static final String PACKAGE_NAME_AUTH_SCHEME_PATTERN = "%s.auth.scheme"; + public static final String PACKAGE_NAME_SMOKE_TEST_PATTERN = "%s.smoketests"; public static final String PACKAGE_NAME_CUSTOM_AUTH_PATTERN = "%s.auth"; 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 f54d5db23327..61efc8328d94 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 @@ -16,6 +16,7 @@ package software.amazon.awssdk.codegen.model.config.customization; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -263,6 +264,26 @@ public class CustomizationConfig { */ private boolean requiredTraitValidationEnabled = false; + /** + * Whether SRA based auth logic should be used. + */ + private boolean useSraAuth = false; + + /** + * Whether to generate auth scheme params based on endpoint params. + */ + private boolean enableEndpointAuthSchemeParams = false; + + /** + * List of endpoint params to be used for the auth scheme params + */ + private List allowedEndpointAuthSchemeParams = Collections.emptyList(); + + /** + * Whether the list of allowed endpoint auth scheme params was explicitly configured. + */ + private boolean allowedEndpointAuthSchemeParamsConfigured = false; + /** * Customization to attach map of Custom client param configs that can be set on a client builder. */ @@ -686,6 +707,37 @@ public void setRequiredTraitValidationEnabled(boolean requiredTraitValidationEna this.requiredTraitValidationEnabled = requiredTraitValidationEnabled; } + public void setUseSraAuth(boolean useSraAuth) { + this.useSraAuth = useSraAuth; + } + + // TODO(post-sra-identity-auth): Remove this customization and all related switching logic, keeping only the + // useSraAuth==true branch going forward. + public boolean useSraAuth() { + return useSraAuth; + } + + public void setEnableEndpointAuthSchemeParams(boolean enableEndpointAuthSchemeParams) { + this.enableEndpointAuthSchemeParams = enableEndpointAuthSchemeParams; + } + + public boolean isEnableEndpointAuthSchemeParams() { + return enableEndpointAuthSchemeParams; + } + + public void setAllowedEndpointAuthSchemeParams(List allowedEndpointAuthSchemeParams) { + this.allowedEndpointAuthSchemeParamsConfigured = true; + this.allowedEndpointAuthSchemeParams = allowedEndpointAuthSchemeParams; + } + + public List getAllowedEndpointAuthSchemeParams() { + return this.allowedEndpointAuthSchemeParams; + } + + public boolean getAllowedEndpointAuthSchemeParamsConfigured() { + return allowedEndpointAuthSchemeParamsConfigured; + } + public Map getCustomClientContextParams() { return customClientContextParams; } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/Metadata.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/Metadata.java index 1803ef245d6c..144db3b55438 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/Metadata.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/Metadata.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.codegen.model.intermediate; +import java.util.List; import java.util.Map; import software.amazon.awssdk.awscore.exception.AwsErrorDetails; import software.amazon.awssdk.codegen.model.service.AuthType; @@ -72,6 +73,8 @@ public class Metadata { private String endpointRulesPackageName; + private String authSchemePackageName; + private String serviceAbbreviation; private String serviceFullName; @@ -106,6 +109,20 @@ public class Metadata { private String serviceId; + private List auth; + + public List getAuth() { + return auth; + } + + public void setAuth(List auth) { + this.auth = auth; + } + + public Metadata withAuth(List auth) { + this.auth = auth; + return this; + } public String getApiVersion() { return apiVersion; @@ -723,6 +740,27 @@ public String getFullInternalEndpointRulesPackageName() { return joinPackageNames(getFullEndpointRulesPackageName(), "internal"); } + public void setAuthSchemePackageName(String authSchemePackageName) { + this.authSchemePackageName = authSchemePackageName; + } + + public Metadata withAuthSchemePackageName(String authSchemePackageName) { + setAuthSchemePackageName(authSchemePackageName); + return this; + } + + public String getAuthSchemePackageName() { + return authSchemePackageName; + } + + public String getFullAuthSchemePackageName() { + return joinPackageNames(rootPackageName, getAuthSchemePackageName()); + } + + public String getFullInternalAuthSchemePackageName() { + return joinPackageNames(getFullAuthSchemePackageName(), "internal"); + } + public String getFullInternalPackageName() { return joinPackageNames(getFullClientPackageName(), "internal"); } 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 0510abf6e3be..ece1504be186 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 @@ -54,6 +54,8 @@ public class OperationModel extends DocumentationModel { private AuthType authType; + private List auth; + private boolean isPaginated; private boolean endpointOperation; @@ -141,6 +143,14 @@ public void setAuthType(AuthType authType) { this.authType = authType; } + public List getAuth() { + return auth; + } + + public void setAuth(List auth) { + this.auth = auth; + } + public ShapeModel getInputShape() { return inputShape; } 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 e8a6826c17aa..182ef145f88d 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 @@ -49,6 +49,8 @@ public class Operation { private AuthType authtype; + private List auth; + private boolean httpChecksumRequired; private HttpChecksum httpChecksum; @@ -144,6 +146,14 @@ public void setAuthtype(String authtype) { this.authtype = AuthType.fromValue(authtype); } + public List getAuth() { + return auth; + } + + public void setAuth(List auth) { + this.auth = auth; + } + public String getAuthorizer() { return authorizer; } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/ServiceMetadata.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/ServiceMetadata.java index 83de3b029267..073f97e05f8f 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/ServiceMetadata.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/ServiceMetadata.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.codegen.model.service; +import java.util.List; import java.util.Map; public class ServiceMetadata { @@ -47,6 +48,8 @@ public class ServiceMetadata { private String uid; + private List auth; + private Map protocolSettings; public String getApiVersion() { @@ -156,6 +159,14 @@ public void setUid(String uid) { this.uid = uid; } + public List getAuth() { + return auth; + } + + public void setAuth(List auth) { + this.auth = auth; + } + public Map getProtocolSettings() { return protocolSettings; } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java b/codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java index f764525e67f9..03f718a8c1f4 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java @@ -179,6 +179,11 @@ public String getEndpointRulesPackageName(String serviceName) { return getCustomizedPackageName(concatServiceNameIfShareModel(serviceName), Constant.PACKAGE_NAME_RULES_PATTERN); } + @Override + public String getAuthSchemePackageName(String serviceName) { + return getCustomizedPackageName(concatServiceNameIfShareModel(serviceName), Constant.PACKAGE_NAME_AUTH_SCHEME_PATTERN); + } + @Override public String getSmokeTestPackageName(String serviceName) { diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java b/codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java index 5563e760af3f..f231062c14bf 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java @@ -18,6 +18,7 @@ import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; import software.amazon.awssdk.codegen.model.intermediate.MemberModel; import software.amazon.awssdk.codegen.model.service.Shape; +import software.amazon.awssdk.core.SdkField; /** * Strategy to name various Java constructs based on the naming in the model and potentially customizations. @@ -63,6 +64,11 @@ public interface NamingStrategy { */ String getEndpointRulesPackageName(String serviceName); + /** + * Retrieve the auth scheme package name that should be used based on the service name. + */ + String getAuthSchemePackageName(String serviceName); + /** * Retrieve the smote test package name that should be used based on the service name. */ diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadata.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadata.java new file mode 100644 index 000000000000..90149c984fa5 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadata.java @@ -0,0 +1,234 @@ +/* + * 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 com.squareup.javapoet.MethodSpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; +import software.amazon.awssdk.codegen.model.service.AuthType; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; +import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; +import software.amazon.awssdk.utils.Validate; + +public final class AuthSchemeCodegenMetadata { + + static final AuthSchemeCodegenMetadata SIGV4 = builder() + .schemeId(AwsV4AuthScheme.SCHEME_ID) + .authSchemeClass(AwsV4AuthScheme.class) + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4HttpSigner.class) + .fieldName("SERVICE_SIGNING_NAME") + .valueEmitter((spec, utils) -> spec.addCode("$S", utils.signingName())) + .build()) + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4HttpSigner.class) + .fieldName("REGION_NAME") + .valueEmitter((spec, utils) -> spec.addCode("$L", "params.region().id()")) + .build()) + .build(); + + static final AuthSchemeCodegenMetadata SIGV4_UNSIGNED_BODY = + SIGV4.toBuilder() + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4HttpSigner.class) + .fieldName("PAYLOAD_SIGNING_ENABLED") + .valueEmitter((spec, utils) -> spec.addCode("$L", false)) + .build()) + .build(); + + static final AuthSchemeCodegenMetadata S3 = + SIGV4.toBuilder() + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4HttpSigner.class) + .fieldName("DOUBLE_URL_ENCODE") + .valueEmitter((spec, utils) -> spec.addCode("$L", "false")) + .build()) + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4HttpSigner.class) + .fieldName("NORMALIZE_PATH") + .valueEmitter((spec, utils) -> spec.addCode("$L", "false")) + .build()) + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4HttpSigner.class) + .fieldName("PAYLOAD_SIGNING_ENABLED") + .valueEmitter((spec, utils) -> spec.addCode("$L", false)) + .build()) + .build(); + + static final AuthSchemeCodegenMetadata S3V4 = + SIGV4.toBuilder() + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4HttpSigner.class) + .fieldName("DOUBLE_URL_ENCODE") + .valueEmitter((spec, utils) -> spec.addCode("$L", "false")) + .build()) + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4HttpSigner.class) + .fieldName("NORMALIZE_PATH") + .valueEmitter((spec, utils) -> spec.addCode("$L", "false")) + .build()) + .build(); + + static final AuthSchemeCodegenMetadata BEARER = builder() + .schemeId(BearerAuthScheme.SCHEME_ID) + .authSchemeClass(BearerAuthScheme.class) + .build(); + + static final AuthSchemeCodegenMetadata NO_AUTH = builder() + .schemeId(NoAuthAuthScheme.SCHEME_ID) + .authSchemeClass(NoAuthAuthScheme.class) + .build(); + + private final String schemeId; + private final List properties; + private final Class authSchemeClass; + + private AuthSchemeCodegenMetadata(Builder builder) { + this.schemeId = Validate.paramNotNull(builder.schemeId, "schemeId"); + this.properties = Collections.unmodifiableList(Validate.paramNotNull(builder.properties, "properties")); + this.authSchemeClass = Validate.paramNotNull(builder.authSchemeClass, "authSchemeClass"); + } + + public String schemeId() { + return schemeId; + } + + public Class authSchemeClass() { + return authSchemeClass; + } + + public List properties() { + return properties; + } + + public Builder toBuilder() { + return new Builder(this); + } + + private static Builder builder() { + return new Builder(); + } + + public static AuthSchemeCodegenMetadata fromAuthType(AuthType type) { + switch (type) { + case BEARER: + return BEARER; + case NONE: + return NO_AUTH; + case V4: + return SIGV4; + case V4_UNSIGNED_BODY: + return SIGV4_UNSIGNED_BODY; + case S3: + return S3; + case S3V4: + return S3V4; + default: + throw new IllegalArgumentException("Unknown auth type: " + type); + } + } + + private static class Builder { + private String schemeId; + private List properties = new ArrayList<>(); + private Class authSchemeClass; + + Builder() { + } + + Builder(AuthSchemeCodegenMetadata other) { + this.schemeId = other.schemeId; + this.properties.addAll(other.properties); + this.authSchemeClass = other.authSchemeClass; + } + + public Builder schemeId(String schemeId) { + this.schemeId = schemeId; + return this; + } + + public Builder addProperty(SignerPropertyValueProvider property) { + this.properties.add(property); + return this; + } + + public Builder authSchemeClass(Class authSchemeClass) { + this.authSchemeClass = authSchemeClass; + return this; + } + + public AuthSchemeCodegenMetadata build() { + return new AuthSchemeCodegenMetadata(this); + } + } + + static class SignerPropertyValueProvider { + private final Class containingClass; + private final String fieldName; + private final BiConsumer valueEmitter; + + SignerPropertyValueProvider(Builder builder) { + this.containingClass = Validate.paramNotNull(builder.containingClass, "containingClass"); + this.valueEmitter = Validate.paramNotNull(builder.valueEmitter, "valueEmitter"); + this.fieldName = Validate.paramNotNull(builder.fieldName, "fieldName"); + } + + public Class containingClass() { + return containingClass; + } + + public String fieldName() { + return fieldName; + } + + public void emitValue(MethodSpec.Builder spec, AuthSchemeSpecUtils utils) { + valueEmitter.accept(spec, utils); + } + + private static Builder builder() { + return new Builder(); + } + + static class Builder { + private Class containingClass; + private String fieldName; + private BiConsumer valueEmitter; + + public Builder containingClass(Class containingClass) { + this.containingClass = containingClass; + return this; + } + + public Builder fieldName(String fieldName) { + this.fieldName = fieldName; + return this; + } + + public Builder valueEmitter(BiConsumer valueEmitter) { + this.valueEmitter = valueEmitter; + return this; + } + + public SignerPropertyValueProvider build() { + return new SignerPropertyValueProvider(this); + } + } + } +} 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 new file mode 100644 index 000000000000..5e2621c25735 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java @@ -0,0 +1,380 @@ +/* + * 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 com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeVariableName; +import com.squareup.javapoet.WildcardTypeName; +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 javax.lang.model.element.Modifier; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +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.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.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +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.utils.Logger; +import software.amazon.awssdk.utils.Validate; + +public final class AuthSchemeInterceptorSpec implements ClassSpec { + private final AuthSchemeSpecUtils authSchemeSpecUtils; + private final EndpointRulesSpecUtils endpointRulesSpecUtils; + + public AuthSchemeInterceptorSpec(IntermediateModel intermediateModel) { + this.authSchemeSpecUtils = new AuthSchemeSpecUtils(intermediateModel); + this.endpointRulesSpecUtils = new EndpointRulesSpecUtils(intermediateModel); + } + + @Override + public ClassName className() { + return authSchemeSpecUtils.authSchemeInterceptor(); + } + + @Override + public TypeSpec poetSpec() { + TypeSpec.Builder builder = PoetUtils.createClassBuilder(className()) + .addSuperinterface(ExecutionInterceptor.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addAnnotation(SdkInternalApi.class); + + builder.addField(FieldSpec.builder(Logger.class, "LOG", Modifier.PRIVATE, Modifier.STATIC) + .initializer("$T.loggerFor($T.class)", Logger.class, className()) + .build()); + + builder.addMethod(generateBeforeExecution()) + .addMethod(generateResolveAuthOptions()) + .addMethod(generateSelectAuthScheme()) + .addMethod(generateAuthSchemeParams()) + .addMethod(generateTrySelectAuthScheme()) + .addMethod(generateGetIdentityMetric()); + return builder.build(); + } + + private MethodSpec generateBeforeExecution() { + MethodSpec.Builder builder = MethodSpec.methodBuilder("beforeExecution") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addParameter(Context.BeforeExecution.class, + "context") + .addParameter(ExecutionAttributes.class, + "executionAttributes"); + + builder.addStatement("$T authOptions = resolveAuthOptions(context, executionAttributes)", + listOf(AuthSchemeOption.class)) + .addStatement("$T selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes)", + wildcardSelectedAuthScheme()) + .addStatement("$T.putSelectedAuthScheme(executionAttributes, selectedAuthScheme)", + endpointRulesSpecUtils.rulesRuntimeClassName("AuthSchemeUtils")); + return builder.build(); + } + + private MethodSpec generateResolveAuthOptions() { + MethodSpec.Builder builder = MethodSpec.methodBuilder("resolveAuthOptions") + .addModifiers(Modifier.PRIVATE) + .returns(listOf(AuthSchemeOption.class)) + .addParameter(Context.BeforeExecution.class, + "context") + .addParameter(ExecutionAttributes.class, + "executionAttributes"); + + builder.addStatement("$1T authSchemeProvider = $2T.isInstanceOf($1T.class, executionAttributes" + + ".getAttribute($3T.AUTH_SCHEME_RESOLVER), $4S)", + authSchemeSpecUtils.providerInterfaceName(), + Validate.class, + SdkInternalExecutionAttribute.class, + "Expected an instance of " + authSchemeSpecUtils.providerInterfaceName().simpleName()); + builder.addStatement("$T params = authSchemeParams(context.request(), executionAttributes)", + authSchemeSpecUtils.parametersInterfaceName()); + builder.addStatement("return authSchemeProvider.resolveAuthScheme(params)"); + return builder.build(); + } + + private MethodSpec generateAuthSchemeParams() { + MethodSpec.Builder builder = MethodSpec.methodBuilder("authSchemeParams") + .addModifiers(Modifier.PRIVATE) + .returns(authSchemeSpecUtils.parametersInterfaceName()) + .addParameter(SdkRequest.class, "request") + .addParameter(ExecutionAttributes.class, "executionAttributes"); + + if (!authSchemeSpecUtils.useEndpointBasedAuthProvider()) { + builder.addStatement("$T operation = executionAttributes.getAttribute($T.OPERATION_NAME)", String.class, + SdkExecutionAttribute.class); + 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()); + } + return builder.build(); + } + + builder.addStatement("$T endpointParams = $T.ruleParams(request, executionAttributes)", + endpointRulesSpecUtils.parametersClassName(), + endpointRulesSpecUtils.resolverInterceptorName()); + builder.addStatement("$1T.Builder builder = $1T.builder()", authSchemeSpecUtils.parametersInterfaceName()); + boolean regionIncluded = false; + for (String paramName : endpointRulesSpecUtils.parameters().keySet()) { + if (!authSchemeSpecUtils.includeParamForProvider(paramName)) { + continue; + } + regionIncluded = regionIncluded || paramName.equalsIgnoreCase("region"); + String methodName = endpointRulesSpecUtils.paramMethodName(paramName); + builder.addStatement("builder.$1N(endpointParams.$1N())", methodName); + } + + builder.addStatement("$T operation = executionAttributes.getAttribute($T.OPERATION_NAME)", String.class, + SdkExecutionAttribute.class); + builder.addStatement("builder.operation(operation)"); + if (authSchemeSpecUtils.usesSigV4() && !regionIncluded) { + builder.addStatement("$T region = executionAttributes.getAttribute($T.AWS_REGION)", Region.class, + AwsExecutionAttribute.class); + builder.addStatement("builder.region(region)"); + } + builder.addStatement("return builder.build()"); + return builder.build(); + } + + private MethodSpec generateSelectAuthScheme() { + MethodSpec.Builder builder = MethodSpec.methodBuilder("selectAuthScheme") + .addModifiers(Modifier.PRIVATE) + .returns(wildcardSelectedAuthScheme()) + .addParameter(listOf(AuthSchemeOption.class), "authOptions") + .addParameter(ExecutionAttributes.class, "executionAttributes"); + + builder.addStatement("$T metricCollector = executionAttributes.getAttribute($T.API_CALL_METRIC_COLLECTOR)", + MetricCollector.class, SdkExecutionAttribute.class) + .addStatement("$T authSchemes = executionAttributes.getAttribute($T.AUTH_SCHEMES)", + mapOf(String.class, wildcardAuthScheme()), + SdkInternalExecutionAttribute.class) + .addStatement("$T identityProviders = executionAttributes.getAttribute($T.IDENTITY_PROVIDERS)", + IdentityProviders.class, SdkInternalExecutionAttribute.class) + .addStatement("$T discardedReasons = new $T<>()", + listOfStringSuppliers(), ArrayList.class); + + builder.beginControlFlow("for ($T authOption : authOptions)", AuthSchemeOption.class); + { + builder.addStatement("$T authScheme = authSchemes.get(authOption.schemeId())", wildcardAuthScheme()) + .addStatement("$T selectedAuthScheme = trySelectAuthScheme(authOption, authScheme, identityProviders, " + + "discardedReasons, metricCollector)", + wildcardSelectedAuthScheme()); + builder.beginControlFlow("if (selectedAuthScheme != null)"); + { + addLogDebugDiscardedOptions(builder); + builder.addStatement("return selectedAuthScheme") + .endControlFlow(); + } + // end foreach + builder.endControlFlow(); + } + builder.addStatement("throw $T.builder()" + + ".message($S + discardedReasons.stream().map($T::get).collect($T.joining(\", \")))" + + ".build()", + SdkException.class, + "Failed to determine how to authenticate the user: ", + Supplier.class, + Collectors.class); + return builder.build(); + } + + private MethodSpec generateTrySelectAuthScheme() { + MethodSpec.Builder builder = MethodSpec.methodBuilder("trySelectAuthScheme") + .addModifiers(Modifier.PRIVATE) + .returns(namedSelectedAuthScheme()) + .addParameter(AuthSchemeOption.class, "authOption") + .addParameter(namedAuthScheme(), "authScheme") + .addParameter(IdentityProviders.class, "identityProviders") + .addParameter(listOfStringSuppliers(), "discardedReasons") + .addParameter(MetricCollector.class, "metricCollector") + .addTypeVariable(TypeVariableName.get("T", Identity.class)); + + builder.beginControlFlow("if (authScheme == null)"); + { + builder.addStatement("discardedReasons.add(() -> String.format($S, authOption.schemeId()))", + "'%s' is not enabled for this request.") + .addStatement("return null") + .endControlFlow(); + } + builder.addStatement("$T identityProvider = authScheme.identityProvider(identityProviders)", + namedIdentityProvider()); + + builder.beginControlFlow("if (identityProvider == null)"); + { + builder.addStatement("discardedReasons.add(() -> String.format($S, authOption.schemeId()))", + "'%s' does not have an identity provider configured.") + .addStatement("return null") + .endControlFlow(); + } + + builder.addStatement("$T.Builder identityRequestBuilder = $T.builder()", + ResolveIdentityRequest.class, + ResolveIdentityRequest.class); + builder.addStatement("authOption.forEachIdentityProperty(identityRequestBuilder::putProperty)"); + + builder.addStatement("$T identity", namedIdentityFuture()); + builder.addStatement("$T metric = getIdentityMetric(identityProvider)", durationSdkMetric()); + builder.beginControlFlow("if (metric == null)") + .addStatement("identity = identityProvider.resolveIdentity(identityRequestBuilder.build())") + .nextControlFlow("else") + .addStatement("identity = $T.reportDuration(" + + "() -> identityProvider.resolveIdentity(identityRequestBuilder.build()), metricCollector, metric)", + MetricUtils.class) + .endControlFlow(); + + builder.addStatement("return new $T<>(identity, authScheme.signer(), authOption)", SelectedAuthScheme.class); + return builder.build(); + } + + private MethodSpec generateGetIdentityMetric() { + MethodSpec.Builder builder = MethodSpec.methodBuilder("getIdentityMetric") + .addModifiers(Modifier.PRIVATE) + .returns(durationSdkMetric()) + .addParameter(wildcardIdentityProvider(), "identityProvider"); + + builder.addStatement("Class identityType = identityProvider.identityType()") + .beginControlFlow("if (identityType == $T.class)", AwsCredentialsIdentity.class) + .addStatement("return $T.CREDENTIALS_FETCH_DURATION", CoreMetric.class) + .endControlFlow() + .beginControlFlow("if (identityType == $T.class)", TokenIdentity.class) + .addStatement("return $T.TOKEN_FETCH_DURATION", CoreMetric.class) + .endControlFlow() + .addStatement("return null"); + + return builder.build(); + } + + private void addLogDebugDiscardedOptions(MethodSpec.Builder builder) { + builder.beginControlFlow("if (!discardedReasons.isEmpty())"); + { + builder.addStatement("LOG.debug(() -> String.format(\"%s auth will be used, discarded: '%s'\", " + + "authOption.schemeId(), " + + "discardedReasons.stream().map($T::get).collect($T.joining(\", \"))))", + Supplier.class, Collectors.class) + .endControlFlow(); + } + } + + // IdentityProvider + private TypeName namedIdentityProvider() { + return ParameterizedTypeName.get(ClassName.get(IdentityProvider.class), TypeVariableName.get("T")); + } + + // IdentityProvider + private TypeName wildcardIdentityProvider() { + return ParameterizedTypeName.get(ClassName.get(IdentityProvider.class), WildcardTypeName.subtypeOf(Object.class)); + } + + // CompletableFuture + private TypeName namedIdentityFuture() { + return ParameterizedTypeName.get(ClassName.get(CompletableFuture.class), + WildcardTypeName.subtypeOf(TypeVariableName.get("T"))); + } + + // AuthScheme + private TypeName namedAuthScheme() { + return ParameterizedTypeName.get(ClassName.get(AuthScheme.class), + TypeVariableName.get("T", Identity.class)); + } + + // AuthScheme + private TypeName wildcardAuthScheme() { + return ParameterizedTypeName.get(ClassName.get(AuthScheme.class), + WildcardTypeName.subtypeOf(Object.class)); + } + + // SelectedAuthScheme + private TypeName namedSelectedAuthScheme() { + return ParameterizedTypeName.get(ClassName.get(SelectedAuthScheme.class), + TypeVariableName.get("T", Identity.class)); + } + + // SelectedAuthScheme + private TypeName wildcardSelectedAuthScheme() { + return ParameterizedTypeName.get(ClassName.get(SelectedAuthScheme.class), + WildcardTypeName.subtypeOf(Identity.class)); + } + + // List> + private TypeName listOfStringSuppliers() { + return listOf(ParameterizedTypeName.get(Supplier.class, String.class)); + } + + // Map + private TypeName mapOf(Object keyType, Object valueType) { + return ParameterizedTypeName.get(ClassName.get(Map.class), toTypeName(keyType), toTypeName(valueType)); + } + + // List + private TypeName listOf(Object valueType) { + return ParameterizedTypeName.get(ClassName.get(List.class), toTypeName(valueType)); + } + + // SdkMetric + private ParameterizedTypeName durationSdkMetric() { + return ParameterizedTypeName.get(ClassName.get(SdkMetric.class), toTypeName(Duration.class)); + } + + private TypeName toTypeName(Object valueType) { + TypeName result; + if (valueType instanceof Class) { + result = ClassName.get((Class) valueType); + } else if (valueType instanceof TypeName) { + result = (TypeName) valueType; + } else { + throw new IllegalArgumentException("Don't know how to convert " + valueType + " to TypeName"); + } + return result; + } +} 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 new file mode 100644 index 000000000000..7b61a4eb70f0 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeParamsSpec.java @@ -0,0 +1,195 @@ +/* + * 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 com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import java.util.Map; +import javax.lang.model.element.Modifier; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.model.rules.endpoints.ParameterModel; +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.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +public final class AuthSchemeParamsSpec implements ClassSpec { + private final IntermediateModel intermediateModel; + private final AuthSchemeSpecUtils authSchemeSpecUtils; + private final EndpointRulesSpecUtils endpointRulesSpecUtils; + + public AuthSchemeParamsSpec(IntermediateModel intermediateModel) { + this.intermediateModel = intermediateModel; + this.authSchemeSpecUtils = new AuthSchemeSpecUtils(intermediateModel); + this.endpointRulesSpecUtils = new EndpointRulesSpecUtils(intermediateModel); + } + + @Override + public ClassName className() { + return authSchemeSpecUtils.parametersInterfaceName(); + } + + @Override + public TypeSpec poetSpec() { + TypeSpec.Builder b = PoetUtils.createInterfaceBuilder(className()) + .addSuperinterface(toCopyableBuilderInterface()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(SdkPublicApi.class) + .addJavadoc(interfaceJavadoc()) + .addMethod(builderMethod()) + .addType(builderInterfaceSpec()); + + addAccessorMethods(b); + addToBuilder(b); + return b.build(); + } + + private CodeBlock interfaceJavadoc() { + CodeBlock.Builder b = CodeBlock.builder(); + + b.add("The parameters object used to resolve the auth schemes for the $N service.", + intermediateModel.getMetadata().getServiceName()); + + return b.build(); + } + + private MethodSpec builderMethod() { + return MethodSpec.methodBuilder("builder") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .returns(authSchemeSpecUtils.parametersInterfaceBuilderInterfaceName()) + .addStatement("return $T.builder()", authSchemeSpecUtils.parametersDefaultImplName()) + .addJavadoc("Get a new builder for creating a {@link $T}.", + authSchemeSpecUtils.parametersInterfaceName()) + .build(); + } + + private TypeSpec builderInterfaceSpec() { + TypeSpec.Builder b = TypeSpec.interfaceBuilder(authSchemeSpecUtils.parametersInterfaceBuilderInterfaceName()) + .addSuperinterface(copyableBuilderInterface()) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addJavadoc("A builder for a {@link $T}.", authSchemeSpecUtils.parametersInterfaceName()); + + addBuilderSetterMethods(b); + + b.addMethod(MethodSpec.methodBuilder("build") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .returns(className()) + .addJavadoc("Returns a {@link $T} object that is created from the properties that have been set " + + "on the builder.", authSchemeSpecUtils.parametersInterfaceName()) + .build()); + + return b.build(); + } + + private void addAccessorMethods(TypeSpec.Builder b) { + b.addMethod(MethodSpec.methodBuilder("operation") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .returns(String.class) + .addJavadoc("Returns the operation for which to resolve the auth scheme.") + .build()); + + if (authSchemeSpecUtils.usesSigV4()) { + b.addMethod(MethodSpec.methodBuilder("region") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .returns(Region.class) + .addJavadoc("Returns the region. The region parameter may be used with the $S auth scheme.", + AwsV4AuthScheme.SCHEME_ID) + .build()); + + } + + if (authSchemeSpecUtils.generateEndpointBasedParams()) { + parameters().forEach((name, model) -> { + if (authSchemeSpecUtils.includeParam(name)) { + MethodSpec accessor = endpointRulesSpecUtils.parameterInterfaceAccessorMethod(name, model); + if (model.getDocumentation() != null) { + accessor = accessor.toBuilder().addJavadoc(model.getDocumentation()).build(); + } + b.addMethod(accessor); + } + }); + } + } + + private void addToBuilder(TypeSpec.Builder b) { + ClassName builderClassName = authSchemeSpecUtils.parametersInterfaceBuilderInterfaceName(); + b.addMethod(MethodSpec.methodBuilder("toBuilder") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .returns(builderClassName) + .addJavadoc("Returns a {@link $T} to customize the parameters.", builderClassName) + .build()); + + } + + private void addBuilderSetterMethods(TypeSpec.Builder b) { + b.addMethod(MethodSpec.methodBuilder("operation") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addParameter(ParameterSpec.builder(String.class, "operation").build()) + .returns(authSchemeSpecUtils.parametersInterfaceBuilderInterfaceName()) + .addJavadoc("Set the operation for which to resolve the auth scheme.") + .build()); + + if (authSchemeSpecUtils.usesSigV4()) { + b.addMethod(MethodSpec.methodBuilder("region") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addParameter(ParameterSpec.builder(Region.class, "region").build()) + .returns(authSchemeSpecUtils.parametersInterfaceBuilderInterfaceName()) + .addJavadoc("Set the region. The region parameter may be used with the $S auth scheme.", + AwsV4AuthScheme.SCHEME_ID) + .build()); + + } + + if (authSchemeSpecUtils.generateEndpointBasedParams()) { + parameters().forEach((name, model) -> { + if (authSchemeSpecUtils.includeParam(name)) { + ClassName parametersInterfaceName = authSchemeSpecUtils.parametersInterfaceName(); + MethodSpec setter = endpointRulesSpecUtils + .parameterBuilderSetterMethodDeclaration(parametersInterfaceName, name, model); + if (model.getDocumentation() != null) { + setter = setter.toBuilder().addJavadoc(model.getDocumentation()).build(); + } + b.addMethod(setter); + } + }); + } + } + + private Map parameters() { + return intermediateModel.getEndpointRuleSetModel().getParameters(); + } + + private TypeName toCopyableBuilderInterface() { + return ParameterizedTypeName.get(ClassName.get(ToCopyableBuilder.class), + className().nestedClass("Builder"), + className()); + } + + private TypeName copyableBuilderInterface() { + return ParameterizedTypeName.get(ClassName.get(CopyableBuilder.class), + className().nestedClass("Builder"), + className()); + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeProviderSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeProviderSpec.java new file mode 100644 index 000000000000..3b767b1c2235 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeProviderSpec.java @@ -0,0 +1,115 @@ +/* + * 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 com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import java.util.function.Consumer; +import javax.lang.model.element.Modifier; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.poet.ClassSpec; +import software.amazon.awssdk.codegen.poet.PoetUtils; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider; + +public class AuthSchemeProviderSpec implements ClassSpec { + private final IntermediateModel intermediateModel; + private final AuthSchemeSpecUtils authSchemeSpecUtils; + + public AuthSchemeProviderSpec(IntermediateModel intermediateModel) { + this.intermediateModel = intermediateModel; + this.authSchemeSpecUtils = new AuthSchemeSpecUtils(intermediateModel); + } + + @Override + public ClassName className() { + return authSchemeSpecUtils.providerInterfaceName(); + } + + @Override + public TypeSpec poetSpec() { + return PoetUtils.createInterfaceBuilder(className()) + .addSuperinterface(AuthSchemeProvider.class) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(SdkPublicApi.class) + .addJavadoc(interfaceJavadoc()) + .addMethod(resolveAuthSchemeMethod()) + .addMethod(resolveAuthSchemeConsumerBuilderMethod()) + .addMethod(defaultProviderMethod()) + .build(); + } + + private MethodSpec resolveAuthSchemeMethod() { + MethodSpec.Builder b = MethodSpec.methodBuilder("resolveAuthScheme"); + b.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT); + b.addParameter(authSchemeSpecUtils.parametersInterfaceName(), "authSchemeParams"); + b.returns(authSchemeSpecUtils.resolverReturnType()); + b.addJavadoc(resolveMethodJavadoc()); + return b.build(); + } + + private MethodSpec resolveAuthSchemeConsumerBuilderMethod() { + ClassName parametersInterface = authSchemeSpecUtils.parametersInterfaceName(); + ClassName parametersBuilderInterface = parametersInterface.nestedClass("Builder"); + TypeName consumerType = ParameterizedTypeName.get(ClassName.get(Consumer.class), parametersBuilderInterface); + + MethodSpec.Builder b = MethodSpec.methodBuilder("resolveAuthScheme"); + b.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT); + b.addParameter(consumerType, "consumer"); + b.returns(authSchemeSpecUtils.resolverReturnType()); + b.addJavadoc(resolveMethodJavadoc()); + + b.addStatement("$T builder = $T.builder()", parametersBuilderInterface, parametersInterface); + b.addStatement("consumer.accept(builder)"); + b.addStatement("return resolveAuthScheme(builder.build())"); + + return b.build(); + } + + private MethodSpec defaultProviderMethod() { + return MethodSpec.methodBuilder("defaultProvider") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .returns(className()) + .addJavadoc("Get the default auth scheme provider.") + .addStatement("return $T.create()", authSchemeSpecUtils.defaultAuthSchemeProviderName()) + .build(); + } + + private CodeBlock interfaceJavadoc() { + CodeBlock.Builder b = CodeBlock.builder(); + + b.add("An auth scheme provider for $N service. The auth scheme provider takes a set of parameters using {@link $T}, and " + + "resolves a list of {@link $T} based on the given parameters.", + intermediateModel.getMetadata().getServiceName(), + authSchemeSpecUtils.parametersInterfaceName(), + AuthSchemeOption.class); + + return b.build(); + } + + private CodeBlock resolveMethodJavadoc() { + CodeBlock.Builder b = CodeBlock.builder(); + + b.add("Resolve the auth schemes based on the given set of parameters."); + + return b.build(); + } +} 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 new file mode 100644 index 000000000000..6afb9ebc8d00 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java @@ -0,0 +1,218 @@ +/* + * 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 com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.model.intermediate.OperationModel; +import software.amazon.awssdk.codegen.model.service.AuthType; +import software.amazon.awssdk.codegen.utils.AuthUtils; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; + +public final class AuthSchemeSpecUtils { + private static final Set DEFAULT_AUTH_SCHEME_PARAMS = Collections.unmodifiableSet(setOf("region", "operation")); + private final IntermediateModel intermediateModel; + private final boolean useSraAuth; + private final Set allowedEndpointAuthSchemeParams; + private final boolean allowedEndpointAuthSchemeParamsConfigured; + + public AuthSchemeSpecUtils(IntermediateModel intermediateModel) { + this.intermediateModel = intermediateModel; + CustomizationConfig customization = intermediateModel.getCustomizationConfig(); + this.useSraAuth = customization.useSraAuth(); + if (customization.getAllowedEndpointAuthSchemeParamsConfigured()) { + this.allowedEndpointAuthSchemeParams = Collections.unmodifiableSet( + new HashSet<>(customization.getAllowedEndpointAuthSchemeParams())); + this.allowedEndpointAuthSchemeParamsConfigured = true; + } else { + this.allowedEndpointAuthSchemeParams = Collections.emptySet(); + this.allowedEndpointAuthSchemeParamsConfigured = false; + } + } + + public boolean useSraAuth() { + return useSraAuth; + } + + private String basePackage() { + return intermediateModel.getMetadata().getFullAuthSchemePackageName(); + } + + private String internalPackage() { + return intermediateModel.getMetadata().getFullInternalAuthSchemePackageName(); + } + + public ClassName parametersInterfaceName() { + return ClassName.get(basePackage(), intermediateModel.getMetadata().getServiceName() + "AuthSchemeParams"); + } + + public ClassName parametersInterfaceBuilderInterfaceName() { + return parametersInterfaceName().nestedClass("Builder"); + } + + public ClassName parametersDefaultImplName() { + return ClassName.get(internalPackage(), "Default" + parametersInterfaceName().simpleName()); + } + + public ClassName parametersDefaultBuilderImplName() { + return ClassName.get(internalPackage(), "Default" + parametersInterfaceName().simpleName()); + } + + public ClassName providerInterfaceName() { + return ClassName.get(basePackage(), intermediateModel.getMetadata().getServiceName() + "AuthSchemeProvider"); + } + + public ClassName defaultAuthSchemeProviderName() { + return ClassName.get(internalPackage(), "Default" + providerInterfaceName().simpleName()); + } + + public ClassName modeledAuthSchemeProviderName() { + return ClassName.get(internalPackage(), "Modeled" + providerInterfaceName().simpleName()); + } + + public ClassName authSchemeInterceptor() { + return ClassName.get(internalPackage(), intermediateModel.getMetadata().getServiceName() + "AuthSchemeInterceptor"); + } + + public TypeName resolverReturnType() { + return ParameterizedTypeName.get(List.class, AuthSchemeOption.class); + } + + public boolean usesSigV4() { + return AuthUtils.usesAwsAuth(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 + // time enabled or disabled. + return generateEndpointBasedParams(); + } + + public String paramMethodName(String name) { + return intermediateModel.getNamingStrategy().getVariableName(name); + } + + public boolean generateEndpointBasedParams() { + return intermediateModel.getCustomizationConfig().isEnableEndpointAuthSchemeParams(); + } + + public boolean includeParam(String name) { + if (allowedEndpointAuthSchemeParamsConfigured) { + return allowedEndpointAuthSchemeParams.contains(name); + } + // If no explicit allowed endpoint auth scheme params are configured then by default we include all of them except the + // ones already defined by default. + return !DEFAULT_AUTH_SCHEME_PARAMS.contains(name.toLowerCase(Locale.US)); + } + + public boolean includeParamForProvider(String name) { + if (allowedEndpointAuthSchemeParamsConfigured) { + if (DEFAULT_AUTH_SCHEME_PARAMS.contains(name.toLowerCase(Locale.US))) { + return true; + } + return allowedEndpointAuthSchemeParams.contains(name); + } + return true; + } + + public String serviceName() { + return intermediateModel.getMetadata().getServiceName(); + } + + public String signingName() { + return intermediateModel.getMetadata().getSigningName(); + } + + public Map, List> operationsToAuthType() { + Map, List> authSchemesToOperations = + intermediateModel.getOperations() + .entrySet() + .stream() + .filter(kvp -> !kvp.getValue().getAuth().isEmpty()) + .collect(Collectors.groupingBy(kvp -> kvp.getValue().getAuth(), + Collectors.mapping(Map.Entry::getKey, Collectors.toList()))); + + Map, List> operationsToAuthType = authSchemesToOperations + .entrySet() + .stream() + .sorted(Comparator.comparing(left -> left.getValue().get(0))) + .collect(Collectors.toMap(Map.Entry::getValue, + Map.Entry::getKey, (a, b) -> b, + LinkedHashMap::new)); + + List serviceDefaults = serviceDefaultAuthTypes(); + + // 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; + } + + public List serviceDefaultAuthTypes() { + List modeled = intermediateModel.getMetadata().getAuth(); + if (!modeled.isEmpty()) { + return modeled; + } + return Collections.singletonList(intermediateModel.getMetadata().getAuthType()); + } + + public Set> allServiceConcreteAuthSchemeClasses() { + Set> result = + Stream.concat(intermediateModel.getOperations() + .values() + .stream() + .map(OperationModel::getAuth) + .flatMap(List::stream), + intermediateModel.getMetadata().getAuth().stream()) + .map(AuthSchemeCodegenMetadata::fromAuthType) + .map(AuthSchemeCodegenMetadata::authSchemeClass) + .collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Class::getSimpleName)))); + + if (useEndpointBasedAuthProvider()) { + // sigv4a is not modeled but needed for the endpoints based auth-scheme cases. + result.add(AwsV4aAuthScheme.class); + } + // Make the no-auth scheme available. + result.add(NoAuthAuthScheme.class); + return result; + } + + private static Set setOf(String v1, String v2) { + Set set = new HashSet<>(); + set.add(v1); + set.add(v2); + return set; + } +} 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 new file mode 100644 index 000000000000..c2617f0b27d4 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/DefaultAuthSchemeParamsSpec.java @@ -0,0 +1,237 @@ +/* + * 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 com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import java.util.Map; +import javax.lang.model.element.Modifier; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.model.rules.endpoints.ParameterModel; +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.regions.Region; +import software.amazon.awssdk.utils.Validate; + +public class DefaultAuthSchemeParamsSpec implements ClassSpec { + + private final IntermediateModel intermediateModel; + private final AuthSchemeSpecUtils authSchemeSpecUtils; + private final EndpointRulesSpecUtils endpointRulesSpecUtils; + + public DefaultAuthSchemeParamsSpec(IntermediateModel intermediateModel) { + this.intermediateModel = intermediateModel; + this.authSchemeSpecUtils = new AuthSchemeSpecUtils(intermediateModel); + this.endpointRulesSpecUtils = new EndpointRulesSpecUtils(intermediateModel); + } + + @Override + public ClassName className() { + return authSchemeSpecUtils.parametersDefaultImplName(); + } + + @Override + public TypeSpec poetSpec() { + TypeSpec.Builder b = PoetUtils.createClassBuilder(className()) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addAnnotation(SdkInternalApi.class) + .addSuperinterface(authSchemeSpecUtils.parametersInterfaceName()) + .addMethod(constructor()) + .addMethod(builderMethod()) + .addType(builderImplSpec()); + + addFieldsAndAccessors(b); + addToBuilder(b); + return b.build(); + } + + private MethodSpec constructor() { + MethodSpec.Builder b = MethodSpec.constructorBuilder() + .addModifiers(Modifier.PRIVATE) + .addParameter(builderClassName(), "builder") + .addStatement("this.operation = $T.paramNotNull(builder.operation, \"operation\")", + Validate.class); + + if (authSchemeSpecUtils.usesSigV4()) { + b.addStatement("this.region = builder.region"); + } + + if (authSchemeSpecUtils.generateEndpointBasedParams()) { + parameters().forEach((name, model) -> { + if (authSchemeSpecUtils.includeParam(name)) { + String fieldName = authSchemeSpecUtils.paramMethodName(name); + boolean isRequired = isParamRequired(model); + if (isRequired) { + b.addStatement("this.$1N = $2T.paramNotNull(builder.$1N, $1S)", + fieldName, Validate.class); + } else { + b.addStatement("this.$1N = builder.$1N", fieldName); + } + + } + }); + } + + return b.build(); + } + + private boolean isParamRequired(ParameterModel model) { + Boolean isRequired = model.isRequired(); + return (isRequired != null && isRequired) || model.getDefault() != null; + } + + private MethodSpec builderMethod() { + return MethodSpec.methodBuilder("builder") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .returns(authSchemeSpecUtils.parametersInterfaceBuilderInterfaceName()) + .addStatement("return new $T()", builderClassName()) + .build(); + } + + private TypeSpec builderImplSpec() { + TypeSpec.Builder b = TypeSpec.classBuilder(builderClassName()) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .addSuperinterface(authSchemeSpecUtils.parametersInterfaceBuilderInterfaceName()); + + addBuilderConstructors(b); + addBuilderFieldsAndSetter(b); + + b.addMethod(MethodSpec.methodBuilder("build") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(authSchemeSpecUtils.parametersInterfaceName()) + .addStatement("return new $T(this)", className()) + .build()); + + return b.build(); + } + + private void addBuilderConstructors(TypeSpec.Builder b) { + b.addMethod(MethodSpec.constructorBuilder() + .build()); + + MethodSpec.Builder builderFromInstance = MethodSpec.constructorBuilder() + .addParameter(className(), "params"); + + builderFromInstance.addStatement("this.operation = params.operation"); + if (authSchemeSpecUtils.usesSigV4()) { + builderFromInstance.addStatement("this.region = params.region"); + } + if (authSchemeSpecUtils.generateEndpointBasedParams()) { + parameters().forEach((name, model) -> { + if (authSchemeSpecUtils.includeParam(name)) { + builderFromInstance.addStatement("this.$1N = params.$1N", endpointRulesSpecUtils.variableName(name)); + } + }); + } + b.addMethod(builderFromInstance.build()); + } + + private void addFieldsAndAccessors(TypeSpec.Builder b) { + b.addField(FieldSpec.builder(String.class, "operation") + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build()); + + b.addMethod(MethodSpec.methodBuilder("operation") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(String.class) + .addStatement("return operation") + .build()); + + if (authSchemeSpecUtils.usesSigV4()) { + b.addField(FieldSpec.builder(Region.class, "region") + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build()); + + b.addMethod(MethodSpec.methodBuilder("region") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(Region.class) + .addStatement("return region") + .build()); + } + + if (authSchemeSpecUtils.generateEndpointBasedParams()) { + parameters().forEach((name, model) -> { + if (authSchemeSpecUtils.includeParam(name)) { + b.addField(endpointRulesSpecUtils.parameterClassField(name, model)); + b.addMethod(endpointRulesSpecUtils.parameterClassAccessorMethod(name, model) + .toBuilder() + .addAnnotation(Override.class) + .build()); + } + }); + } + } + + private void addToBuilder(TypeSpec.Builder b) { + b.addMethod(MethodSpec.methodBuilder("toBuilder") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(authSchemeSpecUtils.parametersInterfaceBuilderInterfaceName()) + .addStatement("return new $T(this)", builderClassName()) + .build()); + } + + private void addBuilderFieldsAndSetter(TypeSpec.Builder b) { + b.addField(FieldSpec.builder(String.class, "operation") + .addModifiers(Modifier.PRIVATE) + .build()); + b.addMethod(builderSetterMethod("operation", TypeName.get(String.class))); + + if (authSchemeSpecUtils.usesSigV4()) { + b.addField(FieldSpec.builder(Region.class, "region") + .addModifiers(Modifier.PRIVATE) + .build()); + b.addMethod(builderSetterMethod("region", TypeName.get(Region.class))); + } + + if (authSchemeSpecUtils.generateEndpointBasedParams()) { + parameters().forEach((name, model) -> { + if (authSchemeSpecUtils.includeParam(name)) { + b.addField(endpointRulesSpecUtils.parameterBuilderFieldSpec(name, model)); + b.addMethod(endpointRulesSpecUtils.parameterBuilderSetterMethod(className(), name, model)); + } + }); + } + } + + private MethodSpec builderSetterMethod(String field, TypeName type) { + return MethodSpec.methodBuilder(field) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addParameter(ParameterSpec.builder(type, field).build()) + .returns(builderClassName()) + .addStatement("this.$L = $L", field, field) + .addStatement("return this") + .build(); + } + + private ClassName builderClassName() { + return className().nestedClass("Builder"); + } + + private Map parameters() { + return intermediateModel.getEndpointRuleSetModel().getParameters(); + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/EndpointBasedAuthSchemeProviderSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/EndpointBasedAuthSchemeProviderSpec.java new file mode 100644 index 000000000000..f311c05316f0 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/EndpointBasedAuthSchemeProviderSpec.java @@ -0,0 +1,195 @@ +/* + * 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 com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.Modifier; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; +import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.model.rules.endpoints.ParameterModel; +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.endpoints.Endpoint; +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.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.utils.CompletableFutureUtils; +import software.amazon.awssdk.utils.Validate; + +public class EndpointBasedAuthSchemeProviderSpec implements ClassSpec { + private final AuthSchemeSpecUtils authSchemeSpecUtils; + private final EndpointRulesSpecUtils endpointRulesSpecUtils; + + public EndpointBasedAuthSchemeProviderSpec(IntermediateModel intermediateModel) { + this.authSchemeSpecUtils = new AuthSchemeSpecUtils(intermediateModel); + this.endpointRulesSpecUtils = new EndpointRulesSpecUtils(intermediateModel); + } + + @Override + public ClassName className() { + return authSchemeSpecUtils.defaultAuthSchemeProviderName(); + } + + @Override + public TypeSpec poetSpec() { + return PoetUtils.createClassBuilder(className()) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addAnnotation(SdkInternalApi.class) + .addSuperinterface(authSchemeSpecUtils.providerInterfaceName()) + .addMethod(constructor()) + .addField(defaultInstance()) + .addField(modeledResolverInstance()) + .addField(endpointDelegateInstance()) + .addMethod(createMethod()) + .addMethod(resolveAuthSchemeMethod()) + .build(); + } + + private MethodSpec constructor() { + return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build(); + } + + private FieldSpec defaultInstance() { + return FieldSpec.builder(className(), "DEFAULT") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer("new $T()", className()) + .build(); + } + + private FieldSpec endpointDelegateInstance() { + return FieldSpec.builder(endpointRulesSpecUtils.providerInterfaceName(), "DELEGATE") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer("$T.defaultProvider()", endpointRulesSpecUtils.providerInterfaceName()) + .build(); + } + + private FieldSpec modeledResolverInstance() { + return FieldSpec.builder(authSchemeSpecUtils.providerInterfaceName(), "MODELED_RESOLVER") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer("$T.create()", authSchemeSpecUtils.modeledAuthSchemeProviderName()) + .build(); + } + + private MethodSpec createMethod() { + return MethodSpec.methodBuilder("create") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .returns(authSchemeSpecUtils.providerInterfaceName()) + .addStatement("return DEFAULT") + .build(); + } + + private MethodSpec resolveAuthSchemeMethod() { + MethodSpec.Builder spec = MethodSpec.methodBuilder("resolveAuthScheme") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(authSchemeSpecUtils.resolverReturnType()) + .addParameter(authSchemeSpecUtils.parametersInterfaceName(), "params"); + + spec.addCode("$1T endpointParameters = $1T.builder()\n$>", + endpointRulesSpecUtils.parametersClassName()); + + parameters().forEach((name, model) -> { + if (authSchemeSpecUtils.includeParamForProvider(name)) { + spec.addCode(".$1L(params.$1L())\n", endpointRulesSpecUtils.paramMethodName(name)); + } + }); + spec.addStatement(".build()"); + spec.addStatement("$T endpoint = $T.joinLikeSync(DELEGATE.resolveEndpoint(endpointParameters))", + Endpoint.class, CompletableFutureUtils.class); + spec.addStatement("$T authSchemes = endpoint.attribute($T.AUTH_SCHEMES)", + ParameterizedTypeName.get(List.class, EndpointAuthScheme.class), AwsEndpointAttribute.class); + spec.beginControlFlow("if (authSchemes == null)"); + spec.addStatement("return MODELED_RESOLVER.resolveAuthScheme(params)"); + spec.endControlFlow(); + + + spec.addStatement("$T options = new $T<>()", ParameterizedTypeName.get(List.class, AuthSchemeOption.class), + TypeName.get(ArrayList.class)); + spec.beginControlFlow("for ($T authScheme : authSchemes)", EndpointAuthScheme.class); + addAuthSchemeSwitch(spec); + spec.endControlFlow(); + return spec.addStatement("return $T.unmodifiableList(options)", Collections.class) + .build(); + } + + private void addAuthSchemeSwitch(MethodSpec.Builder spec) { + spec.addStatement("$T name = authScheme.name()", String.class); + spec.beginControlFlow("switch(name)"); + addAuthSchemeSwitchSigV4Case(spec); + addAuthSchemeSwitchSigV4aCase(spec); + addAuthSchemeSwitchDefaultCase(spec); + spec.endControlFlow(); + } + + private void addAuthSchemeSwitchSigV4Case(MethodSpec.Builder spec) { + spec.addCode("case $S:", "sigv4"); + spec.addStatement("$T sigv4AuthScheme = $T.isInstanceOf($T.class, authScheme, $S, authScheme.getClass().getName())", + SigV4AuthScheme.class, Validate.class, SigV4AuthScheme.class, + "Expecting auth scheme of class SigV4AuthScheme, got instead object of class %s"); + + spec.addCode("options.add($T.builder().schemeId($S)", AuthSchemeOption.class, AwsV4AuthScheme.SCHEME_ID) + .addCode(".putSignerProperty($T.SERVICE_SIGNING_NAME, sigv4AuthScheme.signingName())", AwsV4HttpSigner.class) + .addCode(".putSignerProperty($T.REGION_NAME, sigv4AuthScheme.signingRegion())", AwsV4HttpSigner.class) + .addCode(".putSignerProperty($T.DOUBLE_URL_ENCODE, !sigv4AuthScheme.disableDoubleEncoding())", + AwsV4HttpSigner.class) + .addCode(".build());"); + spec.addStatement("break"); + } + + private void addAuthSchemeSwitchSigV4aCase(MethodSpec.Builder spec) { + spec.addCode("case $S:", "sigv4a"); + + spec.addStatement("$T sigv4aAuthScheme = $T.isInstanceOf($T.class, authScheme, $S, authScheme.getClass().getName())", + SigV4aAuthScheme.class, Validate.class, SigV4aAuthScheme.class, + "Expecting auth scheme of class SigV4AuthScheme, got instead object of class %s"); + + spec.addStatement("$1T regionSet = $1T.create(sigv4aAuthScheme.signingRegionSet())", RegionSet.class); + + spec.addCode("options.add($T.builder().schemeId($S)", AuthSchemeOption.class, AwsV4aAuthScheme.SCHEME_ID) + .addCode(".putSignerProperty($T.SERVICE_SIGNING_NAME, sigv4aAuthScheme.signingName())", AwsV4aHttpSigner.class) + .addCode(".putSignerProperty($T.REGION_SET, regionSet)", AwsV4aHttpSigner.class) + .addCode(".putSignerProperty($T.DOUBLE_URL_ENCODE, !sigv4aAuthScheme.disableDoubleEncoding())", + AwsV4aHttpSigner.class) + .addCode(".build());"); + spec.addStatement("break"); + } + + private void addAuthSchemeSwitchDefaultCase(MethodSpec.Builder spec) { + spec.addCode("default:"); + spec.addStatement("throw new $T($S + name)", IllegalArgumentException.class, "Unknown auth scheme: "); + } + + private Map parameters() { + return endpointRulesSpecUtils.parameters(); + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelBasedAuthSchemeProviderSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelBasedAuthSchemeProviderSpec.java new file mode 100644 index 000000000000..9f9f0abcf04b --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelBasedAuthSchemeProviderSpec.java @@ -0,0 +1,146 @@ +/* + * 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 static software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeCodegenMetadata.SignerPropertyValueProvider; +import static software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeCodegenMetadata.fromAuthType; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.Modifier; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.model.service.AuthType; +import software.amazon.awssdk.codegen.poet.ClassSpec; +import software.amazon.awssdk.codegen.poet.PoetUtils; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; + +public class ModelBasedAuthSchemeProviderSpec implements ClassSpec { + private final AuthSchemeSpecUtils authSchemeSpecUtils; + + public ModelBasedAuthSchemeProviderSpec(IntermediateModel intermediateModel) { + this.authSchemeSpecUtils = new AuthSchemeSpecUtils(intermediateModel); + } + + @Override + public ClassName className() { + if (authSchemeSpecUtils.useEndpointBasedAuthProvider()) { + return authSchemeSpecUtils.modeledAuthSchemeProviderName(); + } + return authSchemeSpecUtils.defaultAuthSchemeProviderName(); + } + + @Override + public TypeSpec poetSpec() { + return PoetUtils.createClassBuilder(className()) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addAnnotation(SdkInternalApi.class) + .addSuperinterface(authSchemeSpecUtils.providerInterfaceName()) + .addMethod(constructor()) + .addField(defaultInstance()) + .addMethod(createMethod()) + .addMethod(resolveAuthSchemeMethod()) + .build(); + } + + private FieldSpec defaultInstance() { + return FieldSpec.builder(className(), "DEFAULT") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer("new $T()", className()) + .build(); + } + + private MethodSpec constructor() { + return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build(); + } + + private MethodSpec createMethod() { + return MethodSpec.methodBuilder("create") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .returns(className()) + .addStatement("return DEFAULT") + .build(); + } + + private MethodSpec resolveAuthSchemeMethod() { + MethodSpec.Builder spec = MethodSpec.methodBuilder("resolveAuthScheme") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(authSchemeSpecUtils.resolverReturnType()) + .addParameter(authSchemeSpecUtils.parametersInterfaceName(), "params"); + + spec.addStatement("$T options = new $T<>()", ParameterizedTypeName.get(List.class, AuthSchemeOption.class), + TypeName.get(ArrayList.class)); + + Map, List> operationsToAuthType = authSchemeSpecUtils.operationsToAuthType(); + + // All the operations share the same set of auth schemes, no need to create a switch statement. + if (operationsToAuthType.size() == 1) { + List types = operationsToAuthType.get(Collections.emptyList()); + for (AuthType authType : types) { + addAuthTypeProperties(spec, authType); + } + return spec.addStatement("return $T.unmodifiableList(options)", Collections.class) + .build(); + } + spec.beginControlFlow("switch(params.operation())"); + operationsToAuthType.forEach((ops, schemes) -> { + if (!ops.isEmpty()) { + addCasesForOperations(spec, ops, schemes); + } + }); + addCasesForOperations(spec, Collections.emptyList(), operationsToAuthType.get(Collections.emptyList())); + spec.endControlFlow(); + + return spec.addStatement("return $T.unmodifiableList(options)", Collections.class) + .build(); + } + + private void addCasesForOperations(MethodSpec.Builder spec, List operations, List schemes) { + if (operations.isEmpty()) { + spec.addCode("default:"); + } else { + for (String name : operations) { + spec.addCode("case $S:", name); + } + } + for (AuthType authType : schemes) { + addAuthTypeProperties(spec, authType); + } + spec.addStatement("break"); + } + + public void addAuthTypeProperties(MethodSpec.Builder spec, AuthType authType) { + AuthSchemeCodegenMetadata metadata = fromAuthType(authType); + spec.addCode("options.add($T.builder().schemeId($S)", + AuthSchemeOption.class, metadata.schemeId()); + for (SignerPropertyValueProvider property : metadata.properties()) { + spec.addCode(".putSignerProperty($T.$N, ", property.containingClass(), property.fieldName()); + property.emitValue(spec, authSchemeSpecUtils); + spec.addCode(")"); + + } + spec.addCode(".build());\n"); + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderClass.java index 3ff2b99ec98e..25bc9ad11671 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderClass.java @@ -20,10 +20,9 @@ import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeSpec; -import java.net.URI; +import com.squareup.javapoet.WildcardTypeName; import javax.lang.model.element.Modifier; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.codegen.model.config.customization.MultipartCustomization; import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; @@ -34,7 +33,8 @@ import software.amazon.awssdk.codegen.utils.AuthUtils; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; -import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; public class AsyncClientBuilderClass implements ClassSpec { private final IntermediateModel model; @@ -79,7 +79,7 @@ public TypeSpec poetSpec() { builder.addMethod(endpointProviderMethod()); if (AuthUtils.usesBearerAuth(model)) { - builder.addMethod(bearerTokenProviderMethod()); + builder.addMethod(tokenProviderMethod()); } MultipartCustomization multipartCustomization = model.getCustomizationConfig().getMultipartCustomization(); @@ -89,7 +89,7 @@ public TypeSpec poetSpec() { } builder.addMethod(buildClientMethod()); - builder.addMethod(initializeServiceClientConfigMethod()); + builder.addMethod(SyncClientBuilderClass.initializeServiceClientConfigMethod(serviceConfigClassName)); return builder.build(); } @@ -153,12 +153,14 @@ private MethodSpec buildClientMethod() { return builder.build(); } - private MethodSpec bearerTokenProviderMethod() { + private MethodSpec tokenProviderMethod() { + ParameterizedTypeName tokenProviderTypeName = ParameterizedTypeName.get(ClassName.get(IdentityProvider.class), + WildcardTypeName.subtypeOf(TokenIdentity.class)); return MethodSpec.methodBuilder("tokenProvider").addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) - .addParameter(SdkTokenProvider.class, "tokenProvider") + .addParameter(tokenProviderTypeName, "tokenProvider") .returns(builderClassName) - .addStatement("clientConfiguration.option($T.TOKEN_PROVIDER, tokenProvider)", + .addStatement("clientConfiguration.option($T.TOKEN_IDENTITY_PROVIDER, tokenProvider)", AwsClientOption.class) .addStatement("return this") .build(); @@ -190,29 +192,6 @@ private MethodSpec multipartConfigMethods(MultipartCustomization multipartCustom .build(); } - private MethodSpec initializeServiceClientConfigMethod() { - return MethodSpec.methodBuilder("initializeServiceClientConfig").addModifiers(Modifier.PRIVATE) - .addParameter(SdkClientConfiguration.class, "clientConfig") - .returns(serviceConfigClassName) - .addStatement("$T endpointOverride = null", URI.class) - .addStatement("$T endpointProvider = clientConfig.option($T.ENDPOINT_PROVIDER)", - EndpointProvider.class, - SdkClientOption.class) - .addCode("if (clientConfig.option($T.ENDPOINT_OVERRIDDEN) != null" - + "&& $T.TRUE.equals(clientConfig.option($T.ENDPOINT_OVERRIDDEN))) {" - + "endpointOverride = clientConfig.option($T.ENDPOINT);" - + "}", - SdkClientOption.class, Boolean.class, SdkClientOption.class, SdkClientOption.class) - .addStatement("return $T.builder()" - + ".overrideConfiguration(overrideConfiguration())" - + ".region(clientConfig.option($T.AWS_REGION))" - + ".endpointOverride(endpointOverride)" - + ".endpointProvider(endpointProvider)" - + ".build()", - serviceConfigClassName, AwsClientOption.class) - .build(); - } - @Override public ClassName className() { return builderClassName; 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 b5fa79c68039..e54b681c5b03 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 @@ -27,15 +27,17 @@ import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; +import com.squareup.javapoet.WildcardTypeName; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.lang.model.element.Modifier; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.auth.signer.Aws4Signer; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider; import software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner; import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; @@ -47,8 +49,12 @@ import software.amazon.awssdk.codegen.model.service.ClientContextParam; import software.amazon.awssdk.codegen.poet.ClassSpec; import software.amazon.awssdk.codegen.poet.PoetUtils; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils; +import software.amazon.awssdk.codegen.poet.model.ServiceClientConfigurationUtils; import software.amazon.awssdk.codegen.poet.rules.EndpointRulesSpecUtils; import software.amazon.awssdk.codegen.utils.AuthUtils; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; @@ -59,25 +65,40 @@ 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.spi.scheme.AuthScheme; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.protocols.query.interceptor.QueryParametersToBodyInterceptor; import software.amazon.awssdk.utils.AttributeMap; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.StringUtils; import software.amazon.awssdk.utils.Validate; +import software.amazon.awssdk.utils.internal.CodegenNamingUtils; public class BaseClientBuilderClass implements ClassSpec { + private static final ParameterizedTypeName GENERIC_AUTH_SCHEME_TYPE = + ParameterizedTypeName.get(ClassName.get(AuthScheme.class), WildcardTypeName.subtypeOf(Object.class)); + private final IntermediateModel model; private final ClassName builderInterfaceName; private final ClassName builderClassName; + private final ClassName sdkClientConfigurationUtilClassName; private final String basePackage; private final EndpointRulesSpecUtils endpointRulesSpecUtils; + private final AuthSchemeSpecUtils authSchemeSpecUtils; + private final ServiceClientConfigurationUtils configurationUtils; public BaseClientBuilderClass(IntermediateModel model) { this.model = model; this.basePackage = model.getMetadata().getFullClientPackageName(); this.builderInterfaceName = ClassName.get(basePackage, model.getMetadata().getBaseBuilderInterface()); this.builderClassName = ClassName.get(basePackage, model.getMetadata().getBaseBuilder()); + this.sdkClientConfigurationUtilClassName = ClassName.get(model.getMetadata().getFullClientInternalPackageName(), + "SdkClientConfigurationUtil"); this.endpointRulesSpecUtils = new EndpointRulesSpecUtils(model); + this.authSchemeSpecUtils = new AuthSchemeSpecUtils(model); + this.configurationUtils = new ServiceClientConfigurationUtils(model); } @Override @@ -102,6 +123,16 @@ public TypeSpec poetSpec() { .build()); } + if (authSchemeSpecUtils.useSraAuth()) { + builder.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(Map.class), + ClassName.get(String.class), + GENERIC_AUTH_SCHEME_TYPE), + "additionalAuthSchemes") + .addModifiers(PRIVATE, FINAL) + .initializer("new $T<>()", HashMap.class) + .build()); + } + builder.addMethod(serviceEndpointPrefixMethod()); builder.addMethod(serviceNameMethod()); builder.addMethod(mergeServiceDefaultsMethod()); @@ -109,10 +140,19 @@ public TypeSpec poetSpec() { mergeInternalDefaultsMethod().ifPresent(builder::addMethod); builder.addMethod(finalizeServiceConfigurationMethod()); - defaultAwsAuthSignerMethod().ifPresent(builder::addMethod); + if (!authSchemeSpecUtils.useSraAuth()) { + defaultAwsAuthSignerMethod().ifPresent(builder::addMethod); + } builder.addMethod(signingNameMethod()); builder.addMethod(defaultEndpointProviderMethod()); + if (authSchemeSpecUtils.useSraAuth()) { + builder.addMethod(authSchemeProviderMethod()); + builder.addMethod(defaultAuthSchemeProviderMethod()); + builder.addMethod(putAuthSchemeMethod()); + builder.addMethod(authSchemesMethod()); + } + if (hasClientContextParams()) { model.getClientContextParams().forEach((n, m) -> { builder.addMethod(clientContextParamSetter(n, m)); @@ -132,11 +172,13 @@ public TypeSpec poetSpec() { if (AuthUtils.usesBearerAuth(model)) { builder.addMethod(defaultBearerTokenProviderMethod()); - builder.addMethod(defaultTokenAuthSignerMethod()); + if (!authSchemeSpecUtils.useSraAuth()) { + builder.addMethod(defaultTokenAuthSignerMethod()); + } } - + builder.addMethod(setOverridesMethod()); addServiceHttpConfigIfNeeded(builder, model); - + builder.addMethod(invokePluginsMethod()); builder.addMethod(validateClientOptionsMethod()); @@ -197,9 +239,13 @@ private MethodSpec mergeServiceDefaultsMethod() { builder.addCode(".option($T.ENDPOINT_PROVIDER, defaultEndpointProvider())", SdkClientOption.class); - - if (defaultAwsAuthSignerMethod().isPresent()) { - builder.addCode(".option($T.SIGNER, defaultSigner())\n", SdkAdvancedClientOption.class); + if (authSchemeSpecUtils.useSraAuth()) { + builder.addCode(".option($T.AUTH_SCHEME_PROVIDER, defaultAuthSchemeProvider())", SdkClientOption.class); + builder.addCode(".option($T.AUTH_SCHEMES, authSchemes())", SdkClientOption.class); + } else { + if (defaultAwsAuthSignerMethod().isPresent()) { + builder.addCode(".option($T.SIGNER, defaultSigner())\n", SdkAdvancedClientOption.class); + } } builder.addCode(".option($T.CRC32_FROM_COMPRESSED_DATA_ENABLED, $L)\n", SdkClientOption.class, crc32FromCompressedDataEnabled); @@ -211,8 +257,10 @@ private MethodSpec mergeServiceDefaultsMethod() { } if (AuthUtils.usesBearerAuth(model)) { - builder.addCode(".option($T.TOKEN_PROVIDER, defaultTokenProvider())\n", AwsClientOption.class); - builder.addCode(".option($T.TOKEN_SIGNER, defaultTokenSigner())", SdkAdvancedClientOption.class); + builder.addCode(".option($T.TOKEN_IDENTITY_PROVIDER, defaultTokenProvider())\n", AwsClientOption.class); + if (!authSchemeSpecUtils.useSraAuth()) { + builder.addCode(".option($T.TOKEN_SIGNER, defaultTokenSigner())", SdkAdvancedClientOption.class); + } } builder.addCode(");"); @@ -264,8 +312,10 @@ private MethodSpec finalizeServiceConfigurationMethod() { List builtInInterceptors = new ArrayList<>(); + if (authSchemeSpecUtils.useSraAuth()) { + builtInInterceptors.add(authSchemeSpecUtils.authSchemeInterceptor()); + } builtInInterceptors.add(endpointRulesSpecUtils.resolverInterceptorName()); - builtInInterceptors.add(endpointRulesSpecUtils.authSchemesInterceptorName()); builtInInterceptors.add(endpointRulesSpecUtils.requestModifierInterceptorName()); for (String interceptor : model.getCustomizationConfig().getInterceptors()) { @@ -323,8 +373,21 @@ private MethodSpec finalizeServiceConfigurationMethod() { } // Update configuration + builder.addStatement("$T builder = config.toBuilder()", SdkClientConfiguration.Builder.class); + if (AuthUtils.usesBearerAuth(model)) { + builder.addStatement("$T identityProvider = config.option($T.TOKEN_IDENTITY_PROVIDER)", + IdentityProvider.class, TokenIdentity.class, AwsClientOption.class); + builder.beginControlFlow("if (identityProvider != null)"); + builder.addStatement("$T identityProviders = config.option($T.IDENTITY_PROVIDERS)", + IdentityProviders.class, SdkClientOption.class); + + builder.addStatement("builder.option($T.IDENTITY_PROVIDERS, identityProviders.toBuilder()" + + ".putIdentityProvider(identityProvider).build())", SdkClientOption.class); + + builder.endControlFlow(); + } - builder.addCode("return config.toBuilder()\n"); + builder.addCode("builder.option($1T.EXECUTION_INTERCEPTORS, interceptors)", SdkClientOption.class); if (model.getCustomizationConfig().getServiceConfig().hasDualstackProperty()) { builder.addCode(".option($T.DUALSTACK_ENDPOINT_ENABLED, finalServiceConfig.dualstackEnabled())", @@ -340,7 +403,6 @@ private MethodSpec finalizeServiceConfigurationMethod() { SdkClientOption.class); } - builder.addCode(".option($1T.EXECUTION_INTERCEPTORS, interceptors)", SdkClientOption.class); if (StringUtils.isNotBlank(model.getCustomizationConfig().getCustomRetryPolicy())) { builder.addCode(".option($1T.RETRY_POLICY, $2T.resolveRetryPolicy(config))", @@ -361,8 +423,8 @@ private MethodSpec finalizeServiceConfigurationMethod() { builder.addCode(".option($T.CLIENT_CONTEXT_PARAMS, clientContextParams.build())", SdkClientOption.class); } - builder.addCode(".build();"); - + builder.addCode(";\n"); + builder.addStatement("return builder.build()"); return builder.build(); } @@ -529,7 +591,7 @@ private MethodSpec serviceSpecificHttpConfigMethod(String serviceDefaultFqcn, bo } private CodeBlock serviceSpecificHttpConfigMethodBody(String serviceDefaultFqcn, boolean supportsH2) { - CodeBlock.Builder builder = CodeBlock.builder(); + CodeBlock.Builder builder = CodeBlock.builder(); if (serviceDefaultFqcn != null) { builder.addStatement("$T result = $T.defaultHttpConfig()", @@ -560,6 +622,7 @@ private Optional awsAuthSignerDefinitionMethodBody() { case S3V4: return Optional.of(s3SignerDefinitionMethodBody()); case BEARER: + case NONE: return Optional.empty(); default: throw new UnsupportedOperationException("Unsupported signer type: " + authType); @@ -584,6 +647,36 @@ private MethodSpec defaultEndpointProviderMethod() { .build(); } + private MethodSpec authSchemeProviderMethod() { + return MethodSpec.methodBuilder("authSchemeProvider") + .addModifiers(Modifier.PUBLIC) + .returns(TypeVariableName.get("B")) + .addParameter(authSchemeSpecUtils.providerInterfaceName(), "authSchemeProvider") + .addStatement("clientConfiguration.option($T.AUTH_SCHEME_PROVIDER, authSchemeProvider)", + SdkClientOption.class) + .addStatement("return thisBuilder()") + .build(); + } + + private MethodSpec defaultAuthSchemeProviderMethod() { + return MethodSpec.methodBuilder("defaultAuthSchemeProvider") + .addModifiers(PRIVATE) + .returns(authSchemeSpecUtils.providerInterfaceName()) + .addStatement("return $T.defaultProvider()", authSchemeSpecUtils.providerInterfaceName()) + .build(); + } + + private MethodSpec putAuthSchemeMethod() { + return MethodSpec.methodBuilder("putAuthScheme") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .returns(TypeVariableName.get("B")) + .addParameter(GENERIC_AUTH_SCHEME_TYPE, "authScheme") + .addStatement("additionalAuthSchemes.put(authScheme.schemeId(), authScheme)") + .addStatement("return thisBuilder()") + .build(); + } + private MethodSpec clientContextParamSetter(String name, ClientContextParam param) { String setterName = endpointRulesSpecUtils.paramMethodName(name); String keyName = model.getNamingStrategy().getEnumValueName(name); @@ -601,7 +694,8 @@ private MethodSpec clientContextParamSetter(String name, ClientContextParam para private MethodSpec defaultBearerTokenProviderMethod() { return MethodSpec.methodBuilder("defaultTokenProvider") - .returns(SdkTokenProvider.class) + .returns(ParameterizedTypeName.get(ClassName.get(IdentityProvider.class), + WildcardTypeName.subtypeOf(TokenIdentity.class))) .addModifiers(PRIVATE) .addStatement("return $T.create()", DefaultAwsTokenProvider.class) .build(); @@ -615,6 +709,67 @@ private MethodSpec defaultTokenAuthSignerMethod() { .build(); } + private MethodSpec authSchemesMethod() { + TypeName returns = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(String.class), + ParameterizedTypeName.get(ClassName.get(AuthScheme.class), + WildcardTypeName.subtypeOf(Object.class))); + + MethodSpec.Builder builder = MethodSpec.methodBuilder("authSchemes") + .addModifiers(PRIVATE) + .returns(returns); + + Set> concreteAuthSchemeClasses = authSchemeSpecUtils.allServiceConcreteAuthSchemeClasses(); + builder.addStatement("$T schemes = new $T<>($L + this.additionalAuthSchemes.size())", + returns, HashMap.class, concreteAuthSchemeClasses.size()); + for (Class concreteAuthScheme : concreteAuthSchemeClasses) { + String instanceVariable = CodegenNamingUtils.lowercaseFirstChar(concreteAuthScheme.getSimpleName()); + builder.addStatement("$1T $2L = $1T.create()", concreteAuthScheme, instanceVariable); + builder.addStatement("schemes.put($1N.schemeId(), $1N)", instanceVariable); + } + builder.addStatement("schemes.putAll(this.additionalAuthSchemes)"); + builder.addStatement("return $T.unmodifiableMap(schemes)", Collections.class); + return builder.build(); + } + + private MethodSpec invokePluginsMethod() { + MethodSpec.Builder builder = MethodSpec.methodBuilder("invokePlugins") + .addAnnotation(Override.class) + .addModifiers(PROTECTED) + .addParameter(SdkClientConfiguration.class, "config") + .returns(SdkClientConfiguration.class); + builder.addStatement("$T plugins = plugins()", + ParameterizedTypeName.get(List.class, SdkPlugin.class)) + .beginControlFlow("if (plugins.isEmpty())") + .addStatement("return config") + .endControlFlow(); + builder.addStatement("$1T.BuilderInternal serviceConfigBuilder = $1T.builder(config.toBuilder())", + configurationUtils.serviceClientConfigurationBuilderClassName()); + builder.addStatement("serviceConfigBuilder.overrideConfiguration(overrideConfiguration())"); + builder.beginControlFlow("for ($T plugin : plugins)", SdkPlugin.class) + .addStatement("plugin.configureClient(serviceConfigBuilder)") + .endControlFlow(); + builder.addStatement("overrideConfiguration(serviceConfigBuilder.overrideConfiguration())"); + builder.addStatement("return serviceConfigBuilder.buildSdkClientConfiguration()"); + return builder.build(); + } + + private MethodSpec setOverridesMethod() { + return MethodSpec.methodBuilder("setOverrides") + .addModifiers(PROTECTED) + .addAnnotation(Override.class) + .addParameter(SdkClientConfiguration.class, "configuration") + .returns(SdkClientConfiguration.class) + .addStatement("$T overrideConfiguration = overrideConfiguration()", + ClientOverrideConfiguration.class) + .beginControlFlow("if (overrideConfiguration == null)") + .addStatement("return configuration") + .endControlFlow() + .addStatement("return $T.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder())" + + ".build()", + sdkClientConfigurationUtilClassName) + .build(); + } + @Override public ClassName className() { return builderClassName; @@ -637,7 +792,7 @@ private MethodSpec validateClientOptionsMethod() { .addParameter(SdkClientConfiguration.class, "c") .returns(void.class); - if (AuthUtils.usesAwsAuth(model)) { + if (AuthUtils.usesAwsAuth(model) && !authSchemeSpecUtils.useSraAuth()) { builder.addStatement("$T.notNull(c.option($T.SIGNER), $S)", Validate.class, SdkAdvancedClientOption.class, @@ -645,16 +800,17 @@ private MethodSpec validateClientOptionsMethod() { } if (AuthUtils.usesBearerAuth(model)) { - builder.addStatement("$T.notNull(c.option($T.TOKEN_SIGNER), $S)", - Validate.class, - SdkAdvancedClientOption.class, - "The 'overrideConfiguration.advancedOption[TOKEN_SIGNER]' " - + "must be configured in the client builder."); - builder.addStatement("$T.notNull(c.option($T.TOKEN_PROVIDER), $S)", + if (!authSchemeSpecUtils.useSraAuth()) { + builder.addStatement("$T.notNull(c.option($T.TOKEN_SIGNER), $S)", + Validate.class, + SdkAdvancedClientOption.class, + "The 'overrideConfiguration.advancedOption[TOKEN_SIGNER]' " + + "must be configured in the client builder."); + } + builder.addStatement("$T.notNull(c.option($T.TOKEN_IDENTITY_PROVIDER), $S)", Validate.class, AwsClientOption.class, - "The 'overrideConfiguration.advancedOption[TOKEN_PROVIDER]' " - + "must be configured in the client builder."); + "The 'tokenProvider' must be configured in the client builder."); } return builder.build(); 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 0aac740152bc..44700c69f8a3 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 @@ -22,6 +22,7 @@ import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; +import com.squareup.javapoet.WildcardTypeName; import java.util.function.Consumer; import javax.lang.model.element.Modifier; import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; @@ -33,22 +34,30 @@ import software.amazon.awssdk.codegen.model.service.ClientContextParam; import software.amazon.awssdk.codegen.poet.ClassSpec; import software.amazon.awssdk.codegen.poet.PoetUtils; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils; import software.amazon.awssdk.codegen.poet.rules.EndpointRulesSpecUtils; import software.amazon.awssdk.codegen.utils.AuthUtils; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.utils.internal.CodegenNamingUtils; public class BaseClientBuilderInterface implements ClassSpec { + private static final ParameterizedTypeName TOKEN_IDENTITY_PROVIDER_TYPE_NAME = + ParameterizedTypeName.get(ClassName.get(IdentityProvider.class), WildcardTypeName.subtypeOf(TokenIdentity.class)); + private final IntermediateModel model; private final String basePackage; private final ClassName builderInterfaceName; private final EndpointRulesSpecUtils endpointRulesSpecUtils; + private final AuthSchemeSpecUtils authSchemeSpecUtils; public BaseClientBuilderInterface(IntermediateModel model) { this.model = model; this.basePackage = model.getMetadata().getFullClientPackageName(); this.builderInterfaceName = ClassName.get(basePackage, model.getMetadata().getBaseBuilderInterface()); this.endpointRulesSpecUtils = new EndpointRulesSpecUtils(model); + this.authSchemeSpecUtils = new AuthSchemeSpecUtils(model); } @Override @@ -73,6 +82,7 @@ public TypeSpec poetSpec() { } builder.addMethod(endpointProviderMethod()); + builder.addMethod(authSchemeProviderMethod()); if (hasClientContextParams()) { model.getClientContextParams().forEach((n, m) -> { @@ -88,6 +98,7 @@ public TypeSpec poetSpec() { if (generateTokenProviderMethod()) { builder.addMethod(tokenProviderMethod()); + builder.addMethod(tokenIdentityProviderMethod()); } return builder.build(); @@ -154,6 +165,19 @@ private MethodSpec endpointProviderMethod() { .build(); } + private MethodSpec authSchemeProviderMethod() { + return MethodSpec.methodBuilder("authSchemeProvider") + .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) + .addParameter(authSchemeSpecUtils.providerInterfaceName(), "authSchemeProvider") + .addJavadoc("Set the {@link $T} 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.", + authSchemeSpecUtils.providerInterfaceName()) + .returns(TypeVariableName.get("B")) + .addStatement("throw new $T()", UnsupportedOperationException.class) + .build(); + } + private MethodSpec clientContextParamSetter(String name, ClientContextParam param) { String setterName = Utils.unCapitalize(CodegenNamingUtils.pascalCase(name)); TypeName type = endpointRulesSpecUtils.toJavaType(param.getType()); @@ -174,7 +198,7 @@ private boolean generateTokenProviderMethod() { private MethodSpec tokenProviderMethod() { return MethodSpec.methodBuilder("tokenProvider") - .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) .returns(TypeVariableName.get("B")) .addParameter(SdkTokenProvider.class, "tokenProvider") .addJavadoc("Set the token provider to use for bearer token authorization. This is optional, if none " @@ -188,6 +212,27 @@ private MethodSpec tokenProviderMethod() { DefaultAwsTokenProvider.class, SdkAdvancedClientOption.class, BearerTokenSigner.class) + .addStatement("return tokenProvider(($T) tokenProvider)", TOKEN_IDENTITY_PROVIDER_TYPE_NAME) + .build(); + } + + private MethodSpec tokenIdentityProviderMethod() { + return MethodSpec.methodBuilder("tokenProvider") + .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) + .returns(TypeVariableName.get("B")) + .addParameter(TOKEN_IDENTITY_PROVIDER_TYPE_NAME, "tokenProvider") + .addJavadoc("Set the token provider to use for bearer token authorization. This is optional, if none " + + "is provided, the SDK will use {@link $T}.\n" + + "

\n" + + "If the service, or any of its operations require Bearer Token Authorization, then the " + + "SDK will default to this token provider to retrieve the token to use for authorization.\n" + + "

\n" + + "This provider works in conjunction with the {@code $T.TOKEN_SIGNER} set on the client. " + + "By default it is {@link $T}.", + DefaultAwsTokenProvider.class, + SdkAdvancedClientOption.class, + BearerTokenSigner.class) + .addStatement("throw new $T()", UnsupportedOperationException.class) .build(); } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderClass.java index bb5fd4f2208c..1582b90f6c1b 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderClass.java @@ -19,10 +19,10 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.WildcardTypeName; import java.net.URI; import javax.lang.model.element.Modifier; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; import software.amazon.awssdk.codegen.poet.ClassSpec; @@ -32,7 +32,8 @@ import software.amazon.awssdk.codegen.utils.AuthUtils; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; -import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; public class SyncClientBuilderClass implements ClassSpec { private final IntermediateModel model; @@ -59,12 +60,12 @@ public SyncClientBuilderClass(IntermediateModel model) { @Override public TypeSpec poetSpec() { TypeSpec.Builder builder = - PoetUtils.createClassBuilder(builderClassName) - .addAnnotation(SdkInternalApi.class) - .addModifiers(Modifier.FINAL) - .superclass(ParameterizedTypeName.get(builderBaseClassName, builderInterfaceName, clientInterfaceName)) - .addSuperinterface(builderInterfaceName) - .addJavadoc("Internal implementation of {@link $T}.", builderInterfaceName); + PoetUtils.createClassBuilder(builderClassName) + .addAnnotation(SdkInternalApi.class) + .addModifiers(Modifier.FINAL) + .superclass(ParameterizedTypeName.get(builderBaseClassName, builderInterfaceName, clientInterfaceName)) + .addSuperinterface(builderInterfaceName) + .addJavadoc("Internal implementation of {@link $T}.", builderInterfaceName); if (model.getEndpointOperation().isPresent()) { builder.addMethod(endpointDiscoveryEnabled()); @@ -81,7 +82,7 @@ public TypeSpec poetSpec() { } builder.addMethod(buildClientMethod()); - builder.addMethod(initializeServiceClientConfigMethod()); + builder.addMethod(initializeServiceClientConfigMethod(serviceConfigClassName)); return builder.build(); } @@ -124,15 +125,15 @@ private MethodSpec endpointProviderMethod() { private MethodSpec buildClientMethod() { MethodSpec.Builder builder = MethodSpec.methodBuilder("buildClient") - .addAnnotation(Override.class) - .addModifiers(Modifier.PROTECTED, Modifier.FINAL) - .returns(clientInterfaceName) - .addStatement("$T clientConfiguration = super.syncClientConfiguration()", - SdkClientConfiguration.class) - .addStatement("this.validateClientOptions(clientConfiguration)") - .addStatement("$T serviceClientConfiguration = initializeServiceClientConfig" - + "(clientConfiguration)", - serviceConfigClassName); + .addAnnotation(Override.class) + .addModifiers(Modifier.PROTECTED, Modifier.FINAL) + .returns(clientInterfaceName) + .addStatement("$T clientConfiguration = super.syncClientConfiguration()", + SdkClientConfiguration.class) + .addStatement("this.validateClientOptions(clientConfiguration)") + .addStatement("$T serviceClientConfiguration = initializeServiceClientConfig" + + "(clientConfiguration)", + serviceConfigClassName); builder.addStatement("$1T client = new $2T(serviceClientConfiguration, clientConfiguration)", clientInterfaceName, clientClassName); @@ -146,36 +147,35 @@ private MethodSpec buildClientMethod() { } private MethodSpec tokenProviderMethodImpl() { + ParameterizedTypeName tokenProviderTypeName = ParameterizedTypeName.get(ClassName.get(IdentityProvider.class), + WildcardTypeName.subtypeOf(TokenIdentity.class)); return MethodSpec.methodBuilder("tokenProvider").addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) - .addParameter(SdkTokenProvider.class, "tokenProvider") + .addParameter(tokenProviderTypeName, "tokenProvider") .returns(builderClassName) - .addStatement("clientConfiguration.option($T.TOKEN_PROVIDER, tokenProvider)", + .addStatement("clientConfiguration.option($T.TOKEN_IDENTITY_PROVIDER, tokenProvider)", AwsClientOption.class) .addStatement("return this") .build(); } - private MethodSpec initializeServiceClientConfigMethod() { + // TODO(sra-plugins) Move this method to a commons class or move it to the base class + public static MethodSpec initializeServiceClientConfigMethod(ClassName serviceConfigClassName) { return MethodSpec.methodBuilder("initializeServiceClientConfig").addModifiers(Modifier.PRIVATE) .addParameter(SdkClientConfiguration.class, "clientConfig") .returns(serviceConfigClassName) .addStatement("$T endpointOverride = null", URI.class) - .addStatement("$T endpointProvider = clientConfig.option($T.ENDPOINT_PROVIDER)", - EndpointProvider.class, - SdkClientOption.class) - .addCode("if (clientConfig.option($T.ENDPOINT_OVERRIDDEN) != null" - + "&& $T.TRUE.equals(clientConfig.option($T.ENDPOINT_OVERRIDDEN))) {" - + "endpointOverride = clientConfig.option($T.ENDPOINT);" - + "}", - SdkClientOption.class, Boolean.class, SdkClientOption.class, SdkClientOption.class) + .beginControlFlow("if ($T.TRUE.equals(clientConfig.option($T.ENDPOINT_OVERRIDDEN)))", Boolean.class, + SdkClientOption.class) + .addStatement("endpointOverride = clientConfig.option($T.ENDPOINT)", SdkClientOption.class) + .endControlFlow() .addStatement("return $T.builder()" + ".overrideConfiguration(overrideConfiguration())" + ".region(clientConfig.option($T.AWS_REGION))" + ".endpointOverride(endpointOverride)" - + ".endpointProvider(endpointProvider)" + + ".endpointProvider(clientConfig.option($T.ENDPOINT_PROVIDER))" + ".build()", - serviceConfigClassName, AwsClientOption.class) + serviceConfigClassName, AwsClientOption.class, SdkClientOption.class) .build(); } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java index 9b66f9a28259..9c3a8b8127d2 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java @@ -34,6 +34,7 @@ import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.WildcardTypeName; import java.net.URI; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -65,10 +66,14 @@ import software.amazon.awssdk.codegen.poet.PoetExtension; import software.amazon.awssdk.codegen.poet.PoetUtils; import software.amazon.awssdk.codegen.poet.StaticImport; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils; import software.amazon.awssdk.codegen.poet.client.specs.ProtocolSpec; import software.amazon.awssdk.codegen.poet.eventstream.EventStreamUtils; import software.amazon.awssdk.codegen.poet.model.EventStreamSpecHelper; +import software.amazon.awssdk.codegen.poet.model.ServiceClientConfigurationUtils; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.core.async.AsyncResponseTransformerUtils; import software.amazon.awssdk.core.async.SdkPublisher; @@ -79,6 +84,7 @@ import software.amazon.awssdk.core.endpointdiscovery.EndpointDiscoveryRefreshCache; import software.amazon.awssdk.core.endpointdiscovery.EndpointDiscoveryRequest; import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.metrics.NoOpMetricCollector; @@ -93,6 +99,8 @@ public final class AsyncClientClass extends AsyncClientInterface { private final ClassName className; private final ProtocolSpec protocolSpec; private final ClassName serviceClientConfigurationClassName; + private final ServiceClientConfigurationUtils configurationUtils; + private final boolean useSraAuth; public AsyncClientClass(GeneratorTaskParams dependencies) { super(dependencies.getModel()); @@ -101,6 +109,8 @@ public AsyncClientClass(GeneratorTaskParams dependencies) { this.className = poetExtensions.getClientClass(model.getMetadata().getAsyncClient()); this.protocolSpec = getProtocolSpecs(poetExtensions, model); this.serviceClientConfigurationClassName = new PoetExtension(model).getServiceConfigClass(); + this.useSraAuth = new AuthSchemeSpecUtils(model).useSraAuth(); + this.configurationUtils = new ServiceClientConfigurationUtils(model); } @Override @@ -154,11 +164,13 @@ protected void addAdditionalMethods(TypeSpec.Builder type) { .addMethod(protocolSpec.initProtocolFactory(model)) .addMethod(resolveMetricPublishersMethod()); - if (model.containsRequestSigners() || model.containsRequestEventStreams() || hasStreamingV4AuthOperations()) { - type.addMethod(applySignerOverrideMethod(poetExtensions, model)); - type.addMethod(isSignerOverriddenOnClientMethod()); + if (!useSraAuth) { + if (model.containsRequestSigners() || model.containsRequestEventStreams() || hasStreamingV4AuthOperations()) { + type.addMethod(applySignerOverrideMethod(poetExtensions, model)); + type.addMethod(isSignerOverriddenOnClientMethod()); + } } - + type.addMethod(updateSdkClientConfigurationMethod(configurationUtils.serviceClientConfigurationBuilderClassName())); protocolSpec.createErrorResponseHandler().ifPresent(type::addMethod); } @@ -271,6 +283,31 @@ protected MethodSpec serviceClientConfigMethod() { .build(); } + protected static MethodSpec updateSdkClientConfigurationMethod( + TypeName serviceClientConfigurationBuilderClassName) { + MethodSpec.Builder builder = MethodSpec.methodBuilder("updateSdkClientConfiguration") + .addModifiers(PRIVATE) + .addParameter(SdkRequest.class, "request") + .addParameter(SdkClientConfiguration.class, "clientConfiguration") + .returns(SdkClientConfiguration.class); + + + builder.addStatement("$T plugins = request.overrideConfiguration()\n" + + ".map(c -> c.plugins()).orElse(Collections.emptyList())", + ParameterizedTypeName.get(List.class, SdkPlugin.class)) + .beginControlFlow("if (plugins.isEmpty())") + .addStatement("return clientConfiguration") + .endControlFlow(); + builder.addStatement("$1T.BuilderInternal serviceConfigBuilder = $1T.builder(clientConfiguration.toBuilder())", + serviceClientConfigurationBuilderClassName); + builder.addStatement("serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration())"); + builder.beginControlFlow("for ($T plugin : plugins)", SdkPlugin.class) + .addStatement("plugin.configureClient(serviceConfigBuilder)") + .endControlFlow(); + builder.addStatement("return serviceConfigBuilder.buildSdkClientConfiguration()"); + return builder.build(); + } + @Override protected void addCloseMethod(TypeSpec.Builder type) { MethodSpec method = MethodSpec.methodBuilder("close") @@ -287,7 +324,8 @@ protected MethodSpec.Builder operationBody(MethodSpec.Builder builder, Operation builder.addModifiers(PUBLIC) .addAnnotation(Override.class); - + builder.addStatement("$T clientConfiguration = updateSdkClientConfiguration($L, this.clientConfiguration)", + SdkClientConfiguration.class, opModel.getInput().getVariableName()); builder.addStatement("$T<$T> metricPublishers = " + "resolveMetricPublishers(clientConfiguration, $N.overrideConfiguration().orElse(null))", List.class, @@ -325,11 +363,13 @@ protected MethodSpec.Builder operationBody(MethodSpec.Builder builder, Operation "endOfStreamFuture", "pair"); } - - if (shouldUseAsyncWithBodySigner(opModel)) { - builder.addCode(applyAsyncWithBodyV4SignerOverride(opModel)); - } else { - builder.addCode(ClientClassUtils.callApplySignerOverrideMethod(opModel)); + + if (!useSraAuth) { + if (shouldUseAsyncWithBodySigner(opModel)) { + builder.addCode(applyAsyncWithBodyV4SignerOverride(opModel)); + } else { + builder.addCode(ClientClassUtils.callApplySignerOverrideMethod(opModel)); + } } builder.addCode(protocolSpec.responseHandler(model, opModel)); @@ -366,22 +406,29 @@ protected MethodSpec.Builder operationBody(MethodSpec.Builder builder, Operation } } - builder.addStatement("$T cachedEndpoint = null", URI.class); + builder.addStatement("$T<$T> endpointFuture = $T.completedFuture(null)", + CompletableFuture.class, URI.class, CompletableFuture.class); builder.beginControlFlow("if (endpointDiscoveryEnabled)"); - builder.addCode("$T key = $N.overrideConfiguration()", String.class, opModel.getInput().getVariableName()) - .addCode(" .flatMap($T::credentialsProvider)", AwsRequestOverrideConfiguration.class) - .addCode(" .orElseGet(() -> clientConfiguration.option($T.CREDENTIALS_PROVIDER))", AwsClientOption.class) - .addCode(" .resolveCredentials().accessKeyId();"); - - builder.addCode("$1T endpointDiscoveryRequest = $1T.builder()", EndpointDiscoveryRequest.class) - .addCode(" .required($L)", opModel.getInputShape().getEndpointDiscovery().isRequired()) - .addCode(" .defaultEndpoint(clientConfiguration.option($T.ENDPOINT))", SdkClientOption.class) - .addCode(" .overrideConfiguration($N.overrideConfiguration().orElse(null))", + ParameterizedTypeName identityFutureTypeName = ParameterizedTypeName.get(ClassName.get(CompletableFuture.class), + WildcardTypeName.subtypeOf(AwsCredentialsIdentity.class)); + builder.addCode("$T identityFuture = $N.overrideConfiguration()", identityFutureTypeName, + opModel.getInput().getVariableName()) + .addCode(" .flatMap($T::credentialsIdentityProvider)", AwsRequestOverrideConfiguration.class) + .addCode(" .orElseGet(() -> clientConfiguration.option($T.CREDENTIALS_IDENTITY_PROVIDER))", + AwsClientOption.class) + .addCode(" .resolveIdentity();"); + + builder.addCode("endpointFuture = identityFuture.thenApply(credentials -> {") + .addCode(" $1T endpointDiscoveryRequest = $1T.builder()", EndpointDiscoveryRequest.class) + .addCode(" .required($L)", opModel.getInputShape().getEndpointDiscovery().isRequired()) + .addCode(" .defaultEndpoint(clientConfiguration.option($T.ENDPOINT))", SdkClientOption.class) + .addCode(" .overrideConfiguration($N.overrideConfiguration().orElse(null))", opModel.getInput().getVariableName()) - .addCode(" .build();"); + .addCode(" .build();") + .addCode(" return endpointDiscoveryCache.get(credentials.accessKeyId(), endpointDiscoveryRequest);") + .addCode("});"); - builder.addStatement("cachedEndpoint = endpointDiscoveryCache.get(key, endpointDiscoveryRequest)"); builder.endControlFlow(); } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientClass.java index 310170284bfe..2ec170acfd15 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientClass.java @@ -20,6 +20,7 @@ import static javax.lang.model.element.Modifier.PROTECTED; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; +import static software.amazon.awssdk.codegen.poet.client.AsyncClientClass.updateSdkClientConfigurationMethod; import static software.amazon.awssdk.codegen.poet.client.ClientClassUtils.addS3ArnableFieldCode; import static software.amazon.awssdk.codegen.poet.client.ClientClassUtils.applySignerOverrideMethod; @@ -28,10 +29,12 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.WildcardTypeName; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.Stream; import software.amazon.awssdk.annotations.SdkInternalApi; @@ -44,11 +47,13 @@ import software.amazon.awssdk.codegen.model.intermediate.Protocol; import software.amazon.awssdk.codegen.poet.PoetExtension; import software.amazon.awssdk.codegen.poet.PoetUtils; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils; import software.amazon.awssdk.codegen.poet.client.specs.Ec2ProtocolSpec; import software.amazon.awssdk.codegen.poet.client.specs.JsonProtocolSpec; import software.amazon.awssdk.codegen.poet.client.specs.ProtocolSpec; import software.amazon.awssdk.codegen.poet.client.specs.QueryProtocolSpec; import software.amazon.awssdk.codegen.poet.client.specs.XmlProtocolSpec; +import software.amazon.awssdk.codegen.poet.model.ServiceClientConfigurationUtils; import software.amazon.awssdk.core.RequestOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; @@ -56,9 +61,11 @@ import software.amazon.awssdk.core.endpointdiscovery.EndpointDiscoveryRefreshCache; import software.amazon.awssdk.core.endpointdiscovery.EndpointDiscoveryRequest; import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.metrics.NoOpMetricCollector; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Logger; public class SyncClientClass extends SyncClientInterface { @@ -68,6 +75,8 @@ public class SyncClientClass extends SyncClientInterface { private final ClassName className; private final ProtocolSpec protocolSpec; private final ClassName serviceClientConfigurationClassName; + private final ServiceClientConfigurationUtils configurationUtils; + private final boolean useSraAuth; public SyncClientClass(GeneratorTaskParams taskParams) { super(taskParams.getModel()); @@ -76,6 +85,8 @@ public SyncClientClass(GeneratorTaskParams taskParams) { this.className = poetExtensions.getClientClass(model.getMetadata().getSyncClient()); this.protocolSpec = getProtocolSpecs(poetExtensions, model); this.serviceClientConfigurationClassName = new PoetExtension(model).getServiceConfigClass(); + this.configurationUtils = new ServiceClientConfigurationUtils(model); + this.useSraAuth = new AuthSchemeSpecUtils(model).useSraAuth(); } @Override @@ -111,9 +122,10 @@ protected void addFields(TypeSpec.Builder type) { @Override protected void addAdditionalMethods(TypeSpec.Builder type) { - - if (model.containsRequestSigners()) { - type.addMethod(applySignerOverrideMethod(poetExtensions, model)); + if (!useSraAuth) { + if (model.containsRequestSigners()) { + type.addMethod(applySignerOverrideMethod(poetExtensions, model)); + } } model.getEndpointOperation().ifPresent( @@ -125,7 +137,7 @@ protected void addAdditionalMethods(TypeSpec.Builder type) { .addMethod(resolveMetricPublishersMethod()); protocolSpec.createErrorResponseHandler().ifPresent(type::addMethod); - + type.addMethod(updateSdkClientConfigurationMethod(configurationUtils.serviceClientConfigurationBuilderClassName())); type.addMethod(protocolSpec.initProtocolFactory(model)); } @@ -168,6 +180,7 @@ private MethodSpec constructor() { .addStatement("this.clientHandler = new $T(clientConfiguration)", protocolSpec.getClientHandlerClass()) .addStatement("this.clientConfiguration = clientConfiguration") .addStatement("this.serviceClientConfiguration = serviceClientConfiguration"); + FieldSpec protocolFactoryField = protocolSpec.protocolFactory(model); if (model.getMetadata().isJsonProtocol()) { builder.addStatement("this.$N = init($T.builder()).build()", protocolFactoryField.name, @@ -216,9 +229,11 @@ private Stream operations(OperationModel opModel) { private MethodSpec traditionalMethod(OperationModel opModel) { MethodSpec.Builder method = SyncClientInterface.operationMethodSignature(model, opModel) - .addAnnotation(Override.class) - .addCode(ClientClassUtils.callApplySignerOverrideMethod(opModel)) - .addCode(protocolSpec.responseHandler(model, opModel)); + .addAnnotation(Override.class); + if (!useSraAuth) { + method.addCode(ClientClassUtils.callApplySignerOverrideMethod(opModel)); + } + method.addCode(protocolSpec.responseHandler(model, opModel)); protocolSpec.errorResponseHandler(opModel).ifPresent(method::addCode); @@ -255,10 +270,18 @@ private MethodSpec traditionalMethod(OperationModel opModel) { method.addStatement("$T cachedEndpoint = null", URI.class); method.beginControlFlow("if (endpointDiscoveryEnabled)"); - method.addCode("$T key = $N.overrideConfiguration()", String.class, opModel.getInput().getVariableName()) - .addCode(" .flatMap($T::credentialsProvider)", AwsRequestOverrideConfiguration.class) - .addCode(" .orElseGet(() -> clientConfiguration.option($T.CREDENTIALS_PROVIDER))", AwsClientOption.class) - .addCode(" .resolveCredentials().accessKeyId();"); + ParameterizedTypeName identityFutureTypeName = + ParameterizedTypeName.get(ClassName.get(CompletableFuture.class), + WildcardTypeName.subtypeOf(AwsCredentialsIdentity.class)); + method.addCode("$T identityFuture = $N.overrideConfiguration()", + identityFutureTypeName, + opModel.getInput().getVariableName()) + .addCode(" .flatMap($T::credentialsIdentityProvider)", AwsRequestOverrideConfiguration.class) + .addCode(" .orElseGet(() -> clientConfiguration.option($T.CREDENTIALS_IDENTITY_PROVIDER))", + AwsClientOption.class) + .addCode(" .resolveIdentity();"); + + method.addCode("$T key = $T.joinLikeSync(identityFuture).accessKeyId();", String.class, CompletableFutureUtils.class); method.addCode("$1T endpointDiscoveryRequest = $1T.builder()", EndpointDiscoveryRequest.class) .addCode(" .required($L)", opModel.getInputShape().getEndpointDiscovery().isRequired()) @@ -271,6 +294,8 @@ private MethodSpec traditionalMethod(OperationModel opModel) { method.endControlFlow(); } + method.addStatement("$T clientConfiguration = updateSdkClientConfiguration($L, this.clientConfiguration)", + SdkClientConfiguration.class, opModel.getInput().getVariableName()); method.addStatement("$T<$T> metricPublishers = " + "resolveMetricPublishers(clientConfiguration, $N.overrideConfiguration().orElse(null))", List.class, diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientInterface.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientInterface.java index 5037d5eda1f2..a9f9f06d6049 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientInterface.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientInterface.java @@ -142,6 +142,7 @@ protected void addAdditionalMethods(TypeSpec.Builder type) { .addMethod(serviceMetadata()); PoetUtils.addJavadoc(type::addJavadoc, getJavadoc()); + } @Override diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/JsonProtocolSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/JsonProtocolSpec.java index ce46ecf7c824..5d86cfd13078 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/JsonProtocolSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/JsonProtocolSpec.java @@ -39,6 +39,7 @@ import software.amazon.awssdk.codegen.model.intermediate.Protocol; import software.amazon.awssdk.codegen.model.intermediate.ShapeModel; import software.amazon.awssdk.codegen.poet.PoetExtension; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils; import software.amazon.awssdk.codegen.poet.client.traits.HttpChecksumRequiredTrait; import software.amazon.awssdk.codegen.poet.client.traits.HttpChecksumTrait; import software.amazon.awssdk.codegen.poet.client.traits.NoneAuthTypeRequestTrait; @@ -64,10 +65,12 @@ public class JsonProtocolSpec implements ProtocolSpec { private final PoetExtension poetExtensions; private final IntermediateModel model; + private final boolean useSraAuth; public JsonProtocolSpec(PoetExtension poetExtensions, IntermediateModel model) { this.poetExtensions = poetExtensions; this.model = model; + this.useSraAuth = new AuthSchemeSpecUtils(model).useSraAuth(); } @Override @@ -184,12 +187,17 @@ public CodeBlock executionHandler(OperationModel opModel) { .add(hostPrefixExpression(opModel)) .add(discoveredEndpoint(opModel)) .add(credentialType(opModel, model)) + .add(".withRequestConfiguration(clientConfiguration)") .add(".withInput($L)\n", opModel.getInput().getVariableName()) .add(".withMetricCollector(apiCallMetricCollector)") .add(HttpChecksumRequiredTrait.putHttpChecksumAttribute(opModel)) - .add(HttpChecksumTrait.create(opModel)) - .add(NoneAuthTypeRequestTrait.create(opModel)) - .add(RequestCompressionTrait.create(opModel, model)); + .add(HttpChecksumTrait.create(opModel)); + + if (!useSraAuth) { + codeBlock.add(NoneAuthTypeRequestTrait.create(opModel)); + } + + codeBlock.add(RequestCompressionTrait.create(opModel, model)); if (opModel.hasStreamingInput()) { codeBlock.add(".withRequestBody(requestBody)") @@ -242,8 +250,9 @@ public CodeBlock asyncExecutionHandler(IntermediateModel intermediateModel, Oper : pojoResponseType; TypeName executeFutureValueType = executeFutureValueType(opModel, poetExtensions); - builder.add("\n\n$T<$T> executeFuture = clientHandler.execute(new $T<$T, $T>()\n", - CompletableFuture.class, executeFutureValueType, ClientExecutionParams.class, requestType, responseType) + builder.add("\n\n$T<$T> executeFuture = ", CompletableFuture.class, executeFutureValueType) + .add(opModel.getEndpointDiscovery() != null ? "endpointFuture.thenCompose(cachedEndpoint -> " : "") + .add("clientHandler.execute(new $T<$T, $T>()\n", ClientExecutionParams.class, requestType, responseType) .add(".withOperationName(\"$N\")\n", opModel.getOperationName()) .add(".withMarshaller($L)\n", asyncMarshaller(model, opModel, marshaller, protocolFactory)) .add(asyncRequestBody(opModel)) @@ -251,17 +260,23 @@ public CodeBlock asyncExecutionHandler(IntermediateModel intermediateModel, Oper .add(hasInitialRequestEvent(opModel, isRestJson)) .add(".withResponseHandler($L)\n", responseHandlerName(opModel, isRestJson)) .add(".withErrorResponseHandler(errorResponseHandler)\n") + .add(".withRequestConfiguration(clientConfiguration)") .add(".withMetricCollector(apiCallMetricCollector)\n") .add(hostPrefixExpression(opModel)) .add(discoveredEndpoint(opModel)) .add(credentialType(opModel, model)) .add(asyncRequestBody) .add(HttpChecksumRequiredTrait.putHttpChecksumAttribute(opModel)) - .add(HttpChecksumTrait.create(opModel)) - .add(NoneAuthTypeRequestTrait.create(opModel)) - .add(RequestCompressionTrait.create(opModel, model)) - .add(".withInput($L)$L);", - opModel.getInput().getVariableName(), asyncResponseTransformerVariable(isStreaming, isRestJson, opModel)); + .add(HttpChecksumTrait.create(opModel)); + + if (!useSraAuth) { + builder.add(NoneAuthTypeRequestTrait.create(opModel)); + } + + builder.add(RequestCompressionTrait.create(opModel, model)) + .add(".withInput($L)$L)", + opModel.getInput().getVariableName(), asyncResponseTransformerVariable(isStreaming, isRestJson, opModel)) + .add(opModel.getEndpointDiscovery() != null ? ");" : ";"); if (opModel.hasStreamingOutput()) { builder.addStatement("$T<$T, ReturnT> finalAsyncResponseTransformer = asyncResponseTransformer", diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/QueryProtocolSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/QueryProtocolSpec.java index daef19b9def3..f14efae69473 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/QueryProtocolSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/QueryProtocolSpec.java @@ -28,6 +28,7 @@ import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; import software.amazon.awssdk.codegen.model.intermediate.OperationModel; import software.amazon.awssdk.codegen.poet.PoetExtension; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils; import software.amazon.awssdk.codegen.poet.client.traits.HttpChecksumRequiredTrait; import software.amazon.awssdk.codegen.poet.client.traits.HttpChecksumTrait; import software.amazon.awssdk.codegen.poet.client.traits.NoneAuthTypeRequestTrait; @@ -42,10 +43,12 @@ public class QueryProtocolSpec implements ProtocolSpec { protected final PoetExtension poetExtensions; protected final IntermediateModel intermediateModel; + protected final boolean useSraAuth; public QueryProtocolSpec(IntermediateModel intermediateModel, PoetExtension poetExtensions) { this.intermediateModel = intermediateModel; this.poetExtensions = poetExtensions; + this.useSraAuth = new AuthSchemeSpecUtils(intermediateModel).useSraAuth(); } @Override @@ -113,13 +116,17 @@ public CodeBlock executionHandler(OperationModel opModel) { .add(hostPrefixExpression(opModel)) .add(discoveredEndpoint(opModel)) .add(credentialType(opModel, intermediateModel)) + .add(".withRequestConfiguration(clientConfiguration)") .add(".withInput($L)", opModel.getInput().getVariableName()) .add(".withMetricCollector(apiCallMetricCollector)") .add(HttpChecksumRequiredTrait.putHttpChecksumAttribute(opModel)) - .add(HttpChecksumTrait.create(opModel)) - .add(NoneAuthTypeRequestTrait.create(opModel)) - .add(RequestCompressionTrait.create(opModel, intermediateModel)); + .add(HttpChecksumTrait.create(opModel)); + if (!useSraAuth) { + codeBlock.add(NoneAuthTypeRequestTrait.create(opModel)); + } + + codeBlock.add(RequestCompressionTrait.create(opModel, intermediateModel)); if (opModel.hasStreamingInput()) { return codeBlock.add(".withRequestBody(requestBody)") @@ -150,12 +157,16 @@ public CodeBlock asyncExecutionHandler(IntermediateModel intermediateModel, Oper .add(".withResponseHandler(responseHandler)\n") .add(".withErrorResponseHandler(errorResponseHandler)\n") .add(credentialType(opModel, intermediateModel)) + .add(".withRequestConfiguration(clientConfiguration)") .add(".withMetricCollector(apiCallMetricCollector)\n") .add(HttpChecksumRequiredTrait.putHttpChecksumAttribute(opModel)) - .add(HttpChecksumTrait.create(opModel)) - .add(NoneAuthTypeRequestTrait.create(opModel)) - .add(RequestCompressionTrait.create(opModel, intermediateModel)); + .add(HttpChecksumTrait.create(opModel)); + + if (!useSraAuth) { + builder.add(NoneAuthTypeRequestTrait.create(opModel)); + } + builder.add(RequestCompressionTrait.create(opModel, intermediateModel)); builder.add(hostPrefixExpression(opModel) + asyncRequestBody + ".withInput($L)$L);", opModel.getInput().getVariableName(), diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/XmlProtocolSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/XmlProtocolSpec.java index 3f58b49edc7b..f7092ccdaf36 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/XmlProtocolSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/XmlProtocolSpec.java @@ -133,12 +133,16 @@ public CodeBlock executionHandler(OperationModel opModel) { hostPrefixExpression(opModel) + discoveredEndpoint(opModel)) .add(credentialType(opModel, model)) + .add(".withRequestConfiguration(clientConfiguration)") .add(".withInput($L)", opModel.getInput().getVariableName()) .add(HttpChecksumRequiredTrait.putHttpChecksumAttribute(opModel)) - .add(HttpChecksumTrait.create(opModel)) - .add(NoneAuthTypeRequestTrait.create(opModel)) - .add(RequestCompressionTrait.create(opModel, model)); + .add(HttpChecksumTrait.create(opModel)); + if (!useSraAuth) { + codeBlock.add(NoneAuthTypeRequestTrait.create(opModel)); + } + + codeBlock.add(RequestCompressionTrait.create(opModel, model)); s3ArnableFields(opModel, model).ifPresent(codeBlock::add); @@ -195,11 +199,11 @@ public CodeBlock asyncExecutionHandler(IntermediateModel intermediateModel, Oper if (opModel.hasEventStreamOutput()) { executionResponseTransformerName = "restAsyncResponseTransformer"; } - builder.add("\n\n$T<$T> executeFuture = clientHandler.execute(new $T<$T, $T>()\n", CompletableFuture.class, executeFutureValueType, ClientExecutionParams.class, requestType, pojoResponseType) .add(".withOperationName(\"$N\")\n", opModel.getOperationName()) + .add(".withRequestConfiguration(clientConfiguration)") .add(".withMarshaller($L)\n", asyncMarshaller(intermediateModel, opModel, marshaller, "protocolFactory")); if (opModel.hasEventStreamOutput()) { @@ -214,9 +218,13 @@ public CodeBlock asyncExecutionHandler(IntermediateModel intermediateModel, Oper .add(".withMetricCollector(apiCallMetricCollector)\n") .add(asyncRequestBody(opModel)) .add(HttpChecksumRequiredTrait.putHttpChecksumAttribute(opModel)) - .add(HttpChecksumTrait.create(opModel)) - .add(NoneAuthTypeRequestTrait.create(opModel)) - .add(RequestCompressionTrait.create(opModel, model)); + .add(HttpChecksumTrait.create(opModel)); + + if (!useSraAuth) { + builder.add(NoneAuthTypeRequestTrait.create(opModel)); + } + + builder.add(RequestCompressionTrait.create(opModel, model)); s3ArnableFields(opModel, model).ifPresent(builder::add); diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/SdkClientConfigurationUtilGeneratorTask.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/SdkClientConfigurationUtilGeneratorTask.java new file mode 100644 index 000000000000..46b9f0233212 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/SdkClientConfigurationUtilGeneratorTask.java @@ -0,0 +1,66 @@ +/* + * 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.model; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.codegen.emitters.GeneratorTask; +import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams; +import software.amazon.awssdk.codegen.emitters.SimpleGeneratorTask; +import software.amazon.awssdk.codegen.emitters.tasks.BaseGeneratorTasks; +import software.amazon.awssdk.utils.IoUtils; + +public final class SdkClientConfigurationUtilGeneratorTask extends BaseGeneratorTasks { + public static final String RUNTIME_CLASS_NAME = "SdkClientConfigurationUtil"; + + private final String internalPackage; + private final String internalClassDir; + private final String fileHeader; + private final String runtimeClassCode; + + public SdkClientConfigurationUtilGeneratorTask(GeneratorTaskParams generatorTaskParams) { + super(generatorTaskParams); + this.internalPackage = model.getMetadata().getFullClientInternalPackageName(); + this.internalClassDir = generatorTaskParams.getPathProvider().getClientInternalDirectory(); + this.fileHeader = generatorTaskParams.getModel().getFileHeader(); + this.runtimeClassCode = loadConfigUtilCode(); + } + + @Override + protected List createTasks() throws Exception { + String codeContents = + "package " + internalPackage + ";\n" + + "\n" + + runtimeClassCode; + + String fileName = RUNTIME_CLASS_NAME + ".java"; + return Collections.singletonList(new SimpleGeneratorTask(internalClassDir, fileName, fileHeader, + () -> codeContents)); + } + + private static String loadConfigUtilCode() { + try { + InputStream is = SdkClientConfigurationUtilGeneratorTask.class.getResourceAsStream( + "/software/amazon/awssdk/codegen/poet/model/SdkClientConfigurationUtil.resource.java"); + return IoUtils.toUtf8String(is); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationBuilderClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationBuilderClass.java new file mode 100644 index 000000000000..d73b58de43e0 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationBuilderClass.java @@ -0,0 +1,249 @@ +/* + * 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.model; + +import static javax.lang.model.element.Modifier.ABSTRACT; +import static javax.lang.model.element.Modifier.FINAL; +import static javax.lang.model.element.Modifier.PRIVATE; +import static javax.lang.model.element.Modifier.PUBLIC; +import static javax.lang.model.element.Modifier.STATIC; +import static software.amazon.awssdk.codegen.poet.model.ServiceClientConfigurationUtils.Field; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.poet.ClassSpec; +import software.amazon.awssdk.codegen.poet.PoetUtils; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.utils.Validate; + +public class ServiceClientConfigurationBuilderClass implements ClassSpec { + private final ServiceClientConfigurationUtils utils; + private final ClassName builderInterface; + + public ServiceClientConfigurationBuilderClass(IntermediateModel model) { + this.utils = new ServiceClientConfigurationUtils(model); + this.builderInterface = utils.serviceClientConfigurationClassName().nestedClass("Builder"); + } + + @Override + public ClassName className() { + return utils.serviceClientConfigurationBuilderClassName(); + } + + @Override + public TypeSpec poetSpec() { + TypeSpec.Builder builder = PoetUtils.createClassBuilder(className()) + .addModifiers(PUBLIC) + .addAnnotation(SdkInternalApi.class); + + return builder.addMethod(builderMethod()) + .addMethod(builderFromSdkClientConfiguration()) + .addType(builderInternalSpec()) + .addType(builderImplSpec()) + .build(); + } + + + private MethodSpec builderFromSdkClientConfiguration() { + return MethodSpec.methodBuilder("builder") + .addModifiers(PUBLIC, STATIC) + .addParameter(SdkClientConfiguration.Builder.class, "builder") + .returns(className().nestedClass("BuilderInternal")) + .addStatement("return new BuilderImpl(builder)") + .build(); + } + + + private MethodSpec builderMethod() { + return MethodSpec.methodBuilder("builder") + .addModifiers(PUBLIC, STATIC) + .returns(builderInterface) + .addStatement("return new BuilderImpl()") + .build(); + } + + private TypeSpec builderInternalSpec() { + TypeSpec.Builder builder = TypeSpec.interfaceBuilder("BuilderInternal") + .addModifiers(PUBLIC) + .addSuperinterface(builderInterface); + builder.addMethod(MethodSpec.methodBuilder("buildSdkClientConfiguration") + .addModifiers(PUBLIC, ABSTRACT) + .returns(SdkClientConfiguration.class) + .build()); + return builder.build(); + } + + private TypeSpec builderImplSpec() { + TypeSpec.Builder builder = TypeSpec.classBuilder("BuilderImpl") + .addModifiers(PUBLIC, STATIC) + .addSuperinterface(className().nestedClass("BuilderInternal")); + + builder.addField(SdkClientConfiguration.Builder.class, "internalBuilder", PRIVATE, FINAL); + + builder.addMethod(MethodSpec.constructorBuilder() + .addModifiers(PRIVATE) + .addStatement("this.internalBuilder = $T.builder()", SdkClientConfiguration.class) + .build()); + builder.addMethod(constructorFromSdkClientConfiguration()); + + for (Field field : utils.serviceClientConfigurationFields()) { + addLocalFieldForBuilderIfNeeded(field, builder); + builder.addMethod(setterForField(field)); + builder.addMethod(getterForBuilderField(field)); + } + + builder.addMethod(MethodSpec.methodBuilder("build") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(utils.serviceClientConfigurationClassName()) + .addStatement("return new $T(this)", utils.serviceClientConfigurationClassName()) + .build()); + + builder.addMethod(buildSdkClientConfigurationMethod()); + + return builder.build(); + } + + private MethodSpec buildSdkClientConfigurationMethod() { + MethodSpec.Builder builder = MethodSpec.methodBuilder("buildSdkClientConfiguration") + .addModifiers(PUBLIC) + .addAnnotation(Override.class) + .returns(SdkClientConfiguration.class); + + for (Field field : utils.serviceClientConfigurationFields()) { + if (field.optionClass() == null) { + CodeBlock block = field.copyToConfiguration(); + if (block != null) { + builder.addCode(block); + } + } + } + return builder + .addStatement("return internalBuilder.build()") + .build(); + } + + private MethodSpec constructorFromSdkClientConfiguration() { + MethodSpec.Builder builder = MethodSpec.constructorBuilder() + .addModifiers(PRIVATE) + .addParameter(SdkClientConfiguration.Builder.class, "internalBuilder") + .addStatement("this.internalBuilder = internalBuilder"); + + for (Field field : utils.serviceClientConfigurationFields()) { + if (field.optionClass() == null) { + CodeBlock block = field.constructFromConfiguration(); + if (block != null) { + builder.addCode(block); + } + } + } + return builder.build(); + } + + + private void addLocalFieldForBuilderIfNeeded(Field field, TypeSpec.Builder builder) { + if (field.optionClass() == null) { + builder.addField(field.type(), field.name(), PRIVATE); + } + } + + private MethodSpec setterForField(Field field) { + MethodSpec fieldBuilderSetter = field.builderSetterImpl(); + if (fieldBuilderSetter != null) { + return fieldBuilderSetter.toBuilder().returns(builderInterface).build(); + } + MethodSpec.Builder builder = baseSetterForField(field); + if (field.isLocalField()) { + builder.addAnnotation(Override.class); + } + if (field.optionClass() == null) { + return builder.addStatement("this.$1L = $1L", field.name()) + .addStatement("return this") + .build(); + + } + return builder.addStatement("internalBuilder.option($T.$L, $L)", + field.optionClass(), field.optionName(), field.name()) + .addStatement("return this") + .build(); + } + + private MethodSpec.Builder baseSetterForField(Field field) { + MethodSpec.Builder builder = MethodSpec.methodBuilder(field.name()) + .addModifiers(PUBLIC) + .addParameter(field.type(), field.name()) + .addJavadoc("Sets the value for " + field.doc()) + .returns(builderInterface); + if (!field.isLocalField()) { + builder.addAnnotation(Override.class); + } + return builder; + } + + private MethodSpec getterForBuilderField(Field field) { + return getterForField(field, "internalBuilder", false); + } + + + private MethodSpec getterForField(Field field, String fieldName, boolean forDataGetter) { + MethodSpec fieldBuilderGetter = field.builderGetterImpl(); + if (fieldBuilderGetter != null) { + return fieldBuilderGetter.toBuilder() + .returns(field.type()) + .build(); + } + MethodSpec.Builder builder = baseGetterForField(field); + if (!forDataGetter && field.isLocalField()) { + builder.addAnnotation(Override.class); + } + if (forDataGetter && field.isLocalField()) { + return builder.addStatement("return $L", field.name()) + .build(); + } + if (field.optionClass() == null) { + return builder.addStatement("return $L", field.name()) + .build(); + } + if (field.baseType() != null) { + return builder.addStatement("$T result = $L.option($T.$L)", + field.baseType(), fieldName, field.optionClass(), field.optionName()) + .beginControlFlow("if (result == null)") + .addStatement("return null") + .endControlFlow() + .addStatement("return $T.isInstanceOf($T.class, result, $S + $T.class.getSimpleName())", + Validate.class, field.type(), + "Expected an instance of ", field.type()) + .build(); + } + return builder.addStatement("return $L.option($T.$L)", fieldName, field.optionClass(), field.optionName()) + .build(); + } + + private MethodSpec.Builder baseGetterForField(Field field) { + MethodSpec.Builder builder = MethodSpec.methodBuilder(field.name()) + .addModifiers(PUBLIC) + .addJavadoc("Gets the value for " + field.doc()) + .returns(field.type()); + if (!field.isLocalField()) { + builder.addAnnotation(Override.class); + } + return builder; + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationClass.java index 8d872211ea85..14f63bc3b9ed 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationClass.java @@ -20,158 +20,172 @@ import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; +import static software.amazon.awssdk.codegen.poet.model.ServiceClientConfigurationUtils.Field; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; -import java.net.URI; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.awscore.AwsServiceClientConfiguration; import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; import software.amazon.awssdk.codegen.poet.ClassSpec; import software.amazon.awssdk.codegen.poet.PoetUtils; -import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; -import software.amazon.awssdk.endpoints.EndpointProvider; -import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.utils.Validate; public class ServiceClientConfigurationClass implements ClassSpec { private final ClassName defaultClientMetadataClassName; + private final ServiceClientConfigurationUtils utils; public ServiceClientConfigurationClass(IntermediateModel model) { String basePackage = model.getMetadata().getFullClientPackageName(); String serviceId = model.getMetadata().getServiceName(); this.defaultClientMetadataClassName = ClassName.get(basePackage, serviceId + "ServiceClientConfiguration"); + this.utils = new ServiceClientConfigurationUtils(model); } @Override - public TypeSpec poetSpec() { - return PoetUtils.createClassBuilder(defaultClientMetadataClassName) - .superclass(AwsServiceClientConfiguration.class) - .addJavadoc("Class to expose the service client settings to the user. Implementation of {@link $T}", - AwsServiceClientConfiguration.class) - .addMethod(constructor()) - .addMethod(builderMethod()) - .addModifiers(PUBLIC, FINAL) - .addAnnotation(SdkPublicApi.class) - .addType(builderInterfaceSpec()) - .addType(builderImplSpec()) - .build(); + public ClassName className() { + return utils.serviceClientConfigurationClassName(); } @Override - public ClassName className() { - return defaultClientMetadataClassName; + public TypeSpec poetSpec() { + TypeSpec.Builder builder = PoetUtils.createClassBuilder(defaultClientMetadataClassName) + .addModifiers(PUBLIC, FINAL) + .addAnnotation(SdkPublicApi.class) + .superclass(AwsServiceClientConfiguration.class) + .addJavadoc("Class to expose the service client settings to the user. " + + "Implementation of {@link $T}", + AwsServiceClientConfiguration.class); + + builder.addMethod(constructor()); + for (Field field : utils.serviceClientConfigurationFields()) { + addLocalFieldForDataIfNeeded(field, builder); + if (field.isLocalField()) { + builder.addMethod(getterForDataField(field)); + } + } + + return builder.addMethod(builderMethod()) + .addType(builderInterfaceSpec()) + .build(); } - public MethodSpec constructor() { - return MethodSpec.constructorBuilder() - .addModifiers(PRIVATE) - .addParameter(className().nestedClass("Builder"), "builder") - .addStatement("super(builder)") - .build(); + private MethodSpec constructor() { + MethodSpec.Builder builder = MethodSpec.constructorBuilder() + .addModifiers(PUBLIC) + .addParameter(className().nestedClass("Builder"), "builder"); + builder.addStatement("super(builder)"); + for (Field field : utils.serviceClientConfigurationFields()) { + if (field.isLocalField()) { + builder.addStatement("this.$L = builder.$L()", field.name(), field.name()); + } + } + return builder.build(); } - public MethodSpec builderMethod() { + private MethodSpec builderMethod() { return MethodSpec.methodBuilder("builder") .addModifiers(PUBLIC, STATIC) - .addStatement("return new BuilderImpl()") + .addStatement("return $T.builder()", + utils.serviceClientConfigurationBuilderClassName()) .returns(className().nestedClass("Builder")) - .addJavadoc("") .build(); } private TypeSpec builderInterfaceSpec() { - return TypeSpec.interfaceBuilder("Builder") - .addModifiers(PUBLIC) - .addSuperinterface(ClassName.get(AwsServiceClientConfiguration.class).nestedClass("Builder")) - .addJavadoc("A builder for creating a {@link $T}", className()) - .addMethod(MethodSpec.methodBuilder("build") - .addAnnotation(Override.class) - .addModifiers(PUBLIC, ABSTRACT) - .returns(className()) - .build()) - .addMethod(MethodSpec.methodBuilder("region") - .addAnnotation(Override.class) - .addModifiers(PUBLIC, ABSTRACT) - .addParameter(Region.class, "region") - .returns(className().nestedClass("Builder")) - .addJavadoc("Configure the region") - .build()) - .addMethod(MethodSpec.methodBuilder("endpointOverride") - .addAnnotation(Override.class) - .addModifiers(PUBLIC, ABSTRACT) - .addParameter(URI.class, "endpointOverride") - .returns(className().nestedClass("Builder")) - .addJavadoc("Configure the endpointOverride") - .build()) - .addMethod(MethodSpec.methodBuilder("overrideConfiguration") - .addAnnotation(Override.class) - .addModifiers(PUBLIC, ABSTRACT) - .addParameter(ClientOverrideConfiguration.class, "clientOverrideConfiguration") - .returns(className().nestedClass("Builder")) - .addJavadoc("Configure the client override configuration") - .build()) - .addMethod(MethodSpec.methodBuilder("endpointProvider") - .addAnnotation(Override.class) - .addModifiers(PUBLIC, ABSTRACT) - .addParameter(EndpointProvider.class, "endpointProvider") - .returns(className().nestedClass("Builder")) - .addJavadoc("Configure the endpointProvider") - .build()) - .build(); + TypeSpec.Builder builder = TypeSpec.interfaceBuilder("Builder") + .addModifiers(PUBLIC) + .addSuperinterface(ClassName.get(AwsServiceClientConfiguration.class).nestedClass( + "Builder")) + .addJavadoc("A builder for creating a {@link $T}", className()); + for (Field field : utils.serviceClientConfigurationFields()) { + builder.addMethod(baseSetterForField(field) + .addModifiers(ABSTRACT) + .build()); + builder.addMethod(baseGetterForField(field) + .addModifiers(ABSTRACT) + .build()); + } + builder.addMethod(MethodSpec.methodBuilder("build") + .addAnnotation(Override.class) + .addModifiers(PUBLIC, ABSTRACT) + .returns(className()) + .build()); + return builder.build(); + } + + private void addLocalFieldForDataIfNeeded(Field field, TypeSpec.Builder builder) { + if (field.isLocalField()) { + builder.addField(field.type(), field.name(), PRIVATE, FINAL); + } + } + + private MethodSpec.Builder baseSetterForField(Field field) { + MethodSpec fieldBuilderSetter = field.builderSetter(); + if (fieldBuilderSetter != null) { + return fieldBuilderSetter.toBuilder() + .returns(className().nestedClass("Builder")); + } + + MethodSpec.Builder builder = MethodSpec.methodBuilder(field.name()) + .addModifiers(PUBLIC) + .addParameter(field.type(), field.name()) + .addJavadoc("Sets the value for " + field.doc()) + .returns(className().nestedClass("Builder")); + if (!field.isLocalField()) { + builder.addAnnotation(Override.class); + } + return builder; + } + + private MethodSpec getterForDataField(Field field) { + return getterForField(field, "config", true); + } + + private MethodSpec getterForField(Field field, String fieldName, boolean forDataGetter) { + MethodSpec fieldBuilderGetter = field.builderGetterImpl(); + if (fieldBuilderGetter != null) { + return fieldBuilderGetter.toBuilder() + .returns(field.type()) + .build(); + } + + MethodSpec.Builder builder = baseGetterForField(field); + if (!forDataGetter && field.isLocalField()) { + builder.addAnnotation(Override.class); + } + if (forDataGetter && field.isLocalField()) { + return builder.addStatement("return $L", field.name()) + .build(); + } + if (field.optionClass() == null) { + return builder.addStatement("return $L", field.name()) + .build(); + } + if (field.baseType() != null) { + return builder.addStatement("$T result = $L.option($T.$L)", + field.baseType(), fieldName, field.optionClass(), field.optionName()) + .beginControlFlow("if (result == null)") + .addStatement("return null") + .endControlFlow() + .addStatement("return $T.isInstanceOf($T.class, result, $S + $T.class.getSimpleName())", + Validate.class, field.type(), + "Expected an instance of ", field.type()) + .build(); + } + return builder.addStatement("return $L.option($T.$L)", fieldName, field.optionClass(), field.optionName()) + .build(); } - private TypeSpec builderImplSpec() { - return TypeSpec.classBuilder("BuilderImpl") - .addModifiers(PRIVATE, STATIC, FINAL) - .addSuperinterface(className().nestedClass("Builder")) - .superclass(ClassName.get(AwsServiceClientConfiguration.class).nestedClass("BuilderImpl")) - .addMethod(MethodSpec.constructorBuilder() - .addModifiers(PRIVATE) - .build()) - .addMethod(MethodSpec.constructorBuilder() - .addModifiers(PRIVATE) - .addParameter(className(), "serviceClientConfiguration") - .addStatement("super(serviceClientConfiguration)") - .build()) - .addMethod(MethodSpec.methodBuilder("region") - .addAnnotation(Override.class) - .addModifiers(PUBLIC) - .addParameter(Region.class, "region") - .returns(className().nestedClass("Builder")) - .addStatement("this.region = region") - .addStatement("return this") - .build()) - .addMethod(MethodSpec.methodBuilder("overrideConfiguration") - .addAnnotation(Override.class) - .addModifiers(PUBLIC) - .addParameter(ClientOverrideConfiguration.class, "clientOverrideConfiguration") - .returns(className().nestedClass("Builder")) - .addStatement("this.overrideConfiguration = clientOverrideConfiguration") - .addStatement("return this") - .build()) - .addMethod(MethodSpec.methodBuilder("endpointOverride") - .addAnnotation(Override.class) - .addModifiers(PUBLIC) - .addParameter(URI.class, "endpointOverride") - .returns(className().nestedClass("Builder")) - .addStatement("this.endpointOverride = endpointOverride") - .addStatement("return this") - .build()) - .addMethod(MethodSpec.methodBuilder("endpointProvider") - .addAnnotation(Override.class) - .addModifiers(PUBLIC) - .addParameter(EndpointProvider.class, "endpointProvider") - .returns(className().nestedClass("Builder")) - .addStatement("this.endpointProvider = endpointProvider") - .addStatement("return this") - .build()) - .addMethod(MethodSpec.methodBuilder("build") - .addAnnotation(Override.class) - .addModifiers(PUBLIC) - .returns(className()) - .addStatement("return new $T(this)", className()) - .build()) - .build(); + private MethodSpec.Builder baseGetterForField(Field field) { + MethodSpec.Builder builder = MethodSpec.methodBuilder(field.name()) + .addModifiers(PUBLIC) + .addJavadoc("Gets the value for " + field.doc()) + .returns(field.type()); + if (!field.isLocalField()) { + builder.addAnnotation(Override.class); + } + return builder; } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationUtils.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationUtils.java new file mode 100644 index 000000000000..6e2d33f15ae5 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationUtils.java @@ -0,0 +1,508 @@ +/* + * 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.model; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.WildcardTypeName; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.Modifier; +import software.amazon.awssdk.awscore.AwsServiceClientConfiguration; +import software.amazon.awssdk.awscore.client.config.AwsClientOption; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils; +import software.amazon.awssdk.core.SdkServiceClientConfiguration; +import software.amazon.awssdk.core.client.config.ClientOption; +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.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.utils.Validate; + +public class ServiceClientConfigurationUtils { + private static final List BASE_FIELDS = baseServiceClientConfigurationFields(); + private final AuthSchemeSpecUtils authSchemeSpecUtils; + private final ClassName configurationClassName; + private final ClassName configurationBuilderClassName; + private final ClassName sdkClientConfigurationUtilClassName; + + public ServiceClientConfigurationUtils(IntermediateModel model) { + String basePackage = model.getMetadata().getFullClientPackageName(); + String serviceId = model.getMetadata().getServiceName(); + configurationClassName = ClassName.get(basePackage, serviceId + "ServiceClientConfiguration"); + configurationBuilderClassName = ClassName.get(model.getMetadata().getFullClientInternalPackageName(), + serviceId + "ServiceClientConfigurationBuilder"); + sdkClientConfigurationUtilClassName = ClassName.get(model.getMetadata().getFullClientInternalPackageName(), + "SdkClientConfigurationUtil"); + authSchemeSpecUtils = new AuthSchemeSpecUtils(model); + + } + + /** + * Returns the {@link ClassName} of the service client configuration class. + */ + public ClassName serviceClientConfigurationClassName() { + return configurationClassName; + } + + /** + * Returns the {@link ClassName} of the builder for the service client configuration class. + */ + public ClassName serviceClientConfigurationBuilderClassName() { + return configurationBuilderClassName; + } + + /** + * Returns the list of fields present in the service client configuration class with its corresponding {@link ClientOption} + * mapping to the {@link SdkClientConfiguration} class. + */ + public List serviceClientConfigurationFields() { + List fields = new ArrayList<>(); + fields.add(overrideConfigurationField()); + fields.addAll(BASE_FIELDS); + fields.add(Field.builder("authSchemeProvider", authSchemeSpecUtils.providerInterfaceName()) + .doc("auth scheme provider") + .optionClass(SdkClientOption.class) + .optionValue(SdkClientOption.AUTH_SCHEME_PROVIDER) + .baseType(ClassName.get(AuthSchemeProvider.class)) + .build()); + return fields; + } + + private Field overrideConfigurationField() { + Field.Builder builder = Field.builder("overrideConfiguration", ClientOverrideConfiguration.class) + .doc("client override configuration") + .definingClass(SdkServiceClientConfiguration.class); + + builder.copyToConfiguration( + CodeBlock.builder() + .beginControlFlow("if (overrideConfiguration != null)") + .addStatement("$T.copyOverridesToConfiguration(overrideConfiguration, internalBuilder)", + sdkClientConfigurationUtilClassName) + .endControlFlow() + .build() + ); + + return builder.build(); + } + + private static List baseServiceClientConfigurationFields() { + return Arrays.asList( + endpointOverrideField(), + Field.builder("endpointProvider", EndpointProvider.class) + .doc("endpoint provider") + .definingClass(SdkServiceClientConfiguration.class) + .optionClass(SdkClientOption.class) + .optionValue(SdkClientOption.ENDPOINT_PROVIDER) + .build(), + Field.builder("region", Region.class) + .doc("AWS region") + .definingClass(AwsServiceClientConfiguration.class) + .optionClass(AwsClientOption.class) + .optionValue(AwsClientOption.AWS_REGION) + .build(), + credentialsProviderField(), + authSchemesField() + ); + } + + private static Field endpointOverrideField() { + Field.Builder builder = Field.builder("endpointOverride", URI.class) + .doc("endpoint override") + .definingClass(SdkServiceClientConfiguration.class); + builder.constructFromConfiguration( + CodeBlock.builder() + .beginControlFlow("if (Boolean.TRUE.equals(internalBuilder.option($T.$L)))", + SdkClientOption.class, fieldName(SdkClientOption.ENDPOINT_OVERRIDDEN, + SdkClientOption.class)) + .addStatement("this.endpointOverride = internalBuilder.option($T.$L)", + SdkClientOption.class, fieldName(SdkClientOption.ENDPOINT, + SdkClientOption.class)) + .endControlFlow() + .build() + ); + + builder.copyToConfiguration( + CodeBlock.builder() + .beginControlFlow("if (endpointOverride != null)") + .addStatement("internalBuilder.option($T.$L, endpointOverride)", + SdkClientOption.class, fieldName(SdkClientOption.ENDPOINT, SdkClientOption.class)) + .addStatement("internalBuilder.option($T.$L, true)", + SdkClientOption.class, fieldName(SdkClientOption.ENDPOINT_OVERRIDDEN, SdkClientOption.class)) + .endControlFlow() + .build() + ); + + return builder.build(); + } + + private static Field credentialsProviderField() { + Field.Builder builder = Field.builder("credentialsProvider", + ParameterizedTypeName.get(ClassName.get(IdentityProvider.class), + WildcardTypeName.subtypeOf(AwsCredentialsIdentity.class))) + .doc("credentials provider") + .definingClass(AwsServiceClientConfiguration.class); + + builder.constructFromConfiguration( + CodeBlock.builder() + .addStatement("this.credentialsProvider = internalBuilder.option($T.$L)", + AwsClientOption.class, fieldName(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER, + AwsClientOption.class)) + .build() + ); + + builder.copyToConfiguration( + // TODO(sra-plugins) + // This code duplicates the logic here + // https://github.com/aws/aws-sdk-java-v2/blob/fa9dbcce47637486e3f7d4d366ab6509b535342a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java#L212 + // That adds the credentialsProvider to the identityProviders class. This is for request level plugins, + // to be able to support credentialsProvider overrides. + CodeBlock.builder() + .beginControlFlow("if (credentialsProvider != null &&" + + " !credentialsProvider.equals(internalBuilder.option($T.$L)))", + AwsClientOption.class, fieldName(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER, + AwsClientOption.class)) + .addStatement("internalBuilder.option($T.$L, credentialsProvider)", + AwsClientOption.class, fieldName(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER, + AwsClientOption.class)) + .addStatement("$T identityProviders = internalBuilder.option($T.$L)", + IdentityProviders.class, SdkClientOption.class, + fieldName(SdkClientOption.IDENTITY_PROVIDERS, SdkClientOption.class)) + .beginControlFlow("if (identityProviders == null)") + .addStatement("identityProviders = $T.builder().putIdentityProvider(credentialsProvider).build()", + IdentityProviders.class) + .nextControlFlow(" else ") + .addStatement("identityProviders = identityProviders.toBuilder()" + + ".putIdentityProvider(credentialsProvider)" + + ".build()") + .endControlFlow() + .addStatement("internalBuilder.option($T.$L, identityProviders)", + SdkClientOption.class, fieldName(SdkClientOption.IDENTITY_PROVIDERS, SdkClientOption.class)) + .endControlFlow() + .build() + ); + + return builder.build(); + } + + + private static Field authSchemesField() { + TypeName authSchemeGenericType = ParameterizedTypeName.get(ClassName.get(AuthScheme.class), + WildcardTypeName.subtypeOf(Object.class)); + TypeName authSchemesType = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(String.class), + authSchemeGenericType); + Field.Builder builder = Field.builder("authSchemes", + authSchemesType) + .doc("auth schemes") + .definingClass(SdkServiceClientConfiguration.class); + + builder.constructFromConfiguration( + CodeBlock.builder() + .addStatement("$T authSchemes = internalBuilder.option($T.$L)", + authSchemesType, SdkClientOption.class, + fieldName(SdkClientOption.AUTH_SCHEMES, SdkClientOption.class)) + .beginControlFlow("if (authSchemes != null)") + .addStatement("authSchemes = new $T<>(authSchemes)", HashMap.class) + .endControlFlow() + .addStatement("this.authSchemes = authSchemes") + .build() + ); + + builder.copyToConfiguration( + CodeBlock.builder() + .beginControlFlow("if (authSchemes != null &&" + + " !authSchemes.equals(internalBuilder.option($T.$L)))", + SdkClientOption.class, fieldName(SdkClientOption.AUTH_SCHEMES, + SdkClientOption.class)) + .addStatement("internalBuilder.option($T.$L, authSchemes())", + SdkClientOption.class, fieldName(SdkClientOption.AUTH_SCHEMES, + SdkClientOption.class)) + .endControlFlow() + .build() + ); + + builder.builderSetterImpl( + MethodSpec.methodBuilder("putAuthScheme") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addParameter(authSchemeGenericType, "authScheme") + .beginControlFlow("if (authSchemes == null)") + .addStatement("authSchemes = new $T<>()", HashMap.class) + .endControlFlow() + .addStatement("authSchemes.put(authScheme.schemeId(), authScheme)") + .addStatement("return this") + .build() + ); + + builder.builderSetter( + MethodSpec.methodBuilder("putAuthScheme") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addParameter(authSchemeGenericType, "authScheme") + .build() + ); + + builder.builderGetterImpl( + MethodSpec.methodBuilder("authSchemes") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(authSchemesType) + .beginControlFlow("if (authSchemes == null)") + .addStatement("return $T.emptyMap()", Collections.class) + .endControlFlow() + .addStatement("return $T.unmodifiableMap(new $T<>(authSchemes))", Collections.class, HashMap.class) + .build() + ); + + return builder.build(); + } + + static class Field { + private final String name; + private final TypeName type; + private final Class definingClass; + private final Class optionClass; + private final String optionName; + private final String doc; + private final TypeName baseType; + private final CodeBlock constructFromConfiguration; + private final CodeBlock copyToConfiguration; + private final MethodSpec builderSetterImpl; + private final MethodSpec builderSetter; + private final MethodSpec builderGetterImpl; + + Field(Field.Builder builder) { + this.name = Validate.paramNotNull(builder.name, "name"); + this.type = Validate.paramNotNull(builder.type, "type"); + this.definingClass = builder.definingClass; + this.doc = Validate.paramNotNull(builder.doc, "doc"); + this.optionClass = builder.optionClass; + this.optionName = builder.optionName; + this.baseType = builder.baseType; + this.constructFromConfiguration = builder.constructFromConfiguration; + this.copyToConfiguration = builder.copyToConfiguration; + this.builderSetterImpl = builder.builderSetterImpl; + this.builderSetter = builder.builderSetter; + this.builderGetterImpl = builder.builderGetterImpl; + } + + public boolean isLocalField() { + return definingClass == null; + } + + public String name() { + return name; + } + + public TypeName type() { + return type; + } + + public Class definingClass() { + return definingClass; + } + + public Class optionClass() { + return optionClass; + } + + public String optionName() { + return optionName; + } + + public String doc() { + return doc; + } + + public TypeName baseType() { + return baseType; + } + + public CodeBlock constructFromConfiguration() { + return constructFromConfiguration; + } + + public CodeBlock copyToConfiguration() { + return copyToConfiguration; + } + + public MethodSpec builderSetterImpl() { + return builderSetterImpl; + } + + public MethodSpec builderSetter() { + return builderSetter; + } + + public MethodSpec builderGetterImpl() { + return builderGetterImpl; + } + + public static Field.Builder builder() { + return new Field.Builder(); + } + + public static Field.Builder builder(String name, TypeName type) { + return new Field.Builder() + .name(name) + .type(type); + } + + public static Field.Builder builder(String name, Class type) { + return new Field.Builder() + .name(name) + .type(type); + } + + static class Builder { + private String name; + private TypeName type; + private String doc; + private Class definingClass; + private Class optionClass; + private ClientOption value; + private String optionName; + private TypeName baseType; + private CodeBlock constructFromConfiguration; + private CodeBlock copyToConfiguration; + private MethodSpec builderSetterImpl; + private MethodSpec builderSetter; + private MethodSpec builderGetterImpl; + + public Field.Builder name(String name) { + this.name = name; + return this; + } + + public Field.Builder type(Class type) { + this.type = ClassName.get(type); + return this; + } + + public Field.Builder type(TypeName type) { + this.type = type; + return this; + } + + public Field.Builder doc(String doc) { + this.doc = doc; + return this; + } + + public Field.Builder optionClass(Class optionClass) { + this.optionClass = optionClass; + return this; + } + + public Field.Builder optionValue(ClientOption value) { + this.value = value; + return this; + } + + public Field.Builder baseType(TypeName baseType) { + this.baseType = baseType; + return this; + } + + public Field.Builder definingClass(Class definingClass) { + this.definingClass = definingClass; + return this; + } + + public Field.Builder constructFromConfiguration(CodeBlock constructFromConfiguration) { + this.constructFromConfiguration = constructFromConfiguration; + return this; + } + + public Field.Builder copyToConfiguration(CodeBlock copyToConfiguration) { + this.copyToConfiguration = copyToConfiguration; + return this; + } + + public Field.Builder builderSetterImpl(MethodSpec builderSetter) { + this.builderSetterImpl = builderSetter; + return this; + } + + public Field.Builder builderSetter(MethodSpec builderSetter) { + this.builderSetter = builderSetter; + return this; + } + + public Field.Builder builderGetterImpl(MethodSpec builderGetterImpl) { + this.builderGetterImpl = builderGetterImpl; + return this; + } + + public Field build() { + if (value != null && optionClass != null) { + optionName = fieldName(value, optionClass); + } + return new Field(this); + } + } + } + + /** + * This method resolves an static reference to its name, for instance, when called with + *

+     * fieldName(AwsClientOption.AWS_REGION, AwsClientOption.class)
+     * 
+ * it will return the string "AWS_REGION" that we can use for codegen. Using the value directly avoid typo bugs and allows the + * compiler and the IDE to know about this relationship. + *

+ * This method uses the fully qualified names in the reflection package to avoid polluting this class imports. Adapted from + * https://stackoverflow.com/a/35416606 + */ + private static String fieldName(Object fieldObject, Class parent) { + java.lang.reflect.Field[] allFields = parent.getFields(); + for (java.lang.reflect.Field field : allFields) { + int modifiers = field.getModifiers(); + if (!java.lang.reflect.Modifier.isStatic(modifiers)) { + continue; + } + Object currentFieldObject; + try { + // For static fields you can pass a null to get back its value. + currentFieldObject = field.get(null); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + boolean isWantedField = fieldObject.equals(currentFieldObject); + if (isWantedField) { + return field.getName(); + } + } + throw new java.util.NoSuchElementException(String.format("cannot find constant %s in class %s", + fieldObject, + parent.getClass().getName())); + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointAuthSchemeInterceptorClassSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointAuthSchemeInterceptorClassSpec.java deleted file mode 100644 index 8d35223c1e85..000000000000 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointAuthSchemeInterceptorClassSpec.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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.rules; - -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterizedTypeName; -import com.squareup.javapoet.TypeSpec; -import java.util.List; -import java.util.function.Supplier; -import javax.lang.model.element.Modifier; -import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.signer.Aws4Signer; -import software.amazon.awssdk.auth.signer.AwsS3V4Signer; -import software.amazon.awssdk.auth.signer.SignerLoader; -import software.amazon.awssdk.awscore.AwsRequest; -import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; -import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; -import software.amazon.awssdk.awscore.util.SignerOverrideUtils; -import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; -import software.amazon.awssdk.codegen.poet.ClassSpec; -import software.amazon.awssdk.codegen.poet.PoetUtils; -import software.amazon.awssdk.core.SdkRequest; -import software.amazon.awssdk.core.exception.SdkClientException; -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.SdkInternalExecutionAttribute; -import software.amazon.awssdk.core.signer.Signer; -import software.amazon.awssdk.endpoints.Endpoint; - -/** - * Generates the Endpoint Interceptor responsible for applying the {@link AwsEndpointAttribute#AUTH_SCHEMES} property on the - * endpoint if they exist. Auth schemes describe auth related requirements for the endpoint, such as signing name, signing - * region, and the name of the auth scheme to use, such as SigV4. - */ -public class EndpointAuthSchemeInterceptorClassSpec implements ClassSpec { - private static final String SIGV4_NAME = "sigv4"; - private static final String SIGV4A_NAME = "sigv4a"; - - private final EndpointRulesSpecUtils endpointRulesSpecUtils; - - public EndpointAuthSchemeInterceptorClassSpec(IntermediateModel intermediateModel) { - this.endpointRulesSpecUtils = new EndpointRulesSpecUtils(intermediateModel); - } - - @Override - public TypeSpec poetSpec() { - TypeSpec.Builder b = PoetUtils.createClassBuilder(className()) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addAnnotation(SdkInternalApi.class) - .addSuperinterface(ExecutionInterceptor.class); - - b.addMethod(modifyRequestMethod()); - b.addMethod(signerProviderMethod()); - - return b.build(); - } - - @Override - public ClassName className() { - return endpointRulesSpecUtils.authSchemesInterceptorName(); - } - - private MethodSpec modifyRequestMethod() { - MethodSpec.Builder builder = MethodSpec.methodBuilder("modifyRequest") - .addModifiers(Modifier.PUBLIC) - .addAnnotation(Override.class) - .addParameter(ClassName.get(Context.ModifyRequest.class), "context") - .addParameter(ExecutionAttributes.class, "executionAttributes") - .returns(SdkRequest.class); - - builder.addStatement("$T resolvedEndpoint = executionAttributes.getAttribute($T.RESOLVED_ENDPOINT)", Endpoint.class, - SdkInternalExecutionAttribute.class); - - builder.addStatement("$1T request = ($1T) context.request()", AwsRequest.class); - - builder.beginControlFlow("if (resolvedEndpoint.headers() != null)") - .addStatement("request = $T.addHeaders(request, resolvedEndpoint.headers())", - endpointRulesSpecUtils.rulesRuntimeClassName("AwsEndpointProviderUtils")); - builder.endControlFlow(); - - builder.addStatement("$T authSchemes = resolvedEndpoint.attribute($T.AUTH_SCHEMES)", - ParameterizedTypeName.get(List.class, EndpointAuthScheme.class), AwsEndpointAttribute.class); - - builder.beginControlFlow("if (authSchemes == null)") - .addStatement("return request") - .endControlFlow(); - - // find the scheme to use - builder.addStatement("$T chosenAuthScheme = $T.chooseAuthScheme(authSchemes)", EndpointAuthScheme.class, - endpointRulesSpecUtils.rulesRuntimeClassName("AuthSchemeUtils")); - - // Create a signer provider - builder.addStatement("$T signerProvider = signerProvider(chosenAuthScheme)", ParameterizedTypeName.get(Supplier.class, - Signer.class)); - - // Set signing attributes - builder.addStatement("$T.setSigningParams(executionAttributes, chosenAuthScheme)", - endpointRulesSpecUtils.rulesRuntimeClassName("AuthSchemeUtils")); - - // Override signer - builder.addStatement("return $T.overrideSignerIfNotOverridden(request, executionAttributes, signerProvider)", - SignerOverrideUtils.class); - - - return builder.build(); - } - - private MethodSpec signerProviderMethod() { - MethodSpec.Builder builder = MethodSpec.methodBuilder("signerProvider") - .addModifiers(Modifier.PRIVATE) - .addParameter(EndpointAuthScheme.class, "authScheme") - .returns(ParameterizedTypeName.get(Supplier.class, Signer.class)); - - builder.beginControlFlow("switch (authScheme.name())"); - builder.addCode("case $S:", SIGV4_NAME); - if (endpointRulesSpecUtils.isS3() || endpointRulesSpecUtils.isS3Control()) { - builder.addStatement("return $T::create", AwsS3V4Signer.class); - } else { - builder.addStatement("return $T::create", Aws4Signer.class); - } - - builder.addCode("case $S:", SIGV4A_NAME); - if (endpointRulesSpecUtils.isS3() || endpointRulesSpecUtils.isS3Control()) { - builder.addStatement("return $T::getS3SigV4aSigner", SignerLoader.class); - } else { - builder.addStatement("return $T::getSigV4aSigner", SignerLoader.class); - } - - builder.addCode("default:"); - builder.addStatement("break"); - builder.endControlFlow(); - - builder.addStatement("throw $T.create($S + authScheme.name())", - SdkClientException.class, - "Don't know how to create signer for auth scheme: "); - - return builder.build(); - } -} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointParametersClassSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointParametersClassSpec.java index a330213aa873..eebe516b02d2 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointParametersClassSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointParametersClassSpec.java @@ -15,14 +15,9 @@ package software.amazon.awssdk.codegen.poet.rules; -import com.fasterxml.jackson.core.TreeNode; -import com.fasterxml.jackson.jr.stree.JrsBoolean; -import com.fasterxml.jackson.jr.stree.JrsString; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; @@ -30,11 +25,9 @@ import javax.lang.model.element.Modifier; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; -import software.amazon.awssdk.codegen.model.rules.endpoints.BuiltInParameter; import software.amazon.awssdk.codegen.model.rules.endpoints.ParameterModel; import software.amazon.awssdk.codegen.poet.ClassSpec; import software.amazon.awssdk.codegen.poet.PoetUtils; -import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.utils.builder.CopyableBuilder; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -61,8 +54,8 @@ public TypeSpec poetSpec() { .addModifiers(Modifier.PUBLIC, Modifier.FINAL); parameters().forEach((name, model) -> { - b.addField(fieldSpec(name, model).toBuilder().addModifiers(Modifier.FINAL).build()); - b.addMethod(accessorMethod(name, model)); + b.addField(endpointRulesSpecUtils.parameterClassField(name, model)); + b.addMethod(endpointRulesSpecUtils.parameterClassAccessorMethod(name, model)); }); b.addMethod(toBuilderMethod()); @@ -81,7 +74,7 @@ private TypeSpec builderInterfaceSpec() { .addModifiers(Modifier.PUBLIC); parameters().forEach((name, model) -> { - b.addMethod(setterMethodDeclaration(name, model)); + b.addMethod(endpointRulesSpecUtils.parameterBuilderSetterMethodDeclaration(className(), name, model)); }); b.addMethod(MethodSpec.methodBuilder("build") @@ -103,8 +96,8 @@ private TypeSpec builderImplSpec() { b.addMethod(toBuilderConstructor().build()); parameters().forEach((name, model) -> { - b.addField(fieldSpec(name, model).toBuilder().initializer(defaultValueCode(model)).build()); - b.addMethod(builderSetterMethod(name, model)); + b.addField(endpointRulesSpecUtils.parameterBuilderFieldSpec(name, model)); + b.addMethod(endpointRulesSpecUtils.parameterBuilderSetterMethod(className(), name, model)); }); b.addMethod(MethodSpec.methodBuilder("build") @@ -131,51 +124,6 @@ private Map parameters() { return intermediateModel.getEndpointRuleSetModel().getParameters(); } - private ParameterSpec parameterSpec(String name, ParameterModel model) { - return ParameterSpec.builder(endpointRulesSpecUtils.parameterType(model), variableName(name)).build(); - } - - private FieldSpec fieldSpec(String name, ParameterModel model) { - return FieldSpec.builder(endpointRulesSpecUtils.parameterType(model), variableName(name)) - .addModifiers(Modifier.PRIVATE) - .build(); - } - - private MethodSpec setterMethodDeclaration(String name, ParameterModel model) { - MethodSpec.Builder b = paramMethodBuilder(name, model); - b.addModifiers(Modifier.ABSTRACT); - b.addParameter(parameterSpec(name, model)); - b.returns(builderInterfaceName()); - return b.build(); - } - - private MethodSpec accessorMethod(String name, ParameterModel model) { - MethodSpec.Builder b = paramMethodBuilder(name, model); - b.returns(endpointRulesSpecUtils.parameterType(model)); - b.addStatement("return $N", variableName(name)); - return b.build(); - } - - private MethodSpec builderSetterMethod(String name, ParameterModel model) { - String memberName = variableName(name); - - MethodSpec.Builder b = paramMethodBuilder(name, model) - .addAnnotation(Override.class) - .addParameter(parameterSpec(name, model)) - .returns(builderInterfaceName()) - .addStatement("this.$1N = $1N", memberName); - - TreeNode defaultValue = model.getDefault(); - if (defaultValue != null) { - b.beginControlFlow("if (this.$N == null)", memberName); - b.addStatement("this.$N = $L", memberName, defaultValueCode(model)); - b.endControlFlow(); - } - - b.addStatement("return this"); - return b.build(); - } - private MethodSpec ctor() { MethodSpec.Builder b = MethodSpec.constructorBuilder() .addModifiers(Modifier.PRIVATE) @@ -208,44 +156,6 @@ private String variableName(String name) { return intermediateModel.getNamingStrategy().getVariableName(name); } - private CodeBlock defaultValueCode(ParameterModel parameterModel) { - CodeBlock.Builder b = CodeBlock.builder(); - - TreeNode defaultValue = parameterModel.getDefault(); - - if (defaultValue == null) { - return b.build(); - } - - switch (defaultValue.asToken()) { - case VALUE_STRING: - String stringValue = ((JrsString) defaultValue).getValue(); - if (parameterModel.getBuiltInEnum() == BuiltInParameter.AWS_REGION) { - b.add("$T.of($S)", Region.class, stringValue); - } else { - b.add("$S", stringValue); - } - break; - case VALUE_TRUE: - case VALUE_FALSE: - b.add("$L", ((JrsBoolean) defaultValue).booleanValue()); - break; - default: - throw new RuntimeException("Don't know how to set default value for parameter of type " - + defaultValue.asToken()); - } - return b.build(); - } - - private MethodSpec.Builder paramMethodBuilder(String name, ParameterModel model) { - MethodSpec.Builder b = MethodSpec.methodBuilder(endpointRulesSpecUtils.paramMethodName(name)); - b.addModifiers(Modifier.PUBLIC); - if (model.getDeprecated() != null) { - b.addAnnotation(Deprecated.class); - } - return b; - } - private MethodSpec.Builder toBuilderConstructor() { MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder(); constructorBuilder.addModifiers(Modifier.PRIVATE); diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java index 9a8cdfdc127d..38fe5355b1d1 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java @@ -19,16 +19,30 @@ import com.fasterxml.jackson.jr.stree.JrsBoolean; import com.fasterxml.jackson.jr.stree.JrsString; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeVariableName; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletionException; +import java.util.function.Supplier; import javax.lang.model.element.Modifier; import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.signer.Aws4Signer; +import software.amazon.awssdk.auth.signer.AwsS3V4Signer; +import software.amazon.awssdk.auth.signer.SignerLoader; import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; +import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; +import software.amazon.awssdk.awscore.util.SignerOverrideUtils; import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; import software.amazon.awssdk.codegen.model.intermediate.OperationModel; import software.amazon.awssdk.codegen.model.rules.endpoints.ParameterModel; @@ -40,14 +54,25 @@ import software.amazon.awssdk.codegen.poet.ClassSpec; import software.amazon.awssdk.codegen.poet.PoetExtension; import software.amazon.awssdk.codegen.poet.PoetUtils; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils; import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.exception.SdkClientException; 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.signer.Signer; import software.amazon.awssdk.endpoints.Endpoint; +import software.amazon.awssdk.http.SdkHttpRequest; +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.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.identity.spi.Identity; import software.amazon.awssdk.utils.AttributeMap; import software.amazon.awssdk.utils.HostnameValidator; import software.amazon.awssdk.utils.StringUtils; @@ -56,11 +81,21 @@ public class EndpointResolverInterceptorSpec implements ClassSpec { private final IntermediateModel model; private final EndpointRulesSpecUtils endpointRulesSpecUtils; private final PoetExtension poetExtension; + private final boolean dependsOnHttpAuthAws; + private final boolean useSraAuth; public EndpointResolverInterceptorSpec(IntermediateModel model) { this.model = model; this.endpointRulesSpecUtils = new EndpointRulesSpecUtils(model); this.poetExtension = new PoetExtension(model); + + // We need to know whether the service has a dependency on the http-auth-aws module. Because we can't check that + // directly, assume that if they're using AwsV4AuthScheme or AwsV4aAuthScheme that it's available. + Set> supportedAuthSchemes = new AuthSchemeSpecUtils(model).allServiceConcreteAuthSchemeClasses(); + this.dependsOnHttpAuthAws = supportedAuthSchemes.contains(AwsV4AuthScheme.class) || + supportedAuthSchemes.contains(AwsV4aAuthScheme.class); + + this.useSraAuth = new AuthSchemeSpecUtils(model).useSraAuth(); } @Override @@ -71,6 +106,7 @@ public TypeSpec poetSpec() { .addSuperinterface(ExecutionInterceptor.class); b.addMethod(modifyRequestMethod()); + b.addMethod(modifyHttpRequestMethod()); b.addMethod(ruleParams()); b.addMethod(setContextParams()); @@ -79,12 +115,18 @@ public TypeSpec poetSpec() { b.addMethod(setStaticContextParamsMethod()); addStaticContextParamMethods(b); + b.addMethod(authSchemeWithEndpointSignerPropertiesMethod()); + if (hasClientContextParams()) { b.addMethod(setClientContextParamsMethod()); } b.addMethod(hostPrefixMethod()); + if (!useSraAuth) { + b.addMethod(signerProviderMethod()); + } + return b.build(); } @@ -104,28 +146,55 @@ private MethodSpec modifyRequestMethod() { String providerVar = "provider"; + b.addStatement("$T result = context.request()", SdkRequest.class); // We skip resolution if the source of the endpoint is the endpoint discovery call b.beginControlFlow("if ($1T.endpointIsDiscovered(executionAttributes))", endpointRulesSpecUtils.rulesRuntimeClassName("AwsEndpointProviderUtils")); - b.addStatement("return context.request()"); + b.addStatement("return result"); b.endControlFlow(); b.addStatement("$1T $2N = ($1T) executionAttributes.getAttribute($3T.ENDPOINT_PROVIDER)", endpointRulesSpecUtils.providerInterfaceName(), providerVar, SdkInternalExecutionAttribute.class); b.beginControlFlow("try"); - b.addStatement("$T result = $N.resolveEndpoint(ruleParams(context, executionAttributes)).join()", Endpoint.class, - providerVar); + b.addStatement("$T endpoint = $N.resolveEndpoint(ruleParams(result, executionAttributes)).join()", + Endpoint.class, providerVar); b.beginControlFlow("if (!$T.disableHostPrefixInjection(executionAttributes))", endpointRulesSpecUtils.rulesRuntimeClassName("AwsEndpointProviderUtils")); - b.addStatement("$T hostPrefix = hostPrefix(executionAttributes.getAttribute($T.OPERATION_NAME), context.request())", + b.addStatement("$T hostPrefix = hostPrefix(executionAttributes.getAttribute($T.OPERATION_NAME), result)", ParameterizedTypeName.get(Optional.class, String.class), SdkExecutionAttribute.class); b.beginControlFlow("if (hostPrefix.isPresent())"); - b.addStatement("result = $T.addHostPrefix(result, hostPrefix.get())", + b.addStatement("endpoint = $T.addHostPrefix(endpoint, hostPrefix.get())", endpointRulesSpecUtils.rulesRuntimeClassName("AwsEndpointProviderUtils")); b.endControlFlow(); b.endControlFlow(); - b.addStatement("executionAttributes.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, result)"); - b.addStatement("return context.request()"); + + + // If the endpoint resolver returns auth settings, use them as signer properties. + // This effectively works to set the preSRA Signer ExecutionAttributes, so it is not conditional on useSraAuth. + b.addStatement("$T<$T> endpointAuthSchemes = endpoint.attribute($T.AUTH_SCHEMES)", + List.class, EndpointAuthScheme.class, AwsEndpointAttribute.class); + b.addStatement("$T selectedAuthScheme = executionAttributes.getAttribute($T.SELECTED_AUTH_SCHEME)", + SelectedAuthScheme.class, SdkInternalExecutionAttribute.class); + b.beginControlFlow("if (endpointAuthSchemes != null && selectedAuthScheme != null)"); + b.addStatement("selectedAuthScheme = authSchemeWithEndpointSignerProperties(endpointAuthSchemes, selectedAuthScheme)"); + + b.addStatement("executionAttributes.putAttribute($T.SELECTED_AUTH_SCHEME, selectedAuthScheme)", + SdkInternalExecutionAttribute.class); + b.endControlFlow(); + + // For pre SRA client, use Signer as determined by endpoint resolved auth scheme + if (!useSraAuth) { + b.beginControlFlow("if (endpointAuthSchemes != null)"); + b.addStatement("$T chosenAuthScheme = $T.chooseAuthScheme(endpointAuthSchemes)", EndpointAuthScheme.class, + endpointRulesSpecUtils.rulesRuntimeClassName("AuthSchemeUtils")); + b.addStatement("$T<$T> signerProvider = signerProvider(chosenAuthScheme)", Supplier.class, Signer.class); + b.addStatement("result = $T.overrideSignerIfNotOverridden(result, executionAttributes, signerProvider)", + SignerOverrideUtils.class); + b.endControlFlow(); + } + + b.addStatement("executionAttributes.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, endpoint)"); + b.addStatement("return result"); b.endControlFlow(); b.beginControlFlow("catch ($T e)", CompletionException.class); b.addStatement("$T cause = e.getCause()", Throwable.class); @@ -139,11 +208,34 @@ private MethodSpec modifyRequestMethod() { return b.build(); } + private MethodSpec modifyHttpRequestMethod() { + MethodSpec.Builder b = MethodSpec.methodBuilder("modifyHttpRequest") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(SdkHttpRequest.class) + .addParameter(Context.ModifyHttpRequest.class, "context") + .addParameter(ExecutionAttributes.class, "executionAttributes"); + + b.addStatement("$T resolvedEndpoint = executionAttributes.getAttribute($T.RESOLVED_ENDPOINT)", + Endpoint.class, SdkInternalExecutionAttribute.class); + b.beginControlFlow("if (resolvedEndpoint.headers().isEmpty())"); + b.addStatement("return context.httpRequest()"); + b.endControlFlow(); + + b.addStatement("$T httpRequestBuilder = context.httpRequest().toBuilder()", SdkHttpRequest.Builder.class); + b.addCode("resolvedEndpoint.headers().forEach((name, values) -> {"); + b.addStatement("values.forEach(v -> httpRequestBuilder.appendHeader(name, v))"); + b.addCode("});"); + b.addStatement("return httpRequestBuilder.build()"); + + return b.build(); + } + private MethodSpec ruleParams() { MethodSpec.Builder b = MethodSpec.methodBuilder("ruleParams") - .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(endpointRulesSpecUtils.parametersClassName()) - .addParameter(Context.ModifyRequest.class, "context") + .addParameter(SdkRequest.class, "request") .addParameter(ExecutionAttributes.class, "executionAttributes"); b.addStatement("$T builder = $T.builder()", paramsBuilderClass(), endpointRulesSpecUtils.parametersClassName()); @@ -194,7 +286,7 @@ private MethodSpec ruleParams() { if (hasClientContextParams()) { b.addStatement("setClientContextParams(builder, executionAttributes)"); } - b.addStatement("setContextParams(builder, executionAttributes.getAttribute($T.OPERATION_NAME), context.request())", + b.addStatement("setContextParams(builder, executionAttributes.getAttribute($T.OPERATION_NAME), request)", AwsExecutionAttribute.class); b.addStatement("setStaticContextParams(builder, executionAttributes.getAttribute($T.OPERATION_NAME))", AwsExecutionAttribute.class); @@ -452,4 +544,132 @@ private String getHostPrefix(OperationModel opModel) { return endpointTrait.getHostPrefix(); } + + private MethodSpec authSchemeWithEndpointSignerPropertiesMethod() { + TypeVariableName tExtendsIdentity = TypeVariableName.get("T", Identity.class); + TypeName selectedAuthSchemeOfT = ParameterizedTypeName.get(ClassName.get(SelectedAuthScheme.class), + TypeVariableName.get("T")); + TypeName listOfEndpointAuthScheme = ParameterizedTypeName.get(List.class, EndpointAuthScheme.class); + + MethodSpec.Builder method = + MethodSpec.methodBuilder("authSchemeWithEndpointSignerProperties") + .addModifiers(Modifier.PRIVATE) + .addTypeVariable(tExtendsIdentity) + .returns(selectedAuthSchemeOfT) + .addParameter(listOfEndpointAuthScheme, "endpointAuthSchemes") + .addParameter(selectedAuthSchemeOfT, "selectedAuthScheme"); + + method.beginControlFlow("for ($T endpointAuthScheme : endpointAuthSchemes)", EndpointAuthScheme.class); + + if (useSraAuth) { + // Don't include signer properties for auth options that don't match our selected auth scheme + method.beginControlFlow("if (!endpointAuthScheme.schemeId()" + + ".equals(selectedAuthScheme.authSchemeOption().schemeId()))"); + method.addStatement("continue"); + method.endControlFlow(); + } + + method.addStatement("$T option = selectedAuthScheme.authSchemeOption().toBuilder()", AuthSchemeOption.Builder.class); + + if (dependsOnHttpAuthAws) { + method.addCode(copyV4EndpointSignerPropertiesToAuth()); + method.addCode(copyV4aEndpointSignerPropertiesToAuth()); + } + + method.addStatement("throw new $T(\"Endpoint auth scheme '\" + endpointAuthScheme.name() + \"' cannot be mapped to the " + + "SDK auth scheme. Was it declared in the service's model?\")", + IllegalArgumentException.class); + + method.endControlFlow(); + + method.addStatement("return selectedAuthScheme"); + + return method.build(); + } + + private static CodeBlock copyV4EndpointSignerPropertiesToAuth() { + CodeBlock.Builder code = CodeBlock.builder(); + code.beginControlFlow("if (endpointAuthScheme instanceof $T)", SigV4AuthScheme.class); + code.addStatement("$1T v4AuthScheme = ($1T) endpointAuthScheme", SigV4AuthScheme.class); + + code.beginControlFlow("if (v4AuthScheme.isDisableDoubleEncodingSet())"); + code.addStatement("option.putSignerProperty($T.DOUBLE_URL_ENCODE, !v4AuthScheme.disableDoubleEncoding())", + AwsV4HttpSigner.class); + code.endControlFlow(); + + code.beginControlFlow("if (v4AuthScheme.signingRegion() != null)"); + code.addStatement("option.putSignerProperty($T.REGION_NAME, v4AuthScheme.signingRegion())", + AwsV4HttpSigner.class); + code.endControlFlow(); + + code.beginControlFlow("if (v4AuthScheme.signingName() != null)"); + code.addStatement("option.putSignerProperty($T.SERVICE_SIGNING_NAME, v4AuthScheme.signingName())", + AwsV4HttpSigner.class); + code.endControlFlow(); + + code.addStatement("return new $T<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), option.build())", + SelectedAuthScheme.class); + code.endControlFlow(); + return code.build(); + } + + private static CodeBlock copyV4aEndpointSignerPropertiesToAuth() { + CodeBlock.Builder code = CodeBlock.builder(); + + code.beginControlFlow("if (endpointAuthScheme instanceof $T)", SigV4aAuthScheme.class); + code.addStatement("$1T v4aAuthScheme = ($1T) endpointAuthScheme", SigV4aAuthScheme.class); + + code.beginControlFlow("if (v4aAuthScheme.isDisableDoubleEncodingSet())"); + code.addStatement("option.putSignerProperty($T.DOUBLE_URL_ENCODE, !v4aAuthScheme.disableDoubleEncoding())", + AwsV4aHttpSigner.class); + code.endControlFlow(); + + code.beginControlFlow("if (v4aAuthScheme.signingRegionSet() != null)"); + code.addStatement("$1T regionSet = $1T.create(v4aAuthScheme.signingRegionSet())", RegionSet.class); + + code.addStatement("option.putSignerProperty($T.REGION_SET, regionSet)", AwsV4aHttpSigner.class); + code.endControlFlow(); + + code.beginControlFlow("if (v4aAuthScheme.signingName() != null)"); + code.addStatement("option.putSignerProperty($T.SERVICE_SIGNING_NAME, v4aAuthScheme.signingName())", + AwsV4aHttpSigner.class); + code.endControlFlow(); + + code.addStatement("return new $T<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), option.build())", + SelectedAuthScheme.class); + code.endControlFlow(); + return code.build(); + } + + private MethodSpec signerProviderMethod() { + MethodSpec.Builder builder = MethodSpec.methodBuilder("signerProvider") + .addModifiers(Modifier.PRIVATE) + .addParameter(EndpointAuthScheme.class, "authScheme") + .returns(ParameterizedTypeName.get(Supplier.class, Signer.class)); + + builder.beginControlFlow("switch (authScheme.name())"); + builder.addCode("case $S:", "sigv4"); + if (endpointRulesSpecUtils.isS3() || endpointRulesSpecUtils.isS3Control()) { + builder.addStatement("return $T::create", AwsS3V4Signer.class); + } else { + builder.addStatement("return $T::create", Aws4Signer.class); + } + + builder.addCode("case $S:", "sigv4a"); + if (endpointRulesSpecUtils.isS3() || endpointRulesSpecUtils.isS3Control()) { + builder.addStatement("return $T::getS3SigV4aSigner", SignerLoader.class); + } else { + builder.addStatement("return $T::getSigV4aSigner", SignerLoader.class); + } + + builder.addCode("default:"); + builder.addStatement("break"); + builder.endControlFlow(); + + builder.addStatement("throw $T.create($S + authScheme.name())", + SdkClientException.class, + "Don't know how to create signer for auth scheme: "); + + return builder.build(); + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesSpecUtils.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesSpecUtils.java index f0cd296141b2..ef41c826bf3a 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesSpecUtils.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointRulesSpecUtils.java @@ -20,6 +20,9 @@ import com.fasterxml.jackson.jr.stree.JrsString; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import java.io.IOException; @@ -32,6 +35,7 @@ import java.util.jar.JarFile; import java.util.stream.Collectors; import java.util.zip.ZipEntry; +import javax.lang.model.element.Modifier; import software.amazon.awssdk.codegen.internal.Utils; import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; import software.amazon.awssdk.codegen.model.intermediate.Metadata; @@ -78,12 +82,6 @@ public ClassName resolverInterceptorName() { md.getServiceName() + "ResolveEndpointInterceptor"); } - public ClassName authSchemesInterceptorName() { - Metadata md = intermediateModel.getMetadata(); - return ClassName.get(md.getFullInternalEndpointRulesPackageName(), - md.getServiceName() + "EndpointAuthSchemeInterceptor"); - } - public ClassName requestModifierInterceptorName() { Metadata md = intermediateModel.getMetadata(); return ClassName.get(md.getFullInternalEndpointRulesPackageName(), @@ -203,8 +201,197 @@ public List rulesEngineResourceFiles() { } } + public Map parameters() { + return intermediateModel.getEndpointRuleSetModel().getParameters(); + } + public boolean isDeclaredParam(String paramName) { Map parameters = intermediateModel.getEndpointRuleSetModel().getParameters(); return parameters.containsKey(paramName); } + + /** + * Creates a data-class level field for the given parameter. For instance + * + *

+     *     private final Region region;
+     * 
+ */ + public FieldSpec parameterClassField(String name, ParameterModel model) { + return parameterFieldSpecBuilder(name, model) + .addModifiers(Modifier.PRIVATE) + .addModifiers(Modifier.FINAL) + .build(); + } + + /** + * Creates a data-class method to access the given parameter. For instance + * + *
+     *     public Region region() {…};
+     * 
+ */ + public MethodSpec parameterClassAccessorMethod(String name, ParameterModel model) { + MethodSpec.Builder b = parameterMethodBuilder(name, model); + b.returns(parameterType(model)); + b.addStatement("return $N", variableName(name)); + return b.build(); + } + + + /** + * Creates a data-interface method to access the given parameter. For instance + * + *
+     *     Region region();
+     * 
+ */ + public MethodSpec parameterInterfaceAccessorMethod(String name, ParameterModel model) { + MethodSpec.Builder b = parameterMethodBuilder(name, model); + b.returns(parameterType(model)); + b.addModifiers(Modifier.ABSTRACT); + return b.build(); + } + + /** + * Creates a builder-class level field for the given parameter initialized to its default value when present. For instance + * + *
+     *    private Boolean useGlobalEndpoint = false;
+     * 
+ */ + public FieldSpec parameterBuilderFieldSpec(String name, ParameterModel model) { + return parameterFieldSpecBuilder(name, model) + .initializer(parameterDefaultValueCode(model)) + .build(); + } + + /** + * Creates a builder-interface method to set the given parameter. For instance + * + *
+     *    Builder region(Region region);
+     * 
+ * + */ + public MethodSpec parameterBuilderSetterMethodDeclaration(ClassName containingClass, String name, ParameterModel model) { + MethodSpec.Builder b = parameterMethodBuilder(name, model); + b.addModifiers(Modifier.ABSTRACT); + b.addParameter(parameterSpec(name, model)); + b.returns(containingClass.nestedClass("Builder")); + return b.build(); + } + + /** + * Creates a builder-class method to set the given parameter. For instance + * + *
+     *    public Builder region(Region region) {…};
+     * 
+ */ + public MethodSpec parameterBuilderSetterMethod(ClassName containingClass, String name, ParameterModel model) { + String memberName = variableName(name); + + MethodSpec.Builder b = parameterMethodBuilder(name, model) + .addAnnotation(Override.class) + .addParameter(parameterSpec(name, model)) + .returns(containingClass.nestedClass("Builder")) + .addStatement("this.$1N = $1N", memberName); + + TreeNode defaultValue = model.getDefault(); + if (defaultValue != null) { + b.beginControlFlow("if (this.$N == null)", memberName); + b.addStatement("this.$N = $L", memberName, parameterDefaultValueCode(model)); + b.endControlFlow(); + } + + b.addStatement("return this"); + return b.build(); + } + + /** + * Used internally to create a field for the given parameter. Returns the builder that can be further tailor to be used for + * data-classes or for builder-classes. + */ + private FieldSpec.Builder parameterFieldSpecBuilder(String name, ParameterModel model) { + return FieldSpec.builder(parameterType(model), variableName(name)) + .addModifiers(Modifier.PRIVATE); + } + + /** + * Used internally to create the spec for a parameter to be used in a method for the given param model. For instance, for + * {@code ParameterModel} for {@code Region} it creates this parameter for the builder setter. + * + *
+     *    public Builder region(
+     *          Region region // <<--- This
+     *          ) {…};
+     * 
+ */ + private ParameterSpec parameterSpec(String name, ParameterModel model) { + return ParameterSpec.builder(parameterType(model), variableName(name)).build(); + } + + /** + * Used internally to create a accessor method for the given parameter model. Returns the builder that can be further + * tailor to be used for data-classes/interfaces and builder-classes/interfaces. + */ + private MethodSpec.Builder parameterMethodBuilder(String name, ParameterModel model) { + MethodSpec.Builder b = MethodSpec.methodBuilder(paramMethodName(name)); + b.addModifiers(Modifier.PUBLIC); + if (model.getDeprecated() != null) { + b.addAnnotation(Deprecated.class); + } + return b; + } + + /** + * Used internally to create the code to initialize the default value modeled for the given parameter. For instance, if the + * modeled default for region is "us-east-1", it will create + * + *
+     *     Region.of("us-east-1")
+     * 
+ * + * and if the modeled default value for a boolean parameter useGlobalEndpoint is "false", it will create + * + *
+     *     false
+     * 
+ */ + private CodeBlock parameterDefaultValueCode(ParameterModel parameterModel) { + CodeBlock.Builder b = CodeBlock.builder(); + + TreeNode defaultValue = parameterModel.getDefault(); + + if (defaultValue == null) { + return b.build(); + } + + switch (defaultValue.asToken()) { + case VALUE_STRING: + String stringValue = ((JrsString) defaultValue).getValue(); + if (parameterModel.getBuiltInEnum() == BuiltInParameter.AWS_REGION) { + b.add("$T.of($S)", Region.class, stringValue); + } else { + b.add("$S", stringValue); + } + break; + case VALUE_TRUE: + case VALUE_FALSE: + b.add("$L", ((JrsBoolean) defaultValue).booleanValue()); + break; + default: + throw new RuntimeException("Don't know how to set default value for parameter of type " + + defaultValue.asToken()); + } + return b.build(); + } + + /** + * Returns the name as a variable name using the intermediate model naming strategy. + */ + public String variableName(String name) { + return intermediateModel.getNamingStrategy().getVariableName(name); + } } diff --git a/codegen/src/main/resources/software/amazon/awssdk/codegen/poet/model/SdkClientConfigurationUtil.resource.java b/codegen/src/main/resources/software/amazon/awssdk/codegen/poet/model/SdkClientConfigurationUtil.resource.java new file mode 100644 index 000000000000..f5602795ad45 --- /dev/null +++ b/codegen/src/main/resources/software/amazon/awssdk/codegen/poet/model/SdkClientConfigurationUtil.resource.java @@ -0,0 +1,103 @@ + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.core.client.config.ClientOption; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.internal.SdkInternalTestAdvancedClientOption; +import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileFileSupplier; + +@SdkInternalApi +public final class SdkClientConfigurationUtil { + private SdkClientConfigurationUtil() { + } + + /** + * Copies the {@link ClientOverrideConfiguration} values to the configuration builder. + */ + public static SdkClientConfiguration.Builder copyOverridesToConfiguration(ClientOverrideConfiguration overrides, + SdkClientConfiguration.Builder builder) { + + // misc + setClientOption(builder, SdkClientOption.ADDITIONAL_HTTP_HEADERS, overrides.headers()); + setClientOption(builder, SdkClientOption.RETRY_POLICY, overrides.retryPolicy()); + setClientOption(builder, SdkClientOption.API_CALL_TIMEOUT, overrides.apiCallTimeout()); + setClientOption(builder, SdkClientOption.API_CALL_ATTEMPT_TIMEOUT, overrides.apiCallAttemptTimeout()); + setClientOption(builder, SdkClientOption.SCHEDULED_EXECUTOR_SERVICE, overrides.scheduledExecutorService()); + setClientListOption(builder, SdkClientOption.EXECUTION_INTERCEPTORS, overrides.executionInterceptors()); + setClientOption(builder, SdkClientOption.EXECUTION_ATTRIBUTES, overrides.executionAttributes()); + + // advanced option + Signer signer = overrides.advancedOption(SdkAdvancedClientOption.SIGNER).orElse(null); + if (signer != null) { + builder.option(SdkAdvancedClientOption.SIGNER, signer); + builder.option(SdkClientOption.SIGNER_OVERRIDDEN, true); + } + setClientOption(builder, SdkAdvancedClientOption.USER_AGENT_SUFFIX, + overrides.advancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX)); + setClientOption(builder, SdkAdvancedClientOption.USER_AGENT_PREFIX, + overrides.advancedOption(SdkAdvancedClientOption.USER_AGENT_PREFIX)); + setClientOption(builder, SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION, + overrides.advancedOption(SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION)); + overrides.advancedOption(SdkInternalTestAdvancedClientOption.ENDPOINT_OVERRIDDEN_OVERRIDE).ifPresent(value -> { + builder.option(SdkClientOption.ENDPOINT_OVERRIDDEN, value); + }); + + // profile + ProfileFile profileFile = overrides.defaultProfileFile().orElse(null); + if (profileFile != null) { + builder.option(SdkClientOption.PROFILE_FILE_SUPPLIER, ProfileFileSupplier.fixedProfileFile(profileFile)); + } + setClientOption(builder, SdkClientOption.PROFILE_NAME, overrides.defaultProfileName()); + + // misc + setClientListOption(builder, SdkClientOption.METRIC_PUBLISHERS, overrides.metricPublishers()); + setClientOption(builder, SdkAdvancedClientOption.TOKEN_SIGNER, + overrides.advancedOption(SdkAdvancedClientOption.TOKEN_SIGNER)); + setClientOption(builder, SdkClientOption.COMPRESSION_CONFIGURATION, overrides.compressionConfiguration()); + + return builder; + } + + static void setClientOption(SdkClientConfiguration.Builder builder, ClientOption option, T newValue) { + if (newValue != null) { + T oldValue = builder.option(option); + if (oldValue == null || !oldValue.equals(newValue)) { + builder.option(option, newValue); + } + } + } + + static void setClientOption(SdkClientConfiguration.Builder builder, ClientOption option, Optional newValueOpt) { + setClientOption(builder, option, newValueOpt.orElse(null)); + } + + static void setClientListOption(SdkClientConfiguration.Builder builder, ClientOption> option, List newValue) { + if (newValue == null || newValue.isEmpty()) { + return; + } + List oldValue = builder.option(option); + if (oldValue == null || oldValue.isEmpty()) { + builder.option(option, newValue); + return; + } + // Make sure that we don't override derived values or duplicate existing ones. + List result = new ArrayList<>(oldValue); + Set dedup = new HashSet<>(); + dedup.addAll(oldValue); + for (T value : newValue) { + if (!dedup.contains(value)) { + result.add(value); + } + } + builder.option(option, result); + } +} diff --git a/codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AuthSchemeUtils.java.resource b/codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AuthSchemeUtils.java.resource index ce36d0571828..e90be4fdde7c 100644 --- a/codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AuthSchemeUtils.java.resource +++ b/codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AuthSchemeUtils.java.resource @@ -4,16 +4,19 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import software.amazon.awssdk.annotations.SdkProtectedApi; -import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.regions.RegionScope; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.identity.spi.Identity; import software.amazon.awssdk.utils.Logger; +// TODO(sra-identity-auth): seems like this can be SdkInternalApi, similar to other .resource files in this folder, +// since they are generated in each service module @SdkProtectedApi public final class AuthSchemeUtils { private static final Logger LOG = Logger.loggerFor(AuthSchemeUtils.class); @@ -54,102 +57,71 @@ public final class AuthSchemeUtils { String authSchemeName = scheme.get(Identifier.of("name")).expectString(); switch (authSchemeName) { - case SIGV4A_NAME: { - SigV4aAuthScheme.Builder schemeBuilder = SigV4aAuthScheme.builder(); + case SIGV4A_NAME: { + SigV4aAuthScheme.Builder schemeBuilder = SigV4aAuthScheme.builder(); - Value signingName = scheme.get(Identifier.of("signingName")); - if (signingName != null) { - schemeBuilder.signingName(signingName.expectString()); - } - - Value signingRegionSet = scheme.get(Identifier.of("signingRegionSet")); - if (signingRegionSet != null) { - Value.Array signingRegionSetArray = signingRegionSet.expectArray(); - for (int j = 0; j < signingRegionSetArray.size(); ++j) { - schemeBuilder.addSigningRegion(signingRegionSetArray.get(j).expectString()); - } - } + Value signingName = scheme.get(Identifier.of("signingName")); + if (signingName != null) { + schemeBuilder.signingName(signingName.expectString()); + } - Value disableDoubleEncoding = scheme.get(Identifier.of("disableDoubleEncoding")); - if (disableDoubleEncoding != null) { - schemeBuilder.disableDoubleEncoding(disableDoubleEncoding.expectBool()); + Value signingRegionSet = scheme.get(Identifier.of("signingRegionSet")); + if (signingRegionSet != null) { + Value.Array signingRegionSetArray = signingRegionSet.expectArray(); + for (int j = 0; j < signingRegionSetArray.size(); ++j) { + schemeBuilder.addSigningRegion(signingRegionSetArray.get(j).expectString()); } + } - authSchemes.add(schemeBuilder.build()); + Value disableDoubleEncoding = scheme.get(Identifier.of("disableDoubleEncoding")); + if (disableDoubleEncoding != null) { + schemeBuilder.disableDoubleEncoding(disableDoubleEncoding.expectBool()); } - break; - case SIGV4_NAME: { - SigV4AuthScheme.Builder schemeBuilder = SigV4AuthScheme.builder(); - Value signingName = scheme.get(Identifier.of("signingName")); - if (signingName != null) { - schemeBuilder.signingName(signingName.expectString()); - } + authSchemes.add(schemeBuilder.build()); + } + break; + case SIGV4_NAME: { + SigV4AuthScheme.Builder schemeBuilder = SigV4AuthScheme.builder(); - Value signingRegion = scheme.get(Identifier.of("signingRegion")); - if (signingRegion != null) { - schemeBuilder.signingRegion(signingRegion.expectString()); - } + Value signingName = scheme.get(Identifier.of("signingName")); + if (signingName != null) { + schemeBuilder.signingName(signingName.expectString()); + } - Value disableDoubleEncoding = scheme.get(Identifier.of("disableDoubleEncoding")); - if (disableDoubleEncoding != null) { - schemeBuilder.disableDoubleEncoding(disableDoubleEncoding.expectBool()); - } + Value signingRegion = scheme.get(Identifier.of("signingRegion")); + if (signingRegion != null) { + schemeBuilder.signingRegion(signingRegion.expectString()); + } - authSchemes.add(schemeBuilder.build()); + Value disableDoubleEncoding = scheme.get(Identifier.of("disableDoubleEncoding")); + if (disableDoubleEncoding != null) { + schemeBuilder.disableDoubleEncoding(disableDoubleEncoding.expectBool()); } + + authSchemes.add(schemeBuilder.build()); + } + break; + default: + LOG.debug(() -> "Ignoring unknown auth scheme: " + authSchemeName); break; - default: - LOG.debug(() -> "Ignoring unknown auth scheme: " + authSchemeName); - break; } } return authSchemes; } - public static void setSigningParams(ExecutionAttributes executionAttributes, EndpointAuthScheme authScheme) { - if (authScheme instanceof SigV4AuthScheme) { - setSigV4SigningParams(executionAttributes, (SigV4AuthScheme) authScheme); - } else if (authScheme instanceof SigV4aAuthScheme) { - setSigV4aAuthSigningParams(executionAttributes, (SigV4aAuthScheme) authScheme); - } else { - throw SdkClientException.create("Don't know how to set signing params for auth scheme: " + authScheme.name()); + public static 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()); } - } - - private static void setSigV4SigningParams(ExecutionAttributes executionAttributes, SigV4AuthScheme sigV4AuthScheme) { - executionAttributes.putAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE, - !sigV4AuthScheme.disableDoubleEncoding()); - - if (sigV4AuthScheme.signingName() != null) { - executionAttributes.putAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, sigV4AuthScheme.signingName()); - } - - if (sigV4AuthScheme.signingRegion() != null) { - executionAttributes.putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, - Region.of(sigV4AuthScheme.signingRegion())); - } - } - - private static void setSigV4aAuthSigningParams(ExecutionAttributes executionAttributes, SigV4aAuthScheme sigV4aAuthScheme) { - executionAttributes.putAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE, - !sigV4aAuthScheme.disableDoubleEncoding()); - if (sigV4aAuthScheme.signingName() != null) { - executionAttributes.putAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, sigV4aAuthScheme.signingName()); - } - - if (sigV4aAuthScheme.signingRegionSet() != null) { - if (sigV4aAuthScheme.signingRegionSet().size() > 1) { - throw SdkClientException.create("Don't know how to set scope of > 1 region"); - } - - if (sigV4aAuthScheme.signingRegionSet().isEmpty()) { - throw SdkClientException.create("Signing region set is empty"); - } - - String scope = sigV4aAuthScheme.signingRegionSet().get(0); - executionAttributes.putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION_SCOPE, RegionScope.create(scope)); - } + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, selectedAuthScheme); } } diff --git a/codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AwsEndpointProviderUtils.java.resource b/codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AwsEndpointProviderUtils.java.resource index a9a5c05de4d7..83a9d65cb3eb 100644 --- a/codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AwsEndpointProviderUtils.java.resource +++ b/codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AwsEndpointProviderUtils.java.resource @@ -1,13 +1,10 @@ import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; import java.net.URI; -import java.util.ArrayList; import java.util.List; import java.util.Map; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.AwsExecutionAttribute; -import software.amazon.awssdk.awscore.AwsRequest; -import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; @@ -96,11 +93,9 @@ public final class AwsEndpointProviderUtils { URI originalUrl = endpoint.url(); String newHost = prefix + endpoint.url().getHost(); URI newUrl = invokeSafely(() -> new URI(originalUrl.getScheme(), null, newHost, originalUrl.getPort(), - originalUrl.getPath(), originalUrl.getQuery(), originalUrl.getFragment())); + originalUrl.getPath(), originalUrl.getQuery(), originalUrl.getFragment())); - return endpoint.toBuilder() - .url(newUrl) - .build(); + return endpoint.toBuilder().url(newUrl).build(); } public static Endpoint valueAsEndpointOrThrow(Value value) { @@ -167,14 +162,15 @@ public final class AwsEndpointProviderUtils { } return request.toBuilder().protocol(resolvedUri.getScheme()).host(resolvedUri.getHost()).port(resolvedUri.getPort()) - .encodedPath(finalPath).build(); + .encodedPath(finalPath).build(); } /** - * Our goal is to construct [client endpoint path]/[additional path added by resolver]/[request path], so we just need to - * strip the client endpoint path from the marshalled request path to isolate just the part added by the marshaller. Trailing - * slash is removed from client endpoint path before stripping because it could cause the leading slash to be removed from the - * request path: e.g., StringUtils.replaceOnce("/", "//test", "") generates "/test" and the expected result is "//test" + * Our goal is to construct [client endpoint path]/[additional path added by resolver]/[request path], so we just + * need to strip the client endpoint path from the marshalled request path to isolate just the part added by the + * marshaller. Trailing slash is removed from client endpoint path before stripping because it could cause the + * leading slash to be removed from the request path: e.g., StringUtils.replaceOnce("/", "//test", "") generates + * "/test" and the expected result is "//test" */ private static String combinePath(String clientEndpointPath, String requestPath, String resolvedUriPath) { String requestPathWithClientPathRemoved = StringUtils.replaceOnce(requestPath, clientEndpointPath, ""); @@ -182,28 +178,6 @@ public final class AwsEndpointProviderUtils { return finalPath; } - public static AwsRequest addHeaders(AwsRequest request, Map> headers) { - AwsRequestOverrideConfiguration.Builder configBuilder = request.overrideConfiguration() - .map(AwsRequestOverrideConfiguration::toBuilder).orElseGet(AwsRequestOverrideConfiguration::builder); - - headers.forEach((name, values) -> { - List existingValues = configBuilder.headers().get(name); - List updatedValues; - - if (existingValues != null) { - updatedValues = new ArrayList<>(existingValues); - } else { - updatedValues = new ArrayList<>(); - } - - updatedValues.addAll(values); - - configBuilder.putHeader(name, updatedValues); - }); - - return request.toBuilder().overrideConfiguration(configBuilder.build()).build(); - } - private static void addKnownProperties(Endpoint.Builder builder, Map properties) { properties.forEach((n, v) -> { switch (n) { 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 69ab37c3315d..28903e5dedaf 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 @@ -17,7 +17,6 @@ import java.io.File; -import org.eclipse.core.runtime.internal.adaptor.IModel; import software.amazon.awssdk.codegen.C2jModels; import software.amazon.awssdk.codegen.IntermediateModelBuilder; import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig; @@ -108,6 +107,158 @@ public static IntermediateModel queryServiceModels() { return new IntermediateModelBuilder(models).build(); } + public static IntermediateModel queryServiceModelsEndpointAuthParamsWithAllowList() { + File serviceModel = new File(ClientTestModels.class.getResource("client/c2j/query/service-2.json").getFile()); + File customizationModel = + new File(ClientTestModels.class.getResource("client/c2j/query/customization-endpoint-auth-params-with-allowed.config") + .getFile()); + File waitersModel = new File(ClientTestModels.class.getResource("client/c2j/query/waiters-2.json").getFile()); + File endpointRuleSetModel = + new File(ClientTestModels.class.getResource("client/c2j/query/endpoint-rule-set.json").getFile()); + File endpointTestsModel = + new File(ClientTestModels.class.getResource("client/c2j/query/endpoint-tests.json").getFile()); + + C2jModels models = C2jModels + .builder() + .serviceModel(getServiceModel(serviceModel)) + .customizationConfig(getCustomizationConfig(customizationModel)) + .waitersModel(getWaiters(waitersModel)) + .endpointRuleSetModel(getEndpointRuleSet(endpointRuleSetModel)) + .endpointTestSuiteModel(getEndpointTestSuite(endpointTestsModel)) + .build(); + + return new IntermediateModelBuilder(models).build(); + } + + public static IntermediateModel queryServiceModelsEndpointAuthParamsWithoutAllowList() { + File serviceModel = new File(ClientTestModels.class.getResource("client/c2j/query/service-2.json").getFile()); + File customizationModel = + new File(ClientTestModels.class.getResource("client/c2j/query/customization-endpoint-auth-params-without-allowed" + + ".config") + .getFile()); + File waitersModel = new File(ClientTestModels.class.getResource("client/c2j/query/waiters-2.json").getFile()); + File endpointRuleSetModel = + new File(ClientTestModels.class.getResource("client/c2j/query/endpoint-rule-set.json").getFile()); + File endpointTestsModel = + new File(ClientTestModels.class.getResource("client/c2j/query/endpoint-tests.json").getFile()); + + C2jModels models = C2jModels + .builder() + .serviceModel(getServiceModel(serviceModel)) + .customizationConfig(getCustomizationConfig(customizationModel)) + .waitersModel(getWaiters(waitersModel)) + .endpointRuleSetModel(getEndpointRuleSet(endpointRuleSetModel)) + .endpointTestSuiteModel(getEndpointTestSuite(endpointTestsModel)) + .build(); + + return new IntermediateModelBuilder(models).build(); + } + + public static IntermediateModel granularAuthProvidersServiceModels() { + File serviceModel = new File(ClientTestModels.class.getResource("client/c2j/fine-grained-auth/service-2.json").getFile()); + File customizationModel = + new File(ClientTestModels.class.getResource("client/c2j/fine-grained-auth/customization.config") + .getFile()); + + C2jModels models = C2jModels + .builder() + .serviceModel(getServiceModel(serviceModel)) + .customizationConfig(getCustomizationConfig(customizationModel)) + .build(); + + return new IntermediateModelBuilder(models).build(); + } + + public static IntermediateModel granularAuthWithLegacyTraitServiceModels() { + File serviceModel = + new File(ClientTestModels.class.getResource("client/c2j/fine-grained-auth-legacy-trait/service-2.json").getFile()); + File customizationModel = + new File(ClientTestModels.class.getResource("client/c2j/fine-grained-auth-legacy-trait/customization.config") + .getFile()); + C2jModels models = C2jModels + .builder() + .serviceModel(getServiceModel(serviceModel)) + .customizationConfig(getCustomizationConfig(customizationModel)) + .build(); + + return new IntermediateModelBuilder(models).build(); + } + + public static IntermediateModel allOperationsWithAuthSameValueServiceModels() { + File serviceModel = + new File(ClientTestModels.class.getResource("client/c2j/all-ops-with-auth-same-value/service-2.json").getFile()); + File customizationModel = + new File(ClientTestModels.class.getResource("client/c2j/all-ops-with-auth-same-value/customization.config") + .getFile()); + C2jModels models = C2jModels + .builder() + .serviceModel(getServiceModel(serviceModel)) + .customizationConfig(getCustomizationConfig(customizationModel)) + .build(); + + return new IntermediateModelBuilder(models).build(); + } + + public static IntermediateModel allOperationsWithAuthDifferentValueServiceModels() { + File serviceModel = + new File(ClientTestModels.class.getResource("client/c2j/all-ops-with-auth-different-value/service-2.json").getFile()); + File customizationModel = + new File(ClientTestModels.class.getResource("client/c2j/all-ops-with-auth-different-value/customization.config") + .getFile()); + C2jModels models = C2jModels + .builder() + .serviceModel(getServiceModel(serviceModel)) + .customizationConfig(getCustomizationConfig(customizationModel)) + .build(); + + return new IntermediateModelBuilder(models).build(); + } + + public static IntermediateModel operationWithNoAuth() { + File serviceModel = + new File(ClientTestModels.class.getResource("client/c2j/ops-with-no-auth/service-2.json").getFile()); + File customizationModel = + new File(ClientTestModels.class.getResource("client/c2j/ops-with-no-auth/customization.config") + .getFile()); + C2jModels models = C2jModels + .builder() + .serviceModel(getServiceModel(serviceModel)) + .customizationConfig(getCustomizationConfig(customizationModel)) + .build(); + + return new IntermediateModelBuilder(models).build(); + } + + public static IntermediateModel serviceWithNoAuth() { + File serviceModel = + new File(ClientTestModels.class.getResource("client/c2j/service-with-no-auth/service-2.json").getFile()); + File customizationModel = + new File(ClientTestModels.class.getResource("client/c2j/service-with-no-auth/customization.config") + .getFile()); + C2jModels models = C2jModels + .builder() + .serviceModel(getServiceModel(serviceModel)) + .customizationConfig(getCustomizationConfig(customizationModel)) + .build(); + + return new IntermediateModelBuilder(models).build(); + } + + public static IntermediateModel serviceMiniS3() { + File serviceModel = + new File(ClientTestModels.class.getResource("client/c2j/mini-s3/service-2.json").getFile()); + File customizationModel = + new File(ClientTestModels.class.getResource("client/c2j/mini-s3/customization.config") + .getFile()); + C2jModels models = C2jModels + .builder() + .serviceModel(getServiceModel(serviceModel)) + .customizationConfig(getCustomizationConfig(customizationModel)) + .build(); + + return new IntermediateModelBuilder(models).build(); + } + public static IntermediateModel xmlServiceModels() { File serviceModel = new File(ClientTestModels.class.getResource("client/c2j/xml/service-2.json").getFile()); File customizationModel = new File(ClientTestModels.class.getResource("client/c2j/xml/customization.config").getFile()); 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 new file mode 100644 index 000000000000..15b061f9a835 --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java @@ -0,0 +1,244 @@ +/* + * 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 static org.hamcrest.MatcherAssert.assertThat; +import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.poet.ClassSpec; +import software.amazon.awssdk.codegen.poet.ClientTestModels; + +public class AuthSchemeSpecTest { + + @ParameterizedTest + @MethodSource("parameters") + void testCase(TestCase testCase) { + ClassSpec classSpec = testCase.classSpecProvider.apply(testCase.modelProvider.get()); + String expectedFileName = testCase.caseName + "-auth-scheme-" + testCase.outputFileSuffix + ".java"; + assertThat("Generated class must match " + expectedFileName, + classSpec, generatesTo(expectedFileName)); + } + + static List parameters() { + return Arrays.asList( + // query + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModels) + .classSpecProvider(AuthSchemeProviderSpec::new) + .caseName("query") + .outputFileSuffix("provider") + .build(), + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModels) + .classSpecProvider(AuthSchemeParamsSpec::new) + .caseName("query") + .outputFileSuffix("params") + .build(), + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModels) + .classSpecProvider(ModelBasedAuthSchemeProviderSpec::new) + .caseName("query") + .outputFileSuffix("default-provider") + .build(), + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModels) + .classSpecProvider(DefaultAuthSchemeParamsSpec::new) + .caseName("query") + .outputFileSuffix("default-params") + .build(), + // query-endpoint-auth-params + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModelsEndpointAuthParamsWithAllowList) + .classSpecProvider(AuthSchemeProviderSpec::new) + .caseName("query-endpoint-auth-params") + .outputFileSuffix("provider") + .build(), + // Endpoint based AuthScheme provider WITH allow list + // - Endpoint Provider + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModelsEndpointAuthParamsWithAllowList) + .classSpecProvider(EndpointBasedAuthSchemeProviderSpec::new) + .caseName("query-endpoint-auth-params") + .outputFileSuffix("endpoint-provider") + .build(), + // - Modeled provider + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModelsEndpointAuthParamsWithAllowList) + .classSpecProvider(ModelBasedAuthSchemeProviderSpec::new) + .caseName("query-endpoint-auth-params") + .outputFileSuffix("modeled-provider") + .build(), + // Endpoint based AuthScheme provider WITHOUT allow list + // - Endpoint Provider + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModelsEndpointAuthParamsWithoutAllowList) + .classSpecProvider(EndpointBasedAuthSchemeProviderSpec::new) + .caseName("query-endpoint-auth-params") + .outputFileSuffix("endpoint-provider-without-allowlist") + .build(), + // Auth scheme params from endpoint WITH allow list, MUST include ONLY the params in the allow list. + // - Interface + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModelsEndpointAuthParamsWithAllowList) + .classSpecProvider(AuthSchemeParamsSpec::new) + .caseName("query-endpoint-auth-params") + .outputFileSuffix("params-with-allowlist") + .build(), + // - Implementation + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModelsEndpointAuthParamsWithAllowList) + .classSpecProvider(DefaultAuthSchemeParamsSpec::new) + .caseName("query-endpoint-auth-params") + .outputFileSuffix("default-params-with-allowlist") + .build(), + // Auth scheme params from endpoint WITHOUT allow list, it MUST include all the endpoint params except for the + // default params. + // - Interface + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModelsEndpointAuthParamsWithoutAllowList) + .classSpecProvider(AuthSchemeParamsSpec::new) + .caseName("query-endpoint-auth-params") + .outputFileSuffix("params-without-allowlist") + .build(), + // - Implementation + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModelsEndpointAuthParamsWithoutAllowList) + .classSpecProvider(DefaultAuthSchemeParamsSpec::new) + .caseName("query-endpoint-auth-params") + .outputFileSuffix("default-params-without-allowlist") + .build(), + // Granular auth + TestCase.builder() + .modelProvider(ClientTestModels::granularAuthProvidersServiceModels) + .classSpecProvider(ModelBasedAuthSchemeProviderSpec::new) + .caseName("granular") + .outputFileSuffix("default-provider") + .build(), + // All operations with auth with the same values + TestCase.builder() + .modelProvider(ClientTestModels::allOperationsWithAuthSameValueServiceModels) + .classSpecProvider(ModelBasedAuthSchemeProviderSpec::new) + .caseName("all-ops-auth-same-value") + .outputFileSuffix("default-provider") + .build(), + // All operations with auth with different values + TestCase.builder() + .modelProvider(ClientTestModels::allOperationsWithAuthDifferentValueServiceModels) + .classSpecProvider(ModelBasedAuthSchemeProviderSpec::new) + .caseName("all-ops-auth-different-value") + .outputFileSuffix("default-provider") + .build(), + // Service with operations with auth none + TestCase.builder() + .modelProvider(ClientTestModels::operationWithNoAuth) + .classSpecProvider(ModelBasedAuthSchemeProviderSpec::new) + .caseName("ops-with-no-auth") + .outputFileSuffix("default-provider") + .build(), + // Service with signature version with the same value as S3 + TestCase.builder() + .modelProvider(ClientTestModels::serviceMiniS3) + .classSpecProvider(ModelBasedAuthSchemeProviderSpec::new) + .caseName("mini-s3") + .outputFileSuffix("default-provider") + .build(), + TestCase.builder() + .modelProvider(ClientTestModels::granularAuthWithLegacyTraitServiceModels) + .classSpecProvider(ModelBasedAuthSchemeProviderSpec::new) + .caseName("auth-with-legacy-trait") + .outputFileSuffix("default-provider") + .build(), + // Interceptors + // - Normal case + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModels) + .classSpecProvider(AuthSchemeInterceptorSpec::new) + .caseName("query") + .outputFileSuffix("interceptor") + .build(), + // - Endpoints based params with allow list + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModelsEndpointAuthParamsWithAllowList) + .classSpecProvider(AuthSchemeInterceptorSpec::new) + .caseName("query-endpoint-auth-params-with-allowlist") + .outputFileSuffix("interceptor") + .build(), + // - Endpoints based params without allow list + TestCase.builder() + .modelProvider(ClientTestModels::queryServiceModelsEndpointAuthParamsWithoutAllowList) + .classSpecProvider(AuthSchemeInterceptorSpec::new) + .caseName("query-endpoint-auth-params-without-allowlist") + .outputFileSuffix("interceptor") + .build() + ); + } + + static class TestCase { + private final Supplier modelProvider; + private final Function classSpecProvider; + private final String outputFileSuffix; + private final String caseName; + + TestCase(Builder builder) { + this.modelProvider = builder.modelProvider; + this.classSpecProvider = builder.classSpecProvider; + this.outputFileSuffix = builder.outputFileSuffix; + this.caseName = builder.caseName; + } + + static Builder builder() { + return new Builder(); + } + + static class Builder { + private Supplier modelProvider; + private Function classSpecProvider; + private String outputFileSuffix; + private String caseName; + + Builder modelProvider(Supplier modelProvider) { + this.modelProvider = modelProvider; + return this; + } + + Builder classSpecProvider(Function classSpecProvider) { + this.classSpecProvider = classSpecProvider; + return this; + } + + Builder outputFileSuffix(String outputFileSuffix) { + this.outputFileSuffix = outputFileSuffix; + return this; + } + + Builder caseName(String caseName) { + this.caseName = caseName; + return this; + } + + TestCase build() { + return new TestCase(this); + } + } + } +} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderClassTest.java new file mode 100644 index 000000000000..840ec2dbc13c --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderClassTest.java @@ -0,0 +1,39 @@ +/* + * 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.builder; + +import static software.amazon.awssdk.codegen.poet.ClientTestModels.composedClientJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.builder.BuilderClassTestUtils.validateGeneration; + +import org.junit.jupiter.api.Test; + +/** + * Validate AsyncClientBuilderClass generation. + */ +public class AsyncClientBuilderClassTest { + + @Test + public void asyncClientBuilderClass() { + validateGeneration(AsyncClientBuilderClass::new, restJsonServiceModels(), "test-async-client-builder-class.java"); + } + + @Test + public void asyncComposedClientBuilderClass() { + validateGeneration(AsyncClientBuilderClass::new, composedClientJsonServiceModels(), + "test-composed-async-client-builder-class.java"); + } +} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderInterfaceTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderInterfaceTest.java new file mode 100644 index 000000000000..63325fc21b25 --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderInterfaceTest.java @@ -0,0 +1,32 @@ +/* + * 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.builder; + +import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.builder.BuilderClassTestUtils.validateGeneration; + +import org.junit.jupiter.api.Test; + +/** + * Validate AsyncClientBuilderInterface generation. + */ +public class AsyncClientBuilderInterfaceTest { + + @Test + public void asyncClientBuilderInterface() { + validateGeneration(AsyncClientBuilderInterface::new, restJsonServiceModels(), "test-async-client-builder-interface.java"); + } +} 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 new file mode 100644 index 000000000000..ae6ea5a4df3c --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java @@ -0,0 +1,84 @@ +/* + * 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.builder; + +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.internalConfigModels; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.operationWithNoAuth; +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; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.serviceWithNoAuth; +import static software.amazon.awssdk.codegen.poet.builder.BuilderClassTestUtils.validateGeneration; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; + +/** + * Validate BaseClientBuilderClass generation. + */ +public class BaseClientBuilderClassTest { + @Test + public void baseClientBuilderClass() { + validateBaseClientBuilderClassGeneration(restJsonServiceModels(), "test-client-builder-class.java"); + } + + @Test + public void baseClientBuilderClassWithBearerAuth() { + validateBaseClientBuilderClassGeneration(bearerAuthServiceModels(), "test-bearer-auth-client-builder-class.java"); + } + + @Test + public void baseClientBuilderClassWithNoAuthOperation() { + validateBaseClientBuilderClassGeneration(operationWithNoAuth(), "test-no-auth-ops-client-builder-class.java"); + } + + @Test + public void baseClientBuilderClassWithNoAuthService() { + validateBaseClientBuilderClassGeneration(serviceWithNoAuth(), "test-no-auth-service-client-builder-class.java"); + } + + @Test + public void baseClientBuilderClassWithInternalUserAgent() { + validateBaseClientBuilderClassGeneration(internalConfigModels(), "test-client-builder-internal-defaults-class.java"); + } + + @Test + public void baseQueryClientBuilderClass() { + validateBaseClientBuilderClassGeneration(queryServiceModels(), "test-query-client-builder-class.java"); + } + + @Test + public void baseClientBuilderClassWithEndpointsAuthParams() { + validateBaseClientBuilderClassGeneration(queryServiceModelsEndpointAuthParamsWithAllowList(), + "test-client-builder-endpoints-auth-params.java"); + } + + @Test + public void syncComposedDefaultClientBuilderClass() { + validateBaseClientBuilderClassGeneration(composedClientJsonServiceModels(), + "test-composed-sync-default-client-builder.java"); + } + + private void validateBaseClientBuilderClassGeneration(IntermediateModel model, String expectedClassName) { + model.getCustomizationConfig().setUseSraAuth(false); + validateGeneration(BaseClientBuilderClass::new, model, expectedClassName); + + model.getCustomizationConfig().setUseSraAuth(true); + validateGeneration(BaseClientBuilderClass::new, model, "sra/" + expectedClassName); + } +} 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 new file mode 100644 index 000000000000..3839cb7bc3de --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterfaceTest.java @@ -0,0 +1,49 @@ +/* + * 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.builder; + +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.restJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.builder.BuilderClassTestUtils.validateGeneration; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; + +/** + * Validate BaseClientBuilderInterface generation. + */ +public class BaseClientBuilderInterfaceTest { + @Test + public void baseClientBuilderInterface() { + validateBaseClientBuilderInterfaceGeneration(restJsonServiceModels(), "test-client-builder-interface.java"); + } + + @Test + public void baseClientBuilderInterfaceWithBearerAuth() { + validateBaseClientBuilderInterfaceGeneration(bearerAuthServiceModels(), "test-bearer-auth-client-builder-interface.java"); + } + + @Test + public void syncHasCrossRegionAccessEnabledPropertyBuilderClass() { + validateBaseClientBuilderInterfaceGeneration(composedClientJsonServiceModels(), + "test-customcontextparams-sync-client-builder-class.java"); + } + + private void validateBaseClientBuilderInterfaceGeneration(IntermediateModel model, String expectedClassName) { + validateGeneration(BaseClientBuilderInterface::new, model, expectedClassName); + } +} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BuilderClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BuilderClassTest.java deleted file mode 100644 index a1fcfa98150e..000000000000 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BuilderClassTest.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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.builder; - -import static org.hamcrest.MatcherAssert.assertThat; -import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo; - -import java.util.function.Function; -import org.junit.jupiter.api.Test; -import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; -import software.amazon.awssdk.codegen.poet.ClassSpec; -import software.amazon.awssdk.codegen.poet.ClientTestModels; - -/** - * Validate client builder generation. - */ -public class BuilderClassTest { - @Test - public void baseClientBuilderInterface() throws Exception { - validateGeneration(BaseClientBuilderInterface::new, "test-client-builder-interface.java"); - } - - @Test - public void baseClientBuilderClass() throws Exception { - validateGeneration(BaseClientBuilderClass::new, "test-client-builder-class.java"); - } - - @Test - public void baseClientBuilderInterfaceWithBearerAuthClass() throws Exception { - validateBearerAuthGeneration(BaseClientBuilderInterface::new, "test-bearer-auth-client-builder-interface.java"); - } - - @Test - public void baseClientBuilderWithBearerAuthClass() throws Exception { - validateBearerAuthGeneration(BaseClientBuilderClass::new, "test-bearer-auth-client-builder-class.java"); - } - - @Test - public void baseClientBuilderClassWithInternalUserAgent() throws Exception { - assertThat(new BaseClientBuilderClass(ClientTestModels.internalConfigModels()), generatesTo("test-client-builder-internal-defaults-class.java")); - } - - @Test - public void baseQueryClientBuilderClass() throws Exception { - validateQueryGeneration(BaseClientBuilderClass::new, "test-query-client-builder-class.java"); - } - - @Test - public void syncClientBuilderInterface() throws Exception { - validateGeneration(SyncClientBuilderInterface::new, "test-sync-client-builder-interface.java"); - } - - @Test - public void syncClientBuilderClass() throws Exception { - validateGeneration(SyncClientBuilderClass::new, "test-sync-client-builder-class.java"); - } - - @Test - public void syncComposedClientBuilderClass() throws Exception { - validateComposedClientGeneration(SyncClientBuilderClass::new, "test-composed-sync-client-builder-class.java"); - } - - @Test - public void syncComposedDefaultClientBuilderClass() throws Exception { - validateComposedClientGeneration(BaseClientBuilderClass::new, "test-composed-sync-default-client-builder.java"); - } - - @Test - public void syncHasCrossRegionAccessEnabledPropertyBuilderClass() throws Exception { - validateComposedClientGeneration(BaseClientBuilderInterface::new, "test-customcontextparams-sync-client-builder-class.java"); - } - - - @Test - public void asyncClientBuilderInterface() throws Exception { - validateGeneration(AsyncClientBuilderInterface::new, "test-async-client-builder-interface.java"); - } - - @Test - public void asyncClientBuilderClass() throws Exception { - validateGeneration(AsyncClientBuilderClass::new, "test-async-client-builder-class.java"); - } - - @Test - public void asyncComposedClientBuilderClass() throws Exception { - validateComposedClientGeneration(AsyncClientBuilderClass::new, "test-composed-async-client-builder-class.java"); - } - - private void validateGeneration(Function generatorConstructor, String expectedClassName) { - assertThat(generatorConstructor.apply(ClientTestModels.restJsonServiceModels()), generatesTo(expectedClassName)); - } - - private void validateComposedClientGeneration(Function generatorConstructor, - String expectedClassName) { - assertThat(generatorConstructor.apply(ClientTestModels.composedClientJsonServiceModels()), generatesTo(expectedClassName)); - } - - private void validateBearerAuthGeneration(Function generatorConstructor, - String expectedClassName) { - assertThat(generatorConstructor.apply(ClientTestModels.bearerAuthServiceModels()), generatesTo(expectedClassName)); - } - - private void validateQueryGeneration(Function generatorConstructor, String expectedClassName) { - assertThat(generatorConstructor.apply(ClientTestModels.queryServiceModels()), generatesTo(expectedClassName)); - } -} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BuilderClassTestUtils.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BuilderClassTestUtils.java new file mode 100644 index 000000000000..7aaf463d7797 --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BuilderClassTestUtils.java @@ -0,0 +1,31 @@ +/* + * 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.builder; + +import static org.hamcrest.MatcherAssert.assertThat; +import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo; + +import java.util.function.Function; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.poet.ClassSpec; + +public class BuilderClassTestUtils { + static void validateGeneration(Function generatorConstructor, + IntermediateModel model, + String expectedClassName) { + assertThat(generatorConstructor.apply(model), generatesTo(expectedClassName)); + } +} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderClassTest.java new file mode 100644 index 000000000000..fd95f40c4975 --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderClassTest.java @@ -0,0 +1,38 @@ +/* + * 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.builder; + +import static software.amazon.awssdk.codegen.poet.ClientTestModels.composedClientJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.builder.BuilderClassTestUtils.validateGeneration; + +import org.junit.jupiter.api.Test; + +/** + * Validate SyncClientBuilderClass generation. + */ +public class SyncClientBuilderClassTest { + @Test + public void syncClientBuilderClass() { + validateGeneration(SyncClientBuilderClass::new, restJsonServiceModels(), "test-sync-client-builder-class.java"); + } + + @Test + public void syncComposedClientBuilderClass() { + validateGeneration(SyncClientBuilderClass::new, composedClientJsonServiceModels(), + "test-composed-sync-client-builder-class.java"); + } +} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderInterfaceTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderInterfaceTest.java new file mode 100644 index 000000000000..5289c71ecfe4 --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderInterfaceTest.java @@ -0,0 +1,31 @@ +/* + * 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.builder; + +import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.builder.BuilderClassTestUtils.validateGeneration; + +import org.junit.jupiter.api.Test; + +/** + * Validate SyncClientBuilderInterface generation. + */ +public class SyncClientBuilderInterfaceTest { + @Test + public void syncClientBuilderInterface() { + validateGeneration(SyncClientBuilderInterface::new, restJsonServiceModels(), "test-sync-client-builder-interface.java"); + } +} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClassTest.java new file mode 100644 index 000000000000..d2c177051493 --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClassTest.java @@ -0,0 +1,95 @@ +/* + * 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.client; + +import static org.hamcrest.MatcherAssert.assertThat; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.awsJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.awsQueryCompatibleJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.customContentTypeModels; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.endpointDiscoveryModels; +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.ClientTestModels.xmlServiceModels; +import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo; + +import org.junit.Test; +import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; + +public class AsyncClientClassTest { + @Test + public void asyncClientClassRestJson() { + AsyncClientClass asyncClientClass = createAsyncClientClass(restJsonServiceModels(), false); + assertThat(asyncClientClass, generatesTo("test-json-async-client-class.java")); + + AsyncClientClass sraAsyncClientClass = createAsyncClientClass(restJsonServiceModels(), true); + assertThat(sraAsyncClientClass, generatesTo("sra/test-json-async-client-class.java")); + } + + @Test + public void asyncClientClassQuery() { + AsyncClientClass asyncClientClass = createAsyncClientClass(queryServiceModels(), false); + assertThat(asyncClientClass, generatesTo("test-query-async-client-class.java")); + + AsyncClientClass sraAsyncClientClass = createAsyncClientClass(queryServiceModels(), true); + assertThat(sraAsyncClientClass, generatesTo("sra/test-query-async-client-class.java")); + } + + @Test + public void asyncClientClassAwsJson() { + AsyncClientClass asyncClientClass = createAsyncClientClass(awsJsonServiceModels(), false); + assertThat(asyncClientClass, generatesTo("test-aws-json-async-client-class.java")); + + AsyncClientClass sraAsyncClientClass = createAsyncClientClass(awsJsonServiceModels(), true); + assertThat(sraAsyncClientClass, generatesTo("sra/test-aws-json-async-client-class.java")); + } + + @Test + public void asyncClientClassAwsQueryCompatibleJson() { + AsyncClientClass asyncClientClass = createAsyncClientClass(awsQueryCompatibleJsonServiceModels()); + assertThat(asyncClientClass, generatesTo("test-aws-query-compatible-json-async-client-class.java")); + } + + @Test + public void asyncClientClassXml() { + AsyncClientClass asyncClientClass = createAsyncClientClass(xmlServiceModels(), false); + assertThat(asyncClientClass, generatesTo("test-xml-async-client-class.java")); + + AsyncClientClass sraAsyncClientClass = createAsyncClientClass(xmlServiceModels(), true); + assertThat(sraAsyncClientClass, generatesTo("sra/test-xml-async-client-class.java")); + } + + @Test + public void asyncClientEndpointDiscovery() { + AsyncClientClass asyncClientEndpointDiscovery = createAsyncClientClass(endpointDiscoveryModels()); + assertThat(asyncClientEndpointDiscovery, generatesTo("test-endpoint-discovery-async.java")); + } + + @Test + public void asyncClientCustomServiceMetaData() { + AsyncClientClass asyncClientCustomServiceMetaData = createAsyncClientClass(customContentTypeModels()); + assertThat(asyncClientCustomServiceMetaData, generatesTo("test-customservicemetadata-async.java")); + } + + private AsyncClientClass createAsyncClientClass(IntermediateModel model) { + return new AsyncClientClass(GeneratorTaskParams.create(model, "sources/", "tests/", "resources/")); + } + + private AsyncClientClass createAsyncClientClass(IntermediateModel model, boolean useSraAuth) { + model.getCustomizationConfig().setUseSraAuth(useSraAuth); + return createAsyncClientClass(model); + } +} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/rules/EndpointAuthSchemeInterceptorClassSpecTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterfaceTest.java similarity index 61% rename from codegen/src/test/java/software/amazon/awssdk/codegen/poet/rules/EndpointAuthSchemeInterceptorClassSpecTest.java rename to codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterfaceTest.java index 99d4a3b4bf3e..2ab2aa389716 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/rules/EndpointAuthSchemeInterceptorClassSpecTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterfaceTest.java @@ -13,20 +13,19 @@ * permissions and limitations under the License. */ -package software.amazon.awssdk.codegen.poet.rules; +package software.amazon.awssdk.codegen.poet.client; import static org.hamcrest.MatcherAssert.assertThat; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels; import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo; -import org.junit.jupiter.api.Test; +import org.junit.Test; import software.amazon.awssdk.codegen.poet.ClassSpec; -import software.amazon.awssdk.codegen.poet.ClientTestModels; - -public class EndpointAuthSchemeInterceptorClassSpecTest { +public class AsyncClientInterfaceTest { @Test - public void endpointAuthSchemeInterceptor() { - ClassSpec endpointAuthSchemeInterceptor = new EndpointAuthSchemeInterceptorClassSpec(ClientTestModels.queryServiceModels()); - assertThat(endpointAuthSchemeInterceptor, generatesTo("endpoint-auth-scheme-interceptor.java")); + public void asyncClientInterface() { + ClassSpec asyncClientInterface = new AsyncClientInterface(restJsonServiceModels()); + assertThat(asyncClientInterface, generatesTo("test-json-async-client-interface.java")); } } diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClassTest.java new file mode 100644 index 000000000000..a12ab3ac659c --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClassTest.java @@ -0,0 +1,31 @@ +/* + * 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.client; + +import static org.hamcrest.MatcherAssert.assertThat; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo; + +import org.junit.Test; + +public class DelegatingAsyncClientClassTest { + @Test + public void delegatingAsyncClientClass() { + DelegatingAsyncClientClass asyncClientDecoratorAbstractClass = + new DelegatingAsyncClientClass(restJsonServiceModels()); + assertThat(asyncClientDecoratorAbstractClass, generatesTo("test-abstract-async-client-class.java")); + } +} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingSyncClientClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingSyncClientClassTest.java new file mode 100644 index 000000000000..bfa9f0005dfa --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingSyncClientClassTest.java @@ -0,0 +1,31 @@ +/* + * 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.client; + +import static org.hamcrest.MatcherAssert.assertThat; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo; + +import org.junit.Test; + +public class DelegatingSyncClientClassTest { + @Test + public void delegatingSyncClientClass() { + DelegatingSyncClientClass syncClientDecoratorAbstractClass = + new DelegatingSyncClientClass(restJsonServiceModels()); + assertThat(syncClientDecoratorAbstractClass, generatesTo("test-abstract-sync-client-class.java")); + } +} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/PoetClientFunctionalTests.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/PoetClientFunctionalTests.java deleted file mode 100644 index c23890c2d499..000000000000 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/PoetClientFunctionalTests.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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.client; - -import static org.hamcrest.MatcherAssert.assertThat; -import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo; - -import org.junit.Test; -import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams; -import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; -import software.amazon.awssdk.codegen.poet.ClassSpec; -import software.amazon.awssdk.codegen.poet.ClientTestModels; - -public class PoetClientFunctionalTests { - @Test - public void asyncClientClassRestJson() throws Exception { - AsyncClientClass asyncClientClass = createAsyncClientClass(ClientTestModels.restJsonServiceModels()); - assertThat(asyncClientClass, generatesTo("test-json-async-client-class.java")); - } - - @Test - public void asyncClientInterface() throws Exception { - ClassSpec asyncClientInterface = new AsyncClientInterface(ClientTestModels.restJsonServiceModels()); - assertThat(asyncClientInterface, generatesTo("test-json-async-client-interface.java")); - } - - @Test - public void delegatingAsyncClientClass() throws Exception { - DelegatingAsyncClientClass asyncClientDecoratorAbstractClass = - new DelegatingAsyncClientClass(ClientTestModels.restJsonServiceModels()); - assertThat(asyncClientDecoratorAbstractClass, generatesTo("test-abstract-async-client-class.java")); - } - - @Test - public void syncClientInterface() throws Exception { - ClassSpec syncClientInterface = new SyncClientInterface(ClientTestModels.restJsonServiceModels()); - assertThat(syncClientInterface, generatesTo("test-json-client-interface.java")); - } - - @Test - public void delegatingSyncClientClass() throws Exception { - DelegatingSyncClientClass syncClientDecoratorAbstractClass = - new DelegatingSyncClientClass(ClientTestModels.restJsonServiceModels()); - assertThat(syncClientDecoratorAbstractClass, generatesTo("test-abstract-sync-client-class.java")); - } - - @Test - public void syncClientClassRestJson() throws Exception { - SyncClientClass syncClientClass = createSyncClientClass(ClientTestModels.restJsonServiceModels()); - assertThat(syncClientClass, generatesTo("test-json-client-class.java")); - } - - @Test - public void syncClientClassQuery() throws Exception { - SyncClientClass syncClientClass = createSyncClientClass(ClientTestModels.queryServiceModels()); - assertThat(syncClientClass, generatesTo("test-query-client-class.java")); - } - - @Test - public void asyncClientClassQuery() throws Exception { - AsyncClientClass syncClientClass = createAsyncClientClass(ClientTestModels.queryServiceModels()); - assertThat(syncClientClass, generatesTo("test-query-async-client-class.java")); - } - - @Test - public void asyncClientClassAwsJson() throws Exception { - AsyncClientClass asyncClientClass = createAsyncClientClass(ClientTestModels.awsJsonServiceModels()); - assertThat(asyncClientClass, generatesTo("test-aws-json-async-client-class.java")); - } - - @Test - public void asyncClientClassAwsQueryCompatibleJson() throws Exception { - AsyncClientClass asyncClientClass = createAsyncClientClass(ClientTestModels.awsQueryCompatibleJsonServiceModels()); - assertThat(asyncClientClass, generatesTo("test-aws-query-compatible-json-async-client-class.java")); - } - - @Test - public void syncClientClassAwsQueryCompatibleJson() throws Exception { - SyncClientClass syncClientClass = createSyncClientClass(ClientTestModels.awsQueryCompatibleJsonServiceModels()); - assertThat(syncClientClass, generatesTo("test-aws-query-compatible-json-sync-client-class.java")); - } - - @Test - public void syncClientClassXml() throws Exception { - SyncClientClass syncClientClass = createSyncClientClass(ClientTestModels.xmlServiceModels()); - assertThat(syncClientClass, generatesTo("test-xml-client-class.java")); - } - - @Test - public void asyncClientClassXml() throws Exception { - AsyncClientClass syncClientClass = createAsyncClientClass(ClientTestModels.xmlServiceModels()); - assertThat(syncClientClass, generatesTo("test-xml-async-client-class.java")); - } - - private SyncClientClass createSyncClientClass(IntermediateModel model) { - return new SyncClientClass(GeneratorTaskParams.create(model, "sources/", "tests/", "resources/")); - } - - private AsyncClientClass createAsyncClientClass(IntermediateModel model) { - return new AsyncClientClass(GeneratorTaskParams.create(model, "sources/", "tests/", "resources/")); - } - - @Test - public void syncClientEndpointDiscovery() throws Exception { - ClassSpec syncClientEndpointDiscovery = createSyncClientClass(ClientTestModels.endpointDiscoveryModels()); - assertThat(syncClientEndpointDiscovery, generatesTo("test-endpoint-discovery-sync.java")); - } - - @Test - public void asyncClientEndpointDiscovery() throws Exception { - ClassSpec asyncClientEndpointDiscovery = new AsyncClientClass( - GeneratorTaskParams.create(ClientTestModels.endpointDiscoveryModels(), "sources/", "tests/", "resources/")); - assertThat(asyncClientEndpointDiscovery, generatesTo("test-endpoint-discovery-async.java")); - } - - @Test - public void asyncClientCustomServiceMetaData() throws Exception { - ClassSpec asyncClientCustomServiceMetaData = new AsyncClientClass( - GeneratorTaskParams.create(ClientTestModels.customContentTypeModels(), "sources/", "tests/", "resources/")); - assertThat(asyncClientCustomServiceMetaData, generatesTo("test-customservicemetadata-async.java")); - } - - @Test - public void syncClientCustomServiceMetaData() throws Exception { - ClassSpec syncClientCustomServiceMetaData = createSyncClientClass(ClientTestModels.customContentTypeModels()); - assertThat(syncClientCustomServiceMetaData, generatesTo("test-customservicemetadata-sync.java")); - } -} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/SyncClientClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/SyncClientClassTest.java new file mode 100644 index 000000000000..8f40dbd3a7d4 --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/SyncClientClassTest.java @@ -0,0 +1,86 @@ +/* + * 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.client; + +import static org.hamcrest.MatcherAssert.assertThat; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.awsQueryCompatibleJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.customContentTypeModels; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.endpointDiscoveryModels; +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.ClientTestModels.xmlServiceModels; +import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo; + +import org.junit.Test; +import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.poet.ClassSpec; + +public class SyncClientClassTest { + @Test + public void syncClientClassRestJson() { + SyncClientClass syncClientClass = createSyncClientClass(restJsonServiceModels(), false); + assertThat(syncClientClass, generatesTo("test-json-client-class.java")); + + SyncClientClass sraSyncClientClass = createSyncClientClass(restJsonServiceModels(), true); + assertThat(sraSyncClientClass, generatesTo("sra/test-json-client-class.java")); + } + + @Test + public void syncClientClassQuery() { + SyncClientClass syncClientClass = createSyncClientClass(queryServiceModels(), false); + assertThat(syncClientClass, generatesTo("test-query-client-class.java")); + + SyncClientClass sraSyncClientClass = createSyncClientClass(queryServiceModels(), true); + assertThat(sraSyncClientClass, generatesTo("sra/test-query-client-class.java")); + } + + @Test + public void syncClientClassAwsQueryCompatibleJson() { + SyncClientClass syncClientClass = createSyncClientClass(awsQueryCompatibleJsonServiceModels()); + assertThat(syncClientClass, generatesTo("test-aws-query-compatible-json-sync-client-class.java")); + } + + @Test + public void syncClientClassXml() { + SyncClientClass syncClientClass = createSyncClientClass(xmlServiceModels(), false); + assertThat(syncClientClass, generatesTo("test-xml-client-class.java")); + + SyncClientClass sraSyncClientClass = createSyncClientClass(xmlServiceModels(), true); + assertThat(sraSyncClientClass, generatesTo("sra/test-xml-client-class.java")); + } + + @Test + public void syncClientEndpointDiscovery() { + ClassSpec syncClientEndpointDiscovery = createSyncClientClass(endpointDiscoveryModels()); + assertThat(syncClientEndpointDiscovery, generatesTo("test-endpoint-discovery-sync.java")); + } + + @Test + public void syncClientCustomServiceMetaData() { + ClassSpec syncClientCustomServiceMetaData = createSyncClientClass(customContentTypeModels()); + assertThat(syncClientCustomServiceMetaData, generatesTo("test-customservicemetadata-sync.java")); + } + + private SyncClientClass createSyncClientClass(IntermediateModel model) { + return new SyncClientClass(GeneratorTaskParams.create(model, "sources/", "tests/", "resources/")); + } + + private SyncClientClass createSyncClientClass(IntermediateModel model, boolean useSraAuth) { + model.getCustomizationConfig().setUseSraAuth(useSraAuth); + return createSyncClientClass(model); + } +} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/SyncClientInterfaceTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/SyncClientInterfaceTest.java new file mode 100644 index 000000000000..2e074970cb34 --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/SyncClientInterfaceTest.java @@ -0,0 +1,31 @@ +/* + * 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.client; + +import static org.hamcrest.MatcherAssert.assertThat; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo; + +import org.junit.Test; +import software.amazon.awssdk.codegen.poet.ClassSpec; + +public class SyncClientInterfaceTest { + @Test + public void syncClientInterface() { + ClassSpec syncClientInterface = new SyncClientInterface(restJsonServiceModels()); + assertThat(syncClientInterface, generatesTo("test-json-client-interface.java")); + } +} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationBuilderSpecTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationBuilderSpecTest.java new file mode 100644 index 000000000000..ea30f4f02e75 --- /dev/null +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationBuilderSpecTest.java @@ -0,0 +1,55 @@ +/* + * 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.model; + +import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo; + +import java.io.File; +import java.io.IOException; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.codegen.C2jModels; +import software.amazon.awssdk.codegen.IntermediateModelBuilder; +import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.model.service.ServiceModel; +import software.amazon.awssdk.codegen.utils.ModelLoaderUtils; + +public class ServiceClientConfigurationBuilderSpecTest { + private static IntermediateModel intermediateModel; + + @BeforeAll + public static void setUp() throws IOException { + File serviceModelFile = new File(AwsModelSpecTest.class.getResource("service-2.json").getFile()); + File customizationConfigFile = new File(AwsModelSpecTest.class + .getResource("customization.config") + .getFile()); + + intermediateModel = new IntermediateModelBuilder( + C2jModels.builder() + .serviceModel(ModelLoaderUtils.loadModel(ServiceModel.class, serviceModelFile)) + .customizationConfig(ModelLoaderUtils.loadModel(CustomizationConfig.class, customizationConfigFile)) + .build()) + .build(); + } + + @Test + public void testGeneration() { + ServiceClientConfigurationBuilderClass spec = new ServiceClientConfigurationBuilderClass(intermediateModel); + MatcherAssert.assertThat(spec, generatesTo("serviceclientconfiguration-builder.java")); + } +} diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpecTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpecTest.java index 8ef78e35fb52..f3cf566962d1 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpecTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpecTest.java @@ -19,13 +19,27 @@ import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo; import org.junit.jupiter.api.Test; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; import software.amazon.awssdk.codegen.poet.ClassSpec; import software.amazon.awssdk.codegen.poet.ClientTestModels; public class EndpointResolverInterceptorSpecTest { @Test public void endpointResolverInterceptorClass() { - ClassSpec endpointProviderInterceptor = new EndpointResolverInterceptorSpec(ClientTestModels.queryServiceModels()); + ClassSpec endpointProviderInterceptor = new EndpointResolverInterceptorSpec(getModel(true)); assertThat(endpointProviderInterceptor, generatesTo("endpoint-resolve-interceptor.java")); } + + // TODO(post-sra-identity-auth): This can be deleted when useSraAuth is removed + @Test + public void endpointResolverInterceptorClass_preSra() { + ClassSpec endpointProviderInterceptor = new EndpointResolverInterceptorSpec(getModel(false)); + assertThat(endpointProviderInterceptor, generatesTo("endpoint-resolve-interceptor-preSra.java")); + } + + private static IntermediateModel getModel(boolean useSraAuth) { + IntermediateModel model = ClientTestModels.queryServiceModels(); + model.getCustomizationConfig().setUseSraAuth(useSraAuth); + return model; + } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/all-ops-auth-different-value-auth-scheme-default-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/all-ops-auth-different-value-auth-scheme-default-provider.java new file mode 100644 index 000000000000..03fed694330c --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/all-ops-auth-different-value-auth-scheme-default-provider.java @@ -0,0 +1,67 @@ +/* + * 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.database.auth.scheme.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeParams; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultDatabaseAuthSchemeProvider implements DatabaseAuthSchemeProvider { + private static final DefaultDatabaseAuthSchemeProvider DEFAULT = new DefaultDatabaseAuthSchemeProvider(); + + private DefaultDatabaseAuthSchemeProvider() { + } + + public static DefaultDatabaseAuthSchemeProvider create() { + return DEFAULT; + } + + @Override + public List resolveAuthScheme(DatabaseAuthSchemeParams params) { + List options = new ArrayList<>(); + switch (params.operation()) { + case "DeleteRow": + options.add(AuthSchemeOption.builder().schemeId("smithy.api#httpBearerAuth").build()); + 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 "GetRow": + options.add(AuthSchemeOption.builder().schemeId("smithy.api#httpBearerAuth").build()); + break; + case "PutRow": + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()).build()); + break; + default: + 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("smithy.api#httpBearerAuth").build()); + break; + } + return Collections.unmodifiableList(options); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/all-ops-auth-same-value-auth-scheme-default-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/all-ops-auth-same-value-auth-scheme-default-provider.java new file mode 100644 index 000000000000..b1ced5712b19 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/all-ops-auth-same-value-auth-scheme-default-provider.java @@ -0,0 +1,49 @@ +/* + * 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.database.auth.scheme.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeParams; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultDatabaseAuthSchemeProvider implements DatabaseAuthSchemeProvider { + private static final DefaultDatabaseAuthSchemeProvider DEFAULT = new DefaultDatabaseAuthSchemeProvider(); + + private DefaultDatabaseAuthSchemeProvider() { + } + + public static DefaultDatabaseAuthSchemeProvider create() { + return DEFAULT; + } + + @Override + public List resolveAuthScheme(DatabaseAuthSchemeParams params) { + List options = new ArrayList<>(); + 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("smithy.api#httpBearerAuth").build()); + return Collections.unmodifiableList(options); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/auth-scheme-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/auth-scheme-params.java new file mode 100644 index 000000000000..9bbc556dbe27 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/auth-scheme-params.java @@ -0,0 +1,67 @@ +/* + * 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.query.auth.scheme; + +import java.util.Optional; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.services.query.auth.scheme.internal.DefaultQueryAuthSchemeParams; + +/** + * The parameters object used to resolve the auth schemes for the Query service. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkPublicApi +public interface QueryAuthSchemeParams { + + /** + * Get a new builder for creating a {@link QueryAuthSchemeParams}. + */ + static Builder builder() { + return DefaultQueryAuthSchemeParams.builder(); + } + + /** + * Returns the operation for which to resolve the auth scheme. + */ + String operation(); + + /** + * Returns the region. The region is optional. The region parameter may be used with "aws.auth#sigv4" auth scheme. + * By default, the region will be empty. + */ + Optional region(); + + /** + * A builder for a {@link QueryAuthSchemeParams}. + */ + interface Builder { + /** + * 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(String region); + + /** + * Returns a {@link QueryAuthSchemeParams} object that is created from the properties that have been set on the builder. + */ + QueryAuthSchemeParams build(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/auth-scheme-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/auth-scheme-provider.java new file mode 100644 index 000000000000..a4f84dc2665a --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/auth-scheme-provider.java @@ -0,0 +1,53 @@ +/* + * 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.query.auth.scheme; + +import java.util.List; +import java.util.function.Consumer; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider; +import software.amazon.awssdk.services.query.auth.scheme.internal.DefaultQueryAuthSchemeProvider; + +/** + * An auth scheme provider for Query service. The auth scheme provider takes a set of parameters using + * {@link QueryAuthSchemeParams}, and resolves a list of {@link AuthSchemeOption} based on the given parameters. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkPublicApi +public interface QueryAuthSchemeProvider extends AuthSchemeProvider { + /** + * Resolve the auth schemes based on the given set of parameters. + */ + List resolveAuthScheme(QueryAuthSchemeParams authSchemeParams); + + /** + * Resolve the auth schemes based on the given set of parameters. + */ + default List resolveAuthScheme(Consumer consumer) { + QueryAuthSchemeParams.Builder builder = QueryAuthSchemeParams.builder(); + consumer.accept(builder); + return resolveAuthScheme(builder.build()); + } + + /** + * Get the default auth scheme provider. + */ + static QueryAuthSchemeProvider defaultProvider() { + return DefaultQueryAuthSchemeProvider.create(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/auth-with-legacy-trait-auth-scheme-default-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/auth-with-legacy-trait-auth-scheme-default-provider.java new file mode 100644 index 000000000000..1547ed4f412b --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/auth-with-legacy-trait-auth-scheme-default-provider.java @@ -0,0 +1,57 @@ +/* + * 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.authwithlegacytrait.auth.scheme.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.authwithlegacytrait.auth.scheme.AuthWithLegacyTraitAuthSchemeParams; +import software.amazon.awssdk.services.authwithlegacytrait.auth.scheme.AuthWithLegacyTraitAuthSchemeProvider; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultAuthWithLegacyTraitAuthSchemeProvider implements AuthWithLegacyTraitAuthSchemeProvider { + private static final DefaultAuthWithLegacyTraitAuthSchemeProvider DEFAULT = new DefaultAuthWithLegacyTraitAuthSchemeProvider(); + + private DefaultAuthWithLegacyTraitAuthSchemeProvider() { + } + + public static DefaultAuthWithLegacyTraitAuthSchemeProvider create() { + return DEFAULT; + } + + @Override + public List resolveAuthScheme(AuthWithLegacyTraitAuthSchemeParams params) { + List options = new ArrayList<>(); + switch (params.operation()) { + case "OperationWithBearerAuth": + options.add(AuthSchemeOption.builder().schemeId("smithy.api#httpBearerAuth").build()); + break; + case "OperationWithNoAuth": + options.add(AuthSchemeOption.builder().schemeId("smithy.api#noAuth").build()); + break; + default: + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "authwithlegacytraitservice") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()).build()); + break; + } + return Collections.unmodifiableList(options); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/default-auth-scheme-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/default-auth-scheme-params.java new file mode 100644 index 000000000000..d6ae018038a8 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/default-auth-scheme-params.java @@ -0,0 +1,70 @@ +/* + * 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.query.auth.scheme.internal; + +import java.util.Optional; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeParams; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultQueryAuthSchemeParams implements QueryAuthSchemeParams { + private final String operation; + private final String region; + + private DefaultQueryAuthSchemeParams(Builder builder) { + this.operation = Validate.paramNotNull(builder.operation, "operation"); + this.region = builder.region; + } + + public static QueryAuthSchemeParams.Builder builder() { + return new Builder(); + } + + @Override + public String operation() { + return operation; + } + + @Override + public Optional region() { + return region == null ? Optional.empty() : Optional.of(region); + } + + private static final class Builder implements QueryAuthSchemeParams.Builder { + private String operation; + private String region; + + @Override + public Builder operation(String operation) { + this.operation = operation; + return this; + } + + @Override + public Builder region(String region) { + this.region = region; + return this; + } + + @Override + public QueryAuthSchemeParams build() { + return new DefaultQueryAuthSchemeParams(this); + } + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/default-auth-scheme-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/default-auth-scheme-provider.java new file mode 100644 index 000000000000..d1e365854ac3 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/default-auth-scheme-provider.java @@ -0,0 +1,43 @@ +/* + * 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.query.auth.scheme.internal; + +import java.util.ArrayList; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeParams; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultQueryAuthSchemeProvider implements QueryAuthSchemeProvider { + + private static final DefaultQueryAuthSchemeProvider DEFAULT = new DefaultQueryAuthSchemeProvider(); + + private DefaultQueryAuthSchemeProvider() { + } + + public static DefaultQueryAuthSchemeProvider create() { + return DEFAULT; + } + + @Override + public List resolveAuthScheme(QueryAuthSchemeParams authSchemeParams) { + return new ArrayList<>(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/granular-auth-scheme-default-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/granular-auth-scheme-default-provider.java new file mode 100644 index 000000000000..33c084fbc7d1 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/granular-auth-scheme-default-provider.java @@ -0,0 +1,74 @@ +/* + * 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.database.auth.scheme.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeParams; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultDatabaseAuthSchemeProvider implements DatabaseAuthSchemeProvider { + private static final DefaultDatabaseAuthSchemeProvider DEFAULT = new DefaultDatabaseAuthSchemeProvider(); + + private DefaultDatabaseAuthSchemeProvider() { + } + + public static DefaultDatabaseAuthSchemeProvider create() { + return DEFAULT; + } + + @Override + public List resolveAuthScheme(DatabaseAuthSchemeParams params) { + List options = new ArrayList<>(); + switch (params.operation()) { + case "GetRow": + case "GetRowV2": + 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("smithy.api#httpBearerAuth").build()); + break; + case "ListRows": + options.add(AuthSchemeOption.builder().schemeId("smithy.api#httpBearerAuth").build()); + break; + case "PutRow": + 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 "WriteRowResponse": + 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; + default: + options.add(AuthSchemeOption.builder().schemeId("smithy.api#httpBearerAuth").build()); + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()).build()); + break; + } + return Collections.unmodifiableList(options); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/mini-s3-auth-scheme-default-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/mini-s3-auth-scheme-default-provider.java new file mode 100644 index 000000000000..dd7224fd84c7 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/mini-s3-auth-scheme-default-provider.java @@ -0,0 +1,50 @@ +/* + * 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.minis3.auth.scheme.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.minis3.auth.scheme.MiniS3AuthSchemeParams; +import software.amazon.awssdk.services.minis3.auth.scheme.MiniS3AuthSchemeProvider; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultMiniS3AuthSchemeProvider implements MiniS3AuthSchemeProvider { + private static final DefaultMiniS3AuthSchemeProvider DEFAULT = new DefaultMiniS3AuthSchemeProvider(); + + private DefaultMiniS3AuthSchemeProvider() { + } + + public static DefaultMiniS3AuthSchemeProvider create() { + return DEFAULT; + } + + @Override + public List resolveAuthScheme(MiniS3AuthSchemeParams params) { + List options = new ArrayList<>(); + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "mini-s3-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()) + .putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, false) + .putSignerProperty(AwsV4HttpSigner.NORMALIZE_PATH, false) + .putSignerProperty(AwsV4HttpSigner.PAYLOAD_SIGNING_ENABLED, false).build()); + return Collections.unmodifiableList(options); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-with-no-auth-auth-scheme-default-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-with-no-auth-auth-scheme-default-provider.java new file mode 100644 index 000000000000..7a3553a47e59 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-with-no-auth-auth-scheme-default-provider.java @@ -0,0 +1,63 @@ +/* + * 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.database.auth.scheme.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeParams; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultDatabaseAuthSchemeProvider implements DatabaseAuthSchemeProvider { + private static final DefaultDatabaseAuthSchemeProvider DEFAULT = new DefaultDatabaseAuthSchemeProvider(); + + private DefaultDatabaseAuthSchemeProvider() { + } + + public static DefaultDatabaseAuthSchemeProvider create() { + return DEFAULT; + } + + @Override + public List resolveAuthScheme(DatabaseAuthSchemeParams params) { + List options = new ArrayList<>(); + switch (params.operation()) { + case "GetDatabaseVersion": + options.add(AuthSchemeOption.builder().schemeId("smithy.api#noAuth").build()); + break; + case "GetRow": + 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("smithy.api#httpBearerAuth").build()); + options.add(AuthSchemeOption.builder().schemeId("smithy.api#noAuth").build()); + break; + default: + 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("smithy.api#httpBearerAuth").build()); + break; + } + return Collections.unmodifiableList(options); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-default-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-default-params.java new file mode 100644 index 000000000000..8d4589400e62 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-default-params.java @@ -0,0 +1,85 @@ +/* + * 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.query.auth.scheme.internal; + +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeParams; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultQueryAuthSchemeParams implements QueryAuthSchemeParams { + private final String operation; + + private final Region region; + + private DefaultQueryAuthSchemeParams(Builder builder) { + this.operation = Validate.paramNotNull(builder.operation, "operation"); + this.region = builder.region; + } + + public static QueryAuthSchemeParams.Builder builder() { + return new Builder(); + } + + @Override + public String operation() { + return operation; + } + + @Override + public Region region() { + return region; + } + + @Override + public QueryAuthSchemeParams.Builder toBuilder() { + return new Builder(this); + } + + private static final class Builder implements QueryAuthSchemeParams.Builder { + private String operation; + + private Region region; + + Builder() { + } + + Builder(DefaultQueryAuthSchemeParams params) { + this.operation = params.operation; + this.region = params.region; + } + + @Override + public Builder operation(String operation) { + this.operation = operation; + return this; + } + + @Override + public Builder region(Region region) { + this.region = region; + return this; + } + + @Override + public QueryAuthSchemeParams build() { + return new DefaultQueryAuthSchemeParams(this); + } + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-default-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-default-provider.java new file mode 100644 index 000000000000..5d51e68ebb1e --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-default-provider.java @@ -0,0 +1,58 @@ +/* + * 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.query.auth.scheme.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeParams; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultQueryAuthSchemeProvider implements QueryAuthSchemeProvider { + private static final DefaultQueryAuthSchemeProvider DEFAULT = new DefaultQueryAuthSchemeProvider(); + + private DefaultQueryAuthSchemeProvider() { + } + + public static DefaultQueryAuthSchemeProvider create() { + return DEFAULT; + } + + @Override + public List resolveAuthScheme(QueryAuthSchemeParams params) { + List options = new ArrayList<>(); + switch (params.operation()) { + case "BearerAuthOperation": + options.add(AuthSchemeOption.builder().schemeId("smithy.api#httpBearerAuth").build()); + break; + case "OperationWithNoneAuthType": + options.add(AuthSchemeOption.builder().schemeId("smithy.api#noAuth").build()); + break; + default: + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "query-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()).build()); + break; + } + return Collections.unmodifiableList(options); + } +} 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 new file mode 100644 index 000000000000..7c59b1f5af74 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java @@ -0,0 +1,128 @@ +package software.amazon.awssdk.services.query.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.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +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.query.auth.scheme.QueryAuthSchemeParams; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; +import software.amazon.awssdk.services.query.endpoints.internal.AuthSchemeUtils; +import software.amazon.awssdk.utils.Logger; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class QueryAuthSchemeInterceptor implements ExecutionInterceptor { + private static Logger LOG = Logger.loggerFor(QueryAuthSchemeInterceptor.class); + + @Override + public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + List authOptions = resolveAuthOptions(context, executionAttributes); + SelectedAuthScheme selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes); + AuthSchemeUtils.putSelectedAuthScheme(executionAttributes, selectedAuthScheme); + } + + private List resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + QueryAuthSchemeProvider authSchemeProvider = Validate.isInstanceOf(QueryAuthSchemeProvider.class, + executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER), + "Expected an instance of QueryAuthSchemeProvider"); + QueryAuthSchemeParams 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); + 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 QueryAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttributes executionAttributes) { + String operation = executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME); + Region region = executionAttributes.getAttribute(AwsExecutionAttribute.AWS_REGION); + return QueryAuthSchemeParams.builder().operation(operation).region(region).build(); + } + + private SelectedAuthScheme trySelectAuthScheme(AuthSchemeOption authOption, AuthScheme authScheme, + IdentityProviders identityProviders, List> discardedReasons, + MetricCollector metricCollector) { + 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; + } + 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, authScheme.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; + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-params.java new file mode 100644 index 000000000000..b726c4a81e09 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-params.java @@ -0,0 +1,73 @@ +/* + * 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.query.auth.scheme; + +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.query.auth.scheme.internal.DefaultQueryAuthSchemeParams; +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 Query service. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkPublicApi +public interface QueryAuthSchemeParams extends ToCopyableBuilder { + /** + * Get a new builder for creating a {@link QueryAuthSchemeParams}. + */ + static Builder builder() { + return DefaultQueryAuthSchemeParams.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 a {@link Builder} to customize the parameters. + */ + Builder toBuilder(); + + /** + * A builder for a {@link QueryAuthSchemeParams}. + */ + 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); + + /** + * Returns a {@link QueryAuthSchemeParams} object that is created from the properties that have been set on the + * builder. + */ + QueryAuthSchemeParams build(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-provider.java new file mode 100644 index 000000000000..a4f84dc2665a --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-provider.java @@ -0,0 +1,53 @@ +/* + * 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.query.auth.scheme; + +import java.util.List; +import java.util.function.Consumer; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider; +import software.amazon.awssdk.services.query.auth.scheme.internal.DefaultQueryAuthSchemeProvider; + +/** + * An auth scheme provider for Query service. The auth scheme provider takes a set of parameters using + * {@link QueryAuthSchemeParams}, and resolves a list of {@link AuthSchemeOption} based on the given parameters. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkPublicApi +public interface QueryAuthSchemeProvider extends AuthSchemeProvider { + /** + * Resolve the auth schemes based on the given set of parameters. + */ + List resolveAuthScheme(QueryAuthSchemeParams authSchemeParams); + + /** + * Resolve the auth schemes based on the given set of parameters. + */ + default List resolveAuthScheme(Consumer consumer) { + QueryAuthSchemeParams.Builder builder = QueryAuthSchemeParams.builder(); + consumer.accept(builder); + return resolveAuthScheme(builder.build()); + } + + /** + * Get the default auth scheme provider. + */ + static QueryAuthSchemeProvider defaultProvider() { + return DefaultQueryAuthSchemeProvider.create(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-with-allowlist.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-with-allowlist.java new file mode 100644 index 000000000000..e9fb9b7cfaeb --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-with-allowlist.java @@ -0,0 +1,195 @@ +/* + * 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.query.auth.scheme.internal; + +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeParams; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultQueryAuthSchemeParams implements QueryAuthSchemeParams { + private final String operation; + + private final Region region; + + private final Boolean defaultTrueParam; + + private final String defaultStringParam; + + private final String deprecatedParam; + + private final Boolean booleanContextParam; + + private final String stringContextParam; + + private final String operationContextParam; + + private DefaultQueryAuthSchemeParams(Builder builder) { + this.operation = Validate.paramNotNull(builder.operation, "operation"); + this.region = builder.region; + this.defaultTrueParam = Validate.paramNotNull(builder.defaultTrueParam, "defaultTrueParam"); + this.defaultStringParam = Validate.paramNotNull(builder.defaultStringParam, "defaultStringParam"); + this.deprecatedParam = builder.deprecatedParam; + this.booleanContextParam = builder.booleanContextParam; + this.stringContextParam = builder.stringContextParam; + this.operationContextParam = builder.operationContextParam; + } + + public static QueryAuthSchemeParams.Builder builder() { + return new Builder(); + } + + @Override + public String operation() { + return operation; + } + + @Override + public Region region() { + return region; + } + + @Override + public Boolean defaultTrueParam() { + return defaultTrueParam; + } + + @Override + public String defaultStringParam() { + return defaultStringParam; + } + + @Deprecated + @Override + public String deprecatedParam() { + return deprecatedParam; + } + + @Override + public Boolean booleanContextParam() { + return booleanContextParam; + } + + @Override + public String stringContextParam() { + return stringContextParam; + } + + @Override + public String operationContextParam() { + return operationContextParam; + } + + @Override + public QueryAuthSchemeParams.Builder toBuilder() { + return new Builder(this); + } + + private static final class Builder implements QueryAuthSchemeParams.Builder { + private String operation; + + private Region region; + + private Boolean defaultTrueParam = true; + + private String defaultStringParam = "hello endpoints"; + + private String deprecatedParam; + + private Boolean booleanContextParam; + + private String stringContextParam; + + private String operationContextParam; + + Builder() { + } + + Builder(DefaultQueryAuthSchemeParams params) { + this.operation = params.operation; + this.region = params.region; + this.defaultTrueParam = params.defaultTrueParam; + this.defaultStringParam = params.defaultStringParam; + this.deprecatedParam = params.deprecatedParam; + this.booleanContextParam = params.booleanContextParam; + this.stringContextParam = params.stringContextParam; + this.operationContextParam = params.operationContextParam; + } + + @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 defaultTrueParam(Boolean defaultTrueParam) { + this.defaultTrueParam = defaultTrueParam; + if (this.defaultTrueParam == null) { + this.defaultTrueParam = true; + } + return this; + } + + @Override + public Builder defaultStringParam(String defaultStringParam) { + this.defaultStringParam = defaultStringParam; + if (this.defaultStringParam == null) { + this.defaultStringParam = "hello endpoints"; + } + return this; + } + + @Deprecated + @Override + public Builder deprecatedParam(String deprecatedParam) { + this.deprecatedParam = deprecatedParam; + return this; + } + + @Override + public Builder booleanContextParam(Boolean booleanContextParam) { + this.booleanContextParam = booleanContextParam; + return this; + } + + @Override + public Builder stringContextParam(String stringContextParam) { + this.stringContextParam = stringContextParam; + return this; + } + + @Override + public Builder operationContextParam(String operationContextParam) { + this.operationContextParam = operationContextParam; + return this; + } + + @Override + public QueryAuthSchemeParams build() { + return new DefaultQueryAuthSchemeParams(this); + } + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-without-allowlist.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-without-allowlist.java new file mode 100644 index 000000000000..fc20bf433c72 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-without-allowlist.java @@ -0,0 +1,246 @@ +/* + * 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.query.auth.scheme.internal; + +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeParams; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultQueryAuthSchemeParams implements QueryAuthSchemeParams { + private final String operation; + + private final Region region; + + private final Boolean useDualStackEndpoint; + + private final Boolean useFIPSEndpoint; + + private final String endpointId; + + private final Boolean defaultTrueParam; + + private final String defaultStringParam; + + private final String deprecatedParam; + + private final Boolean booleanContextParam; + + private final String stringContextParam; + + private final String operationContextParam; + + private DefaultQueryAuthSchemeParams(Builder builder) { + this.operation = Validate.paramNotNull(builder.operation, "operation"); + this.region = builder.region; + this.useDualStackEndpoint = builder.useDualStackEndpoint; + this.useFIPSEndpoint = builder.useFIPSEndpoint; + this.endpointId = builder.endpointId; + this.defaultTrueParam = Validate.paramNotNull(builder.defaultTrueParam, "defaultTrueParam"); + this.defaultStringParam = Validate.paramNotNull(builder.defaultStringParam, "defaultStringParam"); + this.deprecatedParam = builder.deprecatedParam; + this.booleanContextParam = builder.booleanContextParam; + this.stringContextParam = builder.stringContextParam; + this.operationContextParam = builder.operationContextParam; + } + + public static QueryAuthSchemeParams.Builder builder() { + return new Builder(); + } + + @Override + public String operation() { + return operation; + } + + @Override + public Region region() { + return region; + } + + @Override + public Boolean useDualStackEndpoint() { + return useDualStackEndpoint; + } + + @Override + public Boolean useFipsEndpoint() { + return useFIPSEndpoint; + } + + @Override + public String endpointId() { + return endpointId; + } + + @Override + public Boolean defaultTrueParam() { + return defaultTrueParam; + } + + @Override + public String defaultStringParam() { + return defaultStringParam; + } + + @Deprecated + @Override + public String deprecatedParam() { + return deprecatedParam; + } + + @Override + public Boolean booleanContextParam() { + return booleanContextParam; + } + + @Override + public String stringContextParam() { + return stringContextParam; + } + + @Override + public String operationContextParam() { + return operationContextParam; + } + + @Override + public QueryAuthSchemeParams.Builder toBuilder() { + return new Builder(this); + } + + private static final class Builder implements QueryAuthSchemeParams.Builder { + private String operation; + + private Region region; + + private Boolean useDualStackEndpoint; + + private Boolean useFIPSEndpoint; + + private String endpointId; + + private Boolean defaultTrueParam = true; + + private String defaultStringParam = "hello endpoints"; + + private String deprecatedParam; + + private Boolean booleanContextParam; + + private String stringContextParam; + + private String operationContextParam; + + Builder() { + } + + Builder(DefaultQueryAuthSchemeParams params) { + this.operation = params.operation; + this.region = params.region; + this.useDualStackEndpoint = params.useDualStackEndpoint; + this.useFIPSEndpoint = params.useFIPSEndpoint; + this.endpointId = params.endpointId; + this.defaultTrueParam = params.defaultTrueParam; + this.defaultStringParam = params.defaultStringParam; + this.deprecatedParam = params.deprecatedParam; + this.booleanContextParam = params.booleanContextParam; + this.stringContextParam = params.stringContextParam; + this.operationContextParam = params.operationContextParam; + } + + @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 useDualStackEndpoint(Boolean useDualStackEndpoint) { + this.useDualStackEndpoint = useDualStackEndpoint; + return this; + } + + @Override + public Builder useFipsEndpoint(Boolean useFIPSEndpoint) { + this.useFIPSEndpoint = useFIPSEndpoint; + return this; + } + + @Override + public Builder endpointId(String endpointId) { + this.endpointId = endpointId; + return this; + } + + @Override + public Builder defaultTrueParam(Boolean defaultTrueParam) { + this.defaultTrueParam = defaultTrueParam; + if (this.defaultTrueParam == null) { + this.defaultTrueParam = true; + } + return this; + } + + @Override + public Builder defaultStringParam(String defaultStringParam) { + this.defaultStringParam = defaultStringParam; + if (this.defaultStringParam == null) { + this.defaultStringParam = "hello endpoints"; + } + return this; + } + + @Deprecated + @Override + public Builder deprecatedParam(String deprecatedParam) { + this.deprecatedParam = deprecatedParam; + return this; + } + + @Override + public Builder booleanContextParam(Boolean booleanContextParam) { + this.booleanContextParam = booleanContextParam; + return this; + } + + @Override + public Builder stringContextParam(String stringContextParam) { + this.stringContextParam = stringContextParam; + return this; + } + + @Override + public Builder operationContextParam(String operationContextParam) { + this.operationContextParam = operationContextParam; + return this; + } + + @Override + public QueryAuthSchemeParams build() { + return new DefaultQueryAuthSchemeParams(this); + } + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider-without-allowlist.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider-without-allowlist.java new file mode 100644 index 000000000000..d22e9c1ca151 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider-without-allowlist.java @@ -0,0 +1,82 @@ +package software.amazon.awssdk.services.query.auth.scheme.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; +import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; +import software.amazon.awssdk.endpoints.Endpoint; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeParams; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; +import software.amazon.awssdk.utils.CompletableFutureUtils; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultQueryAuthSchemeProvider implements QueryAuthSchemeProvider { + private static final DefaultQueryAuthSchemeProvider DEFAULT = new DefaultQueryAuthSchemeProvider(); + + private static final QueryAuthSchemeProvider MODELED_RESOLVER = ModeledQueryAuthSchemeProvider.create(); + + private static final QueryEndpointProvider DELEGATE = QueryEndpointProvider.defaultProvider(); + + private DefaultQueryAuthSchemeProvider() { + } + + public static QueryAuthSchemeProvider create() { + return DEFAULT; + } + + @Override + public List resolveAuthScheme(QueryAuthSchemeParams params) { + QueryEndpointParams endpointParameters = QueryEndpointParams.builder().region(params.region()) + .useDualStackEndpoint(params.useDualStackEndpoint()).useFipsEndpoint(params.useFipsEndpoint()) + .endpointId(params.endpointId()).defaultTrueParam(params.defaultTrueParam()) + .defaultStringParam(params.defaultStringParam()).deprecatedParam(params.deprecatedParam()) + .booleanContextParam(params.booleanContextParam()).stringContextParam(params.stringContextParam()) + .operationContextParam(params.operationContextParam()).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(DELEGATE.resolveEndpoint(endpointParameters)); + List authSchemes = endpoint.attribute(AwsEndpointAttribute.AUTH_SCHEMES); + if (authSchemes == null) { + return MODELED_RESOLVER.resolveAuthScheme(params); + } + List options = new ArrayList<>(); + for (EndpointAuthScheme authScheme : authSchemes) { + String name = authScheme.name(); + switch (name) { + case "sigv4": + SigV4AuthScheme sigv4AuthScheme = Validate.isInstanceOf(SigV4AuthScheme.class, authScheme, + "Expecting auth scheme of class SigV4AuthScheme, got instead object of class %s", authScheme.getClass() + .getName()); + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, sigv4AuthScheme.signingName()) + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, sigv4AuthScheme.signingRegion()) + .putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, !sigv4AuthScheme.disableDoubleEncoding()).build()); + break; + case "sigv4a": + SigV4aAuthScheme sigv4aAuthScheme = Validate.isInstanceOf(SigV4aAuthScheme.class, authScheme, + "Expecting auth scheme of class SigV4AuthScheme, got instead object of class %s", authScheme.getClass() + .getName()); + RegionSet regionSet = RegionSet.create(sigv4aAuthScheme.signingRegionSet()); + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, sigv4aAuthScheme.signingName()) + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet) + .putSignerProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, !sigv4aAuthScheme.disableDoubleEncoding()).build()); + break; + default: + throw new IllegalArgumentException("Unknown auth scheme: " + name); + } + } + return Collections.unmodifiableList(options); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider.java new file mode 100644 index 000000000000..0fd273b82882 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider.java @@ -0,0 +1,80 @@ +package software.amazon.awssdk.services.query.auth.scheme.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; +import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; +import software.amazon.awssdk.endpoints.Endpoint; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeParams; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; +import software.amazon.awssdk.utils.CompletableFutureUtils; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultQueryAuthSchemeProvider implements QueryAuthSchemeProvider { + private static final DefaultQueryAuthSchemeProvider DEFAULT = new DefaultQueryAuthSchemeProvider(); + + private static final QueryAuthSchemeProvider MODELED_RESOLVER = ModeledQueryAuthSchemeProvider.create(); + + private static final QueryEndpointProvider DELEGATE = QueryEndpointProvider.defaultProvider(); + + private DefaultQueryAuthSchemeProvider() { + } + + public static QueryAuthSchemeProvider create() { + return DEFAULT; + } + + @Override + public List resolveAuthScheme(QueryAuthSchemeParams params) { + QueryEndpointParams endpointParameters = QueryEndpointParams.builder().region(params.region()) + .defaultTrueParam(params.defaultTrueParam()).defaultStringParam(params.defaultStringParam()) + .deprecatedParam(params.deprecatedParam()).booleanContextParam(params.booleanContextParam()) + .stringContextParam(params.stringContextParam()).operationContextParam(params.operationContextParam()).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(DELEGATE.resolveEndpoint(endpointParameters)); + List authSchemes = endpoint.attribute(AwsEndpointAttribute.AUTH_SCHEMES); + if (authSchemes == null) { + return MODELED_RESOLVER.resolveAuthScheme(params); + } + List options = new ArrayList<>(); + for (EndpointAuthScheme authScheme : authSchemes) { + String name = authScheme.name(); + switch (name) { + case "sigv4": + SigV4AuthScheme sigv4AuthScheme = Validate.isInstanceOf(SigV4AuthScheme.class, authScheme, + "Expecting auth scheme of class SigV4AuthScheme, got instead object of class %s", authScheme.getClass() + .getName()); + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, sigv4AuthScheme.signingName()) + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, sigv4AuthScheme.signingRegion()) + .putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, !sigv4AuthScheme.disableDoubleEncoding()).build()); + break; + case "sigv4a": + SigV4aAuthScheme sigv4aAuthScheme = Validate.isInstanceOf(SigV4aAuthScheme.class, authScheme, + "Expecting auth scheme of class SigV4AuthScheme, got instead object of class %s", authScheme.getClass() + .getName()); + RegionSet regionSet = RegionSet.create(sigv4aAuthScheme.signingRegionSet()); + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, sigv4aAuthScheme.signingName()) + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet) + .putSignerProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, !sigv4aAuthScheme.disableDoubleEncoding()).build()); + break; + default: + throw new IllegalArgumentException("Unknown auth scheme: " + name); + } + } + return Collections.unmodifiableList(options); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-modeled-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-modeled-provider.java new file mode 100644 index 000000000000..f5bdc077f3b3 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-modeled-provider.java @@ -0,0 +1,58 @@ +/* + * 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.query.auth.scheme.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeParams; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class ModeledQueryAuthSchemeProvider implements QueryAuthSchemeProvider { + private static final ModeledQueryAuthSchemeProvider DEFAULT = new ModeledQueryAuthSchemeProvider(); + + private ModeledQueryAuthSchemeProvider() { + } + + public static ModeledQueryAuthSchemeProvider create() { + return DEFAULT; + } + + @Override + public List resolveAuthScheme(QueryAuthSchemeParams params) { + List options = new ArrayList<>(); + switch (params.operation()) { + case "BearerAuthOperation": + options.add(AuthSchemeOption.builder().schemeId("smithy.api#httpBearerAuth").build()); + break; + case "OperationWithNoneAuthType": + options.add(AuthSchemeOption.builder().schemeId("smithy.api#noAuth").build()); + break; + default: + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "query-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()).build()); + break; + } + return Collections.unmodifiableList(options); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-with-allowlist.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-with-allowlist.java new file mode 100644 index 000000000000..38d49f4851a5 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-with-allowlist.java @@ -0,0 +1,105 @@ +/* + * 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.query.auth.scheme; + +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.query.auth.scheme.internal.DefaultQueryAuthSchemeParams; +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 Query service. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkPublicApi +public interface QueryAuthSchemeParams extends ToCopyableBuilder { + /** + * Get a new builder for creating a {@link QueryAuthSchemeParams}. + */ + static Builder builder() { + return DefaultQueryAuthSchemeParams.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(); + + /** + * A param that defauls to true + */ + Boolean defaultTrueParam(); + + String defaultStringParam(); + + @Deprecated + String deprecatedParam(); + + Boolean booleanContextParam(); + + String stringContextParam(); + + String operationContextParam(); + + /** + * Returns a {@link Builder} to customize the parameters. + */ + Builder toBuilder(); + + /** + * A builder for a {@link QueryAuthSchemeParams}. + */ + 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); + + /** + * A param that defauls to true + */ + Builder defaultTrueParam(Boolean defaultTrueParam); + + Builder defaultStringParam(String defaultStringParam); + + @Deprecated + Builder deprecatedParam(String deprecatedParam); + + Builder booleanContextParam(Boolean booleanContextParam); + + Builder stringContextParam(String stringContextParam); + + Builder operationContextParam(String operationContextParam); + + /** + * Returns a {@link QueryAuthSchemeParams} object that is created from the properties that have been set on the + * builder. + */ + QueryAuthSchemeParams build(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-without-allowlist.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-without-allowlist.java new file mode 100644 index 000000000000..e9020e887cb6 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-without-allowlist.java @@ -0,0 +1,117 @@ +/* + * 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.query.auth.scheme; + +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.query.auth.scheme.internal.DefaultQueryAuthSchemeParams; +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 Query service. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkPublicApi +public interface QueryAuthSchemeParams extends ToCopyableBuilder { + /** + * Get a new builder for creating a {@link QueryAuthSchemeParams}. + */ + static Builder builder() { + return DefaultQueryAuthSchemeParams.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(); + + Boolean useDualStackEndpoint(); + + Boolean useFipsEndpoint(); + + String endpointId(); + + /** + * A param that defauls to true + */ + Boolean defaultTrueParam(); + + String defaultStringParam(); + + @Deprecated + String deprecatedParam(); + + Boolean booleanContextParam(); + + String stringContextParam(); + + String operationContextParam(); + + /** + * Returns a {@link Builder} to customize the parameters. + */ + Builder toBuilder(); + + /** + * A builder for a {@link QueryAuthSchemeParams}. + */ + 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); + + Builder useDualStackEndpoint(Boolean useDualStackEndpoint); + + Builder useFipsEndpoint(Boolean useFIPSEndpoint); + + Builder endpointId(String endpointId); + + /** + * A param that defauls to true + */ + Builder defaultTrueParam(Boolean defaultTrueParam); + + Builder defaultStringParam(String defaultStringParam); + + @Deprecated + Builder deprecatedParam(String deprecatedParam); + + Builder booleanContextParam(Boolean booleanContextParam); + + Builder stringContextParam(String stringContextParam); + + Builder operationContextParam(String operationContextParam); + + /** + * Returns a {@link QueryAuthSchemeParams} object that is created from the properties that have been set on the + * builder. + */ + QueryAuthSchemeParams build(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-provider.java new file mode 100644 index 000000000000..a4f84dc2665a --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-provider.java @@ -0,0 +1,53 @@ +/* + * 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.query.auth.scheme; + +import java.util.List; +import java.util.function.Consumer; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider; +import software.amazon.awssdk.services.query.auth.scheme.internal.DefaultQueryAuthSchemeProvider; + +/** + * An auth scheme provider for Query service. The auth scheme provider takes a set of parameters using + * {@link QueryAuthSchemeParams}, and resolves a list of {@link AuthSchemeOption} based on the given parameters. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkPublicApi +public interface QueryAuthSchemeProvider extends AuthSchemeProvider { + /** + * Resolve the auth schemes based on the given set of parameters. + */ + List resolveAuthScheme(QueryAuthSchemeParams authSchemeParams); + + /** + * Resolve the auth schemes based on the given set of parameters. + */ + default List resolveAuthScheme(Consumer consumer) { + QueryAuthSchemeParams.Builder builder = QueryAuthSchemeParams.builder(); + consumer.accept(builder); + return resolveAuthScheme(builder.build()); + } + + /** + * Get the default auth scheme provider. + */ + static QueryAuthSchemeProvider defaultProvider() { + return DefaultQueryAuthSchemeProvider.create(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java new file mode 100644 index 000000000000..ac5ec58f59a7 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java @@ -0,0 +1,137 @@ +package software.amazon.awssdk.services.query.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.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.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +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.services.query.auth.scheme.QueryAuthSchemeParams; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; +import software.amazon.awssdk.services.query.endpoints.internal.AuthSchemeUtils; +import software.amazon.awssdk.services.query.endpoints.internal.QueryResolveEndpointInterceptor; +import software.amazon.awssdk.utils.Logger; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class QueryAuthSchemeInterceptor implements ExecutionInterceptor { + private static Logger LOG = Logger.loggerFor(QueryAuthSchemeInterceptor.class); + + @Override + public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + List authOptions = resolveAuthOptions(context, executionAttributes); + SelectedAuthScheme selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes); + AuthSchemeUtils.putSelectedAuthScheme(executionAttributes, selectedAuthScheme); + } + + private List resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + QueryAuthSchemeProvider authSchemeProvider = Validate.isInstanceOf(QueryAuthSchemeProvider.class, + executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER), + "Expected an instance of QueryAuthSchemeProvider"); + QueryAuthSchemeParams 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); + 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 QueryAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttributes executionAttributes) { + QueryEndpointParams endpointParams = QueryResolveEndpointInterceptor.ruleParams(request, executionAttributes); + QueryAuthSchemeParams.Builder builder = QueryAuthSchemeParams.builder(); + builder.region(endpointParams.region()); + builder.defaultTrueParam(endpointParams.defaultTrueParam()); + builder.defaultStringParam(endpointParams.defaultStringParam()); + builder.deprecatedParam(endpointParams.deprecatedParam()); + builder.booleanContextParam(endpointParams.booleanContextParam()); + builder.stringContextParam(endpointParams.stringContextParam()); + builder.operationContextParam(endpointParams.operationContextParam()); + String operation = executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME); + builder.operation(operation); + return builder.build(); + } + + private SelectedAuthScheme trySelectAuthScheme(AuthSchemeOption authOption, AuthScheme authScheme, + IdentityProviders identityProviders, List> discardedReasons, + MetricCollector metricCollector) { + 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; + } + 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, authScheme.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; + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java new file mode 100644 index 000000000000..2399a2c48d81 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java @@ -0,0 +1,140 @@ +package software.amazon.awssdk.services.query.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.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.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +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.services.query.auth.scheme.QueryAuthSchemeParams; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; +import software.amazon.awssdk.services.query.endpoints.internal.AuthSchemeUtils; +import software.amazon.awssdk.services.query.endpoints.internal.QueryResolveEndpointInterceptor; +import software.amazon.awssdk.utils.Logger; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class QueryAuthSchemeInterceptor implements ExecutionInterceptor { + private static Logger LOG = Logger.loggerFor(QueryAuthSchemeInterceptor.class); + + @Override + public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + List authOptions = resolveAuthOptions(context, executionAttributes); + SelectedAuthScheme selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes); + AuthSchemeUtils.putSelectedAuthScheme(executionAttributes, selectedAuthScheme); + } + + private List resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + QueryAuthSchemeProvider authSchemeProvider = Validate.isInstanceOf(QueryAuthSchemeProvider.class, + executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER), + "Expected an instance of QueryAuthSchemeProvider"); + QueryAuthSchemeParams 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); + 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 QueryAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttributes executionAttributes) { + QueryEndpointParams endpointParams = QueryResolveEndpointInterceptor.ruleParams(request, executionAttributes); + QueryAuthSchemeParams.Builder builder = QueryAuthSchemeParams.builder(); + builder.region(endpointParams.region()); + builder.useDualStackEndpoint(endpointParams.useDualStackEndpoint()); + builder.useFipsEndpoint(endpointParams.useFipsEndpoint()); + builder.endpointId(endpointParams.endpointId()); + builder.defaultTrueParam(endpointParams.defaultTrueParam()); + builder.defaultStringParam(endpointParams.defaultStringParam()); + builder.deprecatedParam(endpointParams.deprecatedParam()); + builder.booleanContextParam(endpointParams.booleanContextParam()); + builder.stringContextParam(endpointParams.stringContextParam()); + builder.operationContextParam(endpointParams.operationContextParam()); + String operation = executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME); + builder.operation(operation); + return builder.build(); + } + + private SelectedAuthScheme trySelectAuthScheme(AuthSchemeOption authOption, AuthScheme authScheme, + IdentityProviders identityProviders, List> discardedReasons, + MetricCollector metricCollector) { + 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; + } + 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, authScheme.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; + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-bearer-auth-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-bearer-auth-client-builder-class.java new file mode 100644 index 000000000000..ee0924b32937 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-bearer-auth-client-builder-class.java @@ -0,0 +1,153 @@ +package software.amazon.awssdk.services.json; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +import software.amazon.awssdk.awscore.client.config.AwsClientOption; +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.http.auth.scheme.BearerAuthScheme; +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.identity.spi.TokenIdentity; +import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; +import software.amazon.awssdk.services.json.auth.scheme.internal.JsonAuthSchemeInterceptor; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; +import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.json.internal.SdkClientConfigurationUtil; +import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.Validate; + +/** + * Internal base class for {@link DefaultJsonClientBuilder} and {@link DefaultJsonAsyncClientBuilder}. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +abstract class DefaultJsonBaseClientBuilder, C> extends AwsDefaultClientBuilder { + private final Map> additionalAuthSchemes = new HashMap<>(); + + @Override + protected final String serviceEndpointPrefix() { + return "json-service-endpoint"; + } + + @Override + protected final String serviceName() { + return "Json"; + } + + @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) + .option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, defaultTokenProvider())); + } + + @Override + protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { + List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new JsonAuthSchemeInterceptor()); + endpointInterceptors.add(new JsonResolveEndpointInterceptor()); + endpointInterceptors.add(new JsonRequestSetEndpointInterceptor()); + ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); + List interceptors = interceptorFactory + .getInterceptors("software/amazon/awssdk/services/json/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(); + IdentityProvider identityProvider = config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER); + if (identityProvider != null) { + IdentityProviders identityProviders = config.option(SdkClientOption.IDENTITY_PROVIDERS); + builder.option(SdkClientOption.IDENTITY_PROVIDERS, identityProviders.toBuilder() + .putIdentityProvider(identityProvider).build()); + } + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); + return builder.build(); + } + + @Override + protected final String signingName() { + return "json-service"; + } + + private JsonEndpointProvider defaultEndpointProvider() { + return JsonEndpointProvider.defaultProvider(); + } + + public B authSchemeProvider(JsonAuthSchemeProvider authSchemeProvider) { + clientConfiguration.option(SdkClientOption.AUTH_SCHEME_PROVIDER, authSchemeProvider); + return thisBuilder(); + } + + private JsonAuthSchemeProvider defaultAuthSchemeProvider() { + return JsonAuthSchemeProvider.defaultProvider(); + } + + @Override + public B putAuthScheme(AuthScheme authScheme) { + additionalAuthSchemes.put(authScheme.schemeId(), authScheme); + return thisBuilder(); + } + + private Map> authSchemes() { + Map> schemes = new HashMap<>(2 + this.additionalAuthSchemes.size()); + BearerAuthScheme bearerAuthScheme = BearerAuthScheme.create(); + schemes.put(bearerAuthScheme.schemeId(), bearerAuthScheme); + NoAuthAuthScheme noAuthAuthScheme = NoAuthAuthScheme.create(); + schemes.put(noAuthAuthScheme.schemeId(), noAuthAuthScheme); + schemes.putAll(this.additionalAuthSchemes); + return Collections.unmodifiableMap(schemes); + } + + private IdentityProvider defaultTokenProvider() { + return DefaultAwsTokenProvider.create(); + } + + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + Validate.notNull(c.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER), + "The 'tokenProvider' must be configured in the client builder."); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-class.java new file mode 100644 index 000000000000..a59675c102b6 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-class.java @@ -0,0 +1,235 @@ +package software.amazon.awssdk.services.json; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.MyServiceHttpConfig; +import software.amazon.MyServiceRetryPolicy; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +import software.amazon.awssdk.awscore.client.config.AwsClientOption; +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.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; +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.identity.spi.TokenIdentity; +import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; +import software.amazon.awssdk.services.json.auth.scheme.internal.JsonAuthSchemeInterceptor; +import software.amazon.awssdk.services.json.endpoints.JsonClientContextParams; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; +import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.json.internal.SdkClientConfigurationUtil; +import software.amazon.awssdk.utils.AttributeMap; +import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.Validate; + +/** + * Internal base class for {@link DefaultJsonClientBuilder} and {@link DefaultJsonAsyncClientBuilder}. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +abstract class DefaultJsonBaseClientBuilder, C> extends AwsDefaultClientBuilder { + private final Map> additionalAuthSchemes = new HashMap<>(); + + @Override + protected final String serviceEndpointPrefix() { + return "json-service-endpoint"; + } + + @Override + protected final String serviceName() { + return "Json"; + } + + @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) + .option(SdkClientOption.SERVICE_CONFIGURATION, ServiceConfiguration.builder().build()) + .option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, defaultTokenProvider())); + } + + @Override + protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { + List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new JsonAuthSchemeInterceptor()); + endpointInterceptors.add(new JsonResolveEndpointInterceptor()); + endpointInterceptors.add(new JsonRequestSetEndpointInterceptor()); + ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); + List interceptors = interceptorFactory + .getInterceptors("software/amazon/awssdk/services/json/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)); + ServiceConfiguration.Builder serviceConfigBuilder = ((ServiceConfiguration) config + .option(SdkClientOption.SERVICE_CONFIGURATION)).toBuilder(); + serviceConfigBuilder.profileFile(serviceConfigBuilder.profileFileSupplier() != null ? serviceConfigBuilder + .profileFileSupplier() : config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)); + serviceConfigBuilder.profileName(serviceConfigBuilder.profileName() != null ? serviceConfigBuilder.profileName() : config + .option(SdkClientOption.PROFILE_NAME)); + if (serviceConfigBuilder.dualstackEnabled() != null) { + Validate.validState( + config.option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED) == null, + "Dualstack has been configured on both ServiceConfiguration and the client/global level. Please limit dualstack configuration to one location."); + } else { + serviceConfigBuilder.dualstackEnabled(config.option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)); + } + if (serviceConfigBuilder.fipsModeEnabled() != null) { + Validate.validState( + config.option(AwsClientOption.FIPS_ENDPOINT_ENABLED) == null, + "Fips has been configured on both ServiceConfiguration and the client/global level. Please limit fips configuration to one location."); + } else { + serviceConfigBuilder.fipsModeEnabled(config.option(AwsClientOption.FIPS_ENDPOINT_ENABLED)); + } + if (serviceConfigBuilder.useArnRegionEnabled() != null) { + Validate.validState( + clientContextParams.get(JsonClientContextParams.USE_ARN_REGION) == null, + "UseArnRegion has been configured on both ServiceConfiguration and the client/global level. Please limit UseArnRegion configuration to one location."); + } else { + serviceConfigBuilder.useArnRegionEnabled(clientContextParams.get(JsonClientContextParams.USE_ARN_REGION)); + } + if (serviceConfigBuilder.multiRegionEnabled() != null) { + Validate.validState( + clientContextParams.get(JsonClientContextParams.DISABLE_MULTI_REGION_ACCESS_POINTS) == null, + "DisableMultiRegionAccessPoints has been configured on both ServiceConfiguration and the client/global level. Please limit DisableMultiRegionAccessPoints configuration to one location."); + } else if (clientContextParams.get(JsonClientContextParams.DISABLE_MULTI_REGION_ACCESS_POINTS) != null) { + serviceConfigBuilder.multiRegionEnabled(!clientContextParams + .get(JsonClientContextParams.DISABLE_MULTI_REGION_ACCESS_POINTS)); + } + if (serviceConfigBuilder.pathStyleAccessEnabled() != null) { + Validate.validState( + clientContextParams.get(JsonClientContextParams.FORCE_PATH_STYLE) == null, + "ForcePathStyle has been configured on both ServiceConfiguration and the client/global level. Please limit ForcePathStyle configuration to one location."); + } else { + serviceConfigBuilder.pathStyleAccessEnabled(clientContextParams.get(JsonClientContextParams.FORCE_PATH_STYLE)); + } + if (serviceConfigBuilder.accelerateModeEnabled() != null) { + Validate.validState( + clientContextParams.get(JsonClientContextParams.ACCELERATE) == null, + "Accelerate has been configured on both ServiceConfiguration and the client/global level. Please limit Accelerate configuration to one location."); + } else { + serviceConfigBuilder.accelerateModeEnabled(clientContextParams.get(JsonClientContextParams.ACCELERATE)); + } + ServiceConfiguration finalServiceConfig = serviceConfigBuilder.build(); + clientContextParams.put(JsonClientContextParams.USE_ARN_REGION, finalServiceConfig.useArnRegionEnabled()); + clientContextParams.put(JsonClientContextParams.DISABLE_MULTI_REGION_ACCESS_POINTS, + !finalServiceConfig.multiRegionEnabled()); + clientContextParams.put(JsonClientContextParams.FORCE_PATH_STYLE, finalServiceConfig.pathStyleAccessEnabled()); + clientContextParams.put(JsonClientContextParams.ACCELERATE, finalServiceConfig.accelerateModeEnabled()); + SdkClientConfiguration.Builder builder = config.toBuilder(); + IdentityProvider identityProvider = config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER); + if (identityProvider != null) { + IdentityProviders identityProviders = config.option(SdkClientOption.IDENTITY_PROVIDERS); + builder.option(SdkClientOption.IDENTITY_PROVIDERS, identityProviders.toBuilder() + .putIdentityProvider(identityProvider).build()); + } + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors) + .option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED, finalServiceConfig.dualstackEnabled()) + .option(AwsClientOption.FIPS_ENDPOINT_ENABLED, finalServiceConfig.fipsModeEnabled()) + .option(SdkClientOption.RETRY_POLICY, MyServiceRetryPolicy.resolveRetryPolicy(config)) + .option(SdkClientOption.SERVICE_CONFIGURATION, finalServiceConfig); + return builder.build(); + } + + @Override + protected final String signingName() { + return "json-service"; + } + + private JsonEndpointProvider defaultEndpointProvider() { + return JsonEndpointProvider.defaultProvider(); + } + + public B authSchemeProvider(JsonAuthSchemeProvider authSchemeProvider) { + clientConfiguration.option(SdkClientOption.AUTH_SCHEME_PROVIDER, authSchemeProvider); + return thisBuilder(); + } + + private JsonAuthSchemeProvider defaultAuthSchemeProvider() { + return JsonAuthSchemeProvider.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); + BearerAuthScheme bearerAuthScheme = BearerAuthScheme.create(); + schemes.put(bearerAuthScheme.schemeId(), bearerAuthScheme); + NoAuthAuthScheme noAuthAuthScheme = NoAuthAuthScheme.create(); + schemes.put(noAuthAuthScheme.schemeId(), noAuthAuthScheme); + schemes.putAll(this.additionalAuthSchemes); + return Collections.unmodifiableMap(schemes); + } + + public B serviceConfiguration(ServiceConfiguration serviceConfiguration) { + clientConfiguration.option(SdkClientOption.SERVICE_CONFIGURATION, serviceConfiguration); + return thisBuilder(); + } + + public void setServiceConfiguration(ServiceConfiguration serviceConfiguration) { + serviceConfiguration(serviceConfiguration); + } + + private IdentityProvider defaultTokenProvider() { + return DefaultAwsTokenProvider.create(); + } + + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected final AttributeMap serviceHttpConfig() { + AttributeMap result = MyServiceHttpConfig.defaultHttpConfig(); + return result; + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + Validate.notNull(c.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER), + "The 'tokenProvider' must be configured in the client builder."); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-endpoints-auth-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-endpoints-auth-params.java new file mode 100644 index 000000000000..322429dc737a --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-endpoints-auth-params.java @@ -0,0 +1,174 @@ +package software.amazon.awssdk.services.query; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +import software.amazon.awssdk.awscore.client.config.AwsClientOption; +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.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; +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.identity.spi.TokenIdentity; +import software.amazon.awssdk.protocols.query.interceptor.QueryParametersToBodyInterceptor; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; +import software.amazon.awssdk.services.query.auth.scheme.internal.QueryAuthSchemeInterceptor; +import software.amazon.awssdk.services.query.endpoints.QueryClientContextParams; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; +import software.amazon.awssdk.services.query.endpoints.internal.QueryRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.query.endpoints.internal.QueryResolveEndpointInterceptor; +import software.amazon.awssdk.services.query.internal.QueryServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.query.internal.SdkClientConfigurationUtil; +import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.Validate; + +/** + * Internal base class for {@link DefaultQueryClientBuilder} and {@link DefaultQueryAsyncClientBuilder}. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +abstract class DefaultQueryBaseClientBuilder, C> extends AwsDefaultClientBuilder { + private final Map> additionalAuthSchemes = new HashMap<>(); + + @Override + protected final String serviceEndpointPrefix() { + return "query-service"; + } + + @Override + protected final String serviceName() { + return "Query"; + } + + @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) + .option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, defaultTokenProvider())); + } + + @Override + protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { + List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new QueryAuthSchemeInterceptor()); + endpointInterceptors.add(new QueryResolveEndpointInterceptor()); + endpointInterceptors.add(new QueryRequestSetEndpointInterceptor()); + ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); + List interceptors = interceptorFactory + .getInterceptors("software/amazon/awssdk/services/query/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)); + List protocolInterceptors = Collections.singletonList(new QueryParametersToBodyInterceptor()); + interceptors = CollectionUtils.mergeLists(interceptors, protocolInterceptors); + SdkClientConfiguration.Builder builder = config.toBuilder(); + IdentityProvider identityProvider = config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER); + if (identityProvider != null) { + IdentityProviders identityProviders = config.option(SdkClientOption.IDENTITY_PROVIDERS); + builder.option(SdkClientOption.IDENTITY_PROVIDERS, identityProviders.toBuilder() + .putIdentityProvider(identityProvider).build()); + } + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors).option(SdkClientOption.CLIENT_CONTEXT_PARAMS, + clientContextParams.build()); + return builder.build(); + } + + @Override + protected final String signingName() { + return "query-service"; + } + + private QueryEndpointProvider defaultEndpointProvider() { + return QueryEndpointProvider.defaultProvider(); + } + + public B authSchemeProvider(QueryAuthSchemeProvider authSchemeProvider) { + clientConfiguration.option(SdkClientOption.AUTH_SCHEME_PROVIDER, authSchemeProvider); + return thisBuilder(); + } + + private QueryAuthSchemeProvider defaultAuthSchemeProvider() { + return QueryAuthSchemeProvider.defaultProvider(); + } + + @Override + public B putAuthScheme(AuthScheme authScheme) { + additionalAuthSchemes.put(authScheme.schemeId(), authScheme); + return thisBuilder(); + } + + private Map> authSchemes() { + Map> schemes = new HashMap<>(4 + this.additionalAuthSchemes.size()); + AwsV4AuthScheme awsV4AuthScheme = AwsV4AuthScheme.create(); + schemes.put(awsV4AuthScheme.schemeId(), awsV4AuthScheme); + AwsV4aAuthScheme awsV4aAuthScheme = AwsV4aAuthScheme.create(); + schemes.put(awsV4aAuthScheme.schemeId(), awsV4aAuthScheme); + BearerAuthScheme bearerAuthScheme = BearerAuthScheme.create(); + schemes.put(bearerAuthScheme.schemeId(), bearerAuthScheme); + NoAuthAuthScheme noAuthAuthScheme = NoAuthAuthScheme.create(); + schemes.put(noAuthAuthScheme.schemeId(), noAuthAuthScheme); + schemes.putAll(this.additionalAuthSchemes); + return Collections.unmodifiableMap(schemes); + } + + public B booleanContextParam(Boolean booleanContextParam) { + clientContextParams.put(QueryClientContextParams.BOOLEAN_CONTEXT_PARAM, booleanContextParam); + return thisBuilder(); + } + + public B stringContextParam(String stringContextParam) { + clientContextParams.put(QueryClientContextParams.STRING_CONTEXT_PARAM, stringContextParam); + return thisBuilder(); + } + + private IdentityProvider defaultTokenProvider() { + return DefaultAwsTokenProvider.create(); + } + + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + QueryServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = QueryServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + Validate.notNull(c.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER), + "The 'tokenProvider' must be configured in the client builder."); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-internal-defaults-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-internal-defaults-class.java new file mode 100644 index 000000000000..217c98e28c7c --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-internal-defaults-class.java @@ -0,0 +1,143 @@ +package software.amazon.awssdk.services.json; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +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.scheme.NoAuthAuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; +import software.amazon.awssdk.services.json.auth.scheme.internal.JsonAuthSchemeInterceptor; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; +import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.json.internal.SdkClientConfigurationUtil; +import software.amazon.awssdk.utils.CollectionUtils; + +/** + * Internal base class for {@link DefaultJsonClientBuilder} and {@link DefaultJsonAsyncClientBuilder}. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +abstract class DefaultJsonBaseClientBuilder, C> extends AwsDefaultClientBuilder { + private final Map> additionalAuthSchemes = new HashMap<>(); + + @Override + protected final String serviceEndpointPrefix() { + return "json-service-endpoint"; + } + + @Override + protected final String serviceName() { + return "Json"; + } + + @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 mergeInternalDefaults(SdkClientConfiguration config) { + return config.merge(c -> { + c.option(SdkClientOption.INTERNAL_USER_AGENT, "md/foobar"); + c.option(SdkClientOption.DEFAULT_RETRY_MODE, RetryMode.STANDARD); + }); + } + + @Override + protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { + List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new JsonAuthSchemeInterceptor()); + endpointInterceptors.add(new JsonResolveEndpointInterceptor()); + endpointInterceptors.add(new JsonRequestSetEndpointInterceptor()); + ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); + List interceptors = interceptorFactory + .getInterceptors("software/amazon/awssdk/services/json/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.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); + return builder.build(); + } + + @Override + protected final String signingName() { + return "json-service"; + } + + private JsonEndpointProvider defaultEndpointProvider() { + return JsonEndpointProvider.defaultProvider(); + } + + public B authSchemeProvider(JsonAuthSchemeProvider authSchemeProvider) { + clientConfiguration.option(SdkClientOption.AUTH_SCHEME_PROVIDER, authSchemeProvider); + return thisBuilder(); + } + + private JsonAuthSchemeProvider defaultAuthSchemeProvider() { + return JsonAuthSchemeProvider.defaultProvider(); + } + + @Override + public B putAuthScheme(AuthScheme authScheme) { + additionalAuthSchemes.put(authScheme.schemeId(), authScheme); + return thisBuilder(); + } + + private Map> authSchemes() { + Map> schemes = new HashMap<>(2 + this.additionalAuthSchemes.size()); + AwsV4AuthScheme awsV4AuthScheme = AwsV4AuthScheme.create(); + schemes.put(awsV4AuthScheme.schemeId(), awsV4AuthScheme); + NoAuthAuthScheme noAuthAuthScheme = NoAuthAuthScheme.create(); + schemes.put(noAuthAuthScheme.schemeId(), noAuthAuthScheme); + schemes.putAll(this.additionalAuthSchemes); + return Collections.unmodifiableMap(schemes); + } + + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-composed-sync-default-client-builder.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-composed-sync-default-client-builder.java new file mode 100644 index 000000000000..53bac0194568 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-composed-sync-default-client-builder.java @@ -0,0 +1,180 @@ +package software.amazon.awssdk.services.json; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +import software.amazon.awssdk.awscore.client.config.AwsClientOption; +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.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; +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.identity.spi.TokenIdentity; +import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; +import software.amazon.awssdk.services.json.auth.scheme.internal.JsonAuthSchemeInterceptor; +import software.amazon.awssdk.services.json.endpoints.JsonClientContextParams; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; +import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.json.internal.SdkClientConfigurationUtil; +import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.Validate; + +/** + * Internal base class for {@link DefaultJsonClientBuilder} and {@link DefaultJsonAsyncClientBuilder}. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +abstract class DefaultJsonBaseClientBuilder, C> extends AwsDefaultClientBuilder { + private final Map> additionalAuthSchemes = new HashMap<>(); + + @Override + protected final String serviceEndpointPrefix() { + return "json-service-endpoint"; + } + + @Override + protected final String serviceName() { + return "Json"; + } + + @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) + .option(SdkClientOption.SERVICE_CONFIGURATION, ServiceConfiguration.builder().build()) + .option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, defaultTokenProvider())); + } + + @Override + protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { + List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new JsonAuthSchemeInterceptor()); + endpointInterceptors.add(new JsonResolveEndpointInterceptor()); + endpointInterceptors.add(new JsonRequestSetEndpointInterceptor()); + ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); + List interceptors = interceptorFactory + .getInterceptors("software/amazon/awssdk/services/json/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)); + ServiceConfiguration.Builder serviceConfigBuilder = ((ServiceConfiguration) config + .option(SdkClientOption.SERVICE_CONFIGURATION)).toBuilder(); + serviceConfigBuilder.profileFile(serviceConfigBuilder.profileFileSupplier() != null ? serviceConfigBuilder + .profileFileSupplier() : config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)); + serviceConfigBuilder.profileName(serviceConfigBuilder.profileName() != null ? serviceConfigBuilder.profileName() : config + .option(SdkClientOption.PROFILE_NAME)); + ServiceConfiguration finalServiceConfig = serviceConfigBuilder.build(); + SdkClientConfiguration.Builder builder = config.toBuilder(); + IdentityProvider identityProvider = config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER); + if (identityProvider != null) { + IdentityProviders identityProviders = config.option(SdkClientOption.IDENTITY_PROVIDERS); + builder.option(SdkClientOption.IDENTITY_PROVIDERS, identityProviders.toBuilder() + .putIdentityProvider(identityProvider).build()); + } + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors).option(SdkClientOption.SERVICE_CONFIGURATION, + finalServiceConfig); + return builder.build(); + } + + @Override + protected final String signingName() { + return "json-service"; + } + + private JsonEndpointProvider defaultEndpointProvider() { + return JsonEndpointProvider.defaultProvider(); + } + + public B authSchemeProvider(JsonAuthSchemeProvider authSchemeProvider) { + clientConfiguration.option(SdkClientOption.AUTH_SCHEME_PROVIDER, authSchemeProvider); + return thisBuilder(); + } + + private JsonAuthSchemeProvider defaultAuthSchemeProvider() { + return JsonAuthSchemeProvider.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); + BearerAuthScheme bearerAuthScheme = BearerAuthScheme.create(); + schemes.put(bearerAuthScheme.schemeId(), bearerAuthScheme); + NoAuthAuthScheme noAuthAuthScheme = NoAuthAuthScheme.create(); + schemes.put(noAuthAuthScheme.schemeId(), noAuthAuthScheme); + schemes.putAll(this.additionalAuthSchemes); + return Collections.unmodifiableMap(schemes); + } + + public B customParameter(Boolean customParameter) { + clientContextParams.put(JsonClientContextParams.CUSTOM_PARAMETER, customParameter); + return thisBuilder(); + } + + public B serviceConfiguration(ServiceConfiguration serviceConfiguration) { + clientConfiguration.option(SdkClientOption.SERVICE_CONFIGURATION, serviceConfiguration); + return thisBuilder(); + } + + public void setServiceConfiguration(ServiceConfiguration serviceConfiguration) { + serviceConfiguration(serviceConfiguration); + } + + private IdentityProvider defaultTokenProvider() { + return DefaultAwsTokenProvider.create(); + } + + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + Validate.notNull(c.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER), + "The 'tokenProvider' must be configured in the client builder."); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-no-auth-ops-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-no-auth-ops-client-builder-class.java new file mode 100644 index 000000000000..25bcbb6adaaf --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-no-auth-ops-client-builder-class.java @@ -0,0 +1,138 @@ +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 software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +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.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; +import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +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.services.database.internal.SdkClientConfigurationUtil; +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.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); + 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); + BearerAuthScheme bearerAuthScheme = BearerAuthScheme.create(); + schemes.put(bearerAuthScheme.schemeId(), bearerAuthScheme); + NoAuthAuthScheme noAuthAuthScheme = NoAuthAuthScheme.create(); + schemes.put(noAuthAuthScheme.schemeId(), noAuthAuthScheme); + schemes.putAll(this.additionalAuthSchemes); + return Collections.unmodifiableMap(schemes); + } + + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + DatabaseServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = DatabaseServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-no-auth-service-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-no-auth-service-client-builder-class.java new file mode 100644 index 000000000000..516e0f2202a4 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-no-auth-service-client-builder-class.java @@ -0,0 +1,132 @@ +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 software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +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.http.auth.scheme.NoAuthAuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +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.services.database.internal.SdkClientConfigurationUtil; +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.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); + 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<>(1 + this.additionalAuthSchemes.size()); + NoAuthAuthScheme noAuthAuthScheme = NoAuthAuthScheme.create(); + schemes.put(noAuthAuthScheme.schemeId(), noAuthAuthScheme); + schemes.putAll(this.additionalAuthSchemes); + return Collections.unmodifiableMap(schemes); + } + + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + DatabaseServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = DatabaseServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-query-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-query-client-builder-class.java new file mode 100644 index 000000000000..1b793ebbd783 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-query-client-builder-class.java @@ -0,0 +1,171 @@ +package software.amazon.awssdk.services.query; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +import software.amazon.awssdk.awscore.client.config.AwsClientOption; +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.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; +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.identity.spi.TokenIdentity; +import software.amazon.awssdk.protocols.query.interceptor.QueryParametersToBodyInterceptor; +import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; +import software.amazon.awssdk.services.query.auth.scheme.internal.QueryAuthSchemeInterceptor; +import software.amazon.awssdk.services.query.endpoints.QueryClientContextParams; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; +import software.amazon.awssdk.services.query.endpoints.internal.QueryRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.query.endpoints.internal.QueryResolveEndpointInterceptor; +import software.amazon.awssdk.services.query.internal.QueryServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.query.internal.SdkClientConfigurationUtil; +import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.Validate; + +/** + * Internal base class for {@link DefaultQueryClientBuilder} and {@link DefaultQueryAsyncClientBuilder}. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +abstract class DefaultQueryBaseClientBuilder, C> extends AwsDefaultClientBuilder { + private final Map> additionalAuthSchemes = new HashMap<>(); + + @Override + protected final String serviceEndpointPrefix() { + return "query-service"; + } + + @Override + protected final String serviceName() { + return "Query"; + } + + @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) + .option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, defaultTokenProvider())); + } + + @Override + protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { + List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new QueryAuthSchemeInterceptor()); + endpointInterceptors.add(new QueryResolveEndpointInterceptor()); + endpointInterceptors.add(new QueryRequestSetEndpointInterceptor()); + ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); + List interceptors = interceptorFactory + .getInterceptors("software/amazon/awssdk/services/query/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)); + List protocolInterceptors = Collections.singletonList(new QueryParametersToBodyInterceptor()); + interceptors = CollectionUtils.mergeLists(interceptors, protocolInterceptors); + SdkClientConfiguration.Builder builder = config.toBuilder(); + IdentityProvider identityProvider = config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER); + if (identityProvider != null) { + IdentityProviders identityProviders = config.option(SdkClientOption.IDENTITY_PROVIDERS); + builder.option(SdkClientOption.IDENTITY_PROVIDERS, identityProviders.toBuilder() + .putIdentityProvider(identityProvider).build()); + } + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors).option(SdkClientOption.CLIENT_CONTEXT_PARAMS, + clientContextParams.build()); + return builder.build(); + } + + @Override + protected final String signingName() { + return "query-service"; + } + + private QueryEndpointProvider defaultEndpointProvider() { + return QueryEndpointProvider.defaultProvider(); + } + + public B authSchemeProvider(QueryAuthSchemeProvider authSchemeProvider) { + clientConfiguration.option(SdkClientOption.AUTH_SCHEME_PROVIDER, authSchemeProvider); + return thisBuilder(); + } + + private QueryAuthSchemeProvider defaultAuthSchemeProvider() { + return QueryAuthSchemeProvider.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); + BearerAuthScheme bearerAuthScheme = BearerAuthScheme.create(); + schemes.put(bearerAuthScheme.schemeId(), bearerAuthScheme); + NoAuthAuthScheme noAuthAuthScheme = NoAuthAuthScheme.create(); + schemes.put(noAuthAuthScheme.schemeId(), noAuthAuthScheme); + schemes.putAll(this.additionalAuthSchemes); + return Collections.unmodifiableMap(schemes); + } + + public B booleanContextParam(Boolean booleanContextParam) { + clientContextParams.put(QueryClientContextParams.BOOLEAN_CONTEXT_PARAM, booleanContextParam); + return thisBuilder(); + } + + public B stringContextParam(String stringContextParam) { + clientContextParams.put(QueryClientContextParams.STRING_CONTEXT_PARAM, stringContextParam); + return thisBuilder(); + } + + private IdentityProvider defaultTokenProvider() { + return DefaultAwsTokenProvider.create(); + } + + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + QueryServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = QueryServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + Validate.notNull(c.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER), + "The 'tokenProvider' must be configured in the client builder."); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-async-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-async-client-builder-class.java index 9308dd1d2969..668712af4ae4 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-async-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-async-client-builder-class.java @@ -3,11 +3,11 @@ import java.net.URI; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; -import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; /** @@ -16,7 +16,7 @@ @Generated("software.amazon.awssdk:codegen") @SdkInternalApi final class DefaultJsonAsyncClientBuilder extends DefaultJsonBaseClientBuilder implements - JsonAsyncClientBuilder { + JsonAsyncClientBuilder { @Override public DefaultJsonAsyncClientBuilder endpointProvider(JsonEndpointProvider endpointProvider) { clientConfiguration.option(SdkClientOption.ENDPOINT_PROVIDER, endpointProvider); @@ -24,8 +24,8 @@ public DefaultJsonAsyncClientBuilder endpointProvider(JsonEndpointProvider endpo } @Override - public DefaultJsonAsyncClientBuilder tokenProvider(SdkTokenProvider tokenProvider) { - clientConfiguration.option(AwsClientOption.TOKEN_PROVIDER, tokenProvider); + public DefaultJsonAsyncClientBuilder tokenProvider(IdentityProvider tokenProvider) { + clientConfiguration.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, tokenProvider); return this; } @@ -40,13 +40,11 @@ protected final JsonAsyncClient buildClient() { private JsonServiceClientConfiguration initializeServiceClientConfig(SdkClientConfiguration clientConfig) { URI endpointOverride = null; - EndpointProvider endpointProvider = clientConfig.option(SdkClientOption.ENDPOINT_PROVIDER); - if (clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN) != null - && Boolean.TRUE.equals(clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN))) { + if (Boolean.TRUE.equals(clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN))) { endpointOverride = clientConfig.option(SdkClientOption.ENDPOINT); } return JsonServiceClientConfiguration.builder().overrideConfiguration(overrideConfiguration()) - .region(clientConfig.option(AwsClientOption.AWS_REGION)).endpointOverride(endpointOverride) - .endpointProvider(endpointProvider).build(); + .region(clientConfig.option(AwsClientOption.AWS_REGION)).endpointOverride(endpointOverride) + .endpointProvider(clientConfig.option(SdkClientOption.ENDPOINT_PROVIDER)).build(); } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-class.java index 0c242955145e..cbe8292bdcfc 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-class.java @@ -4,21 +4,26 @@ import java.util.List; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider; import software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner; import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; import software.amazon.awssdk.awscore.client.config.AwsClientOption; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; 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.signer.Signer; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; -import software.amazon.awssdk.services.json.endpoints.internal.JsonEndpointAuthSchemeInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.json.internal.SdkClientConfigurationUtil; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Validate; @@ -42,7 +47,7 @@ protected final String serviceName() { protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfiguration config) { return config.merge(c -> c.option(SdkClientOption.ENDPOINT_PROVIDER, defaultEndpointProvider()) .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false) - .option(AwsClientOption.TOKEN_PROVIDER, defaultTokenProvider()) + .option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, defaultTokenProvider()) .option(SdkAdvancedClientOption.TOKEN_SIGNER, defaultTokenSigner())); } @@ -50,7 +55,6 @@ protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfigurati protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { List endpointInterceptors = new ArrayList<>(); endpointInterceptors.add(new JsonResolveEndpointInterceptor()); - endpointInterceptors.add(new JsonEndpointAuthSchemeInterceptor()); endpointInterceptors.add(new JsonRequestSetEndpointInterceptor()); ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); List interceptors = interceptorFactory @@ -59,7 +63,15 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon interceptors = CollectionUtils.mergeLists(endpointInterceptors, interceptors); interceptors = CollectionUtils.mergeLists(interceptors, additionalInterceptors); interceptors = CollectionUtils.mergeLists(interceptors, config.option(SdkClientOption.EXECUTION_INTERCEPTORS)); - return config.toBuilder().option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors).build(); + SdkClientConfiguration.Builder builder = config.toBuilder(); + IdentityProvider identityProvider = config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER); + if (identityProvider != null) { + IdentityProviders identityProviders = config.option(SdkClientOption.IDENTITY_PROVIDERS); + builder.option(SdkClientOption.IDENTITY_PROVIDERS, identityProviders.toBuilder() + .putIdentityProvider(identityProvider).build()); + } + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); + return builder.build(); } @Override @@ -71,7 +83,7 @@ private JsonEndpointProvider defaultEndpointProvider() { return JsonEndpointProvider.defaultProvider(); } - private SdkTokenProvider defaultTokenProvider() { + private IdentityProvider defaultTokenProvider() { return DefaultAwsTokenProvider.create(); } @@ -79,10 +91,35 @@ private Signer defaultTokenSigner() { return BearerTokenSigner.create(); } + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + protected static void validateClientOptions(SdkClientConfiguration c) { Validate.notNull(c.option(SdkAdvancedClientOption.TOKEN_SIGNER), "The 'overrideConfiguration.advancedOption[TOKEN_SIGNER]' must be configured in the client builder."); - Validate.notNull(c.option(AwsClientOption.TOKEN_PROVIDER), - "The 'overrideConfiguration.advancedOption[TOKEN_PROVIDER]' must be configured in the client builder."); + Validate.notNull(c.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER), + "The 'tokenProvider' must be configured in the client builder."); } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-interface.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-interface.java index 29957d948b8e..7a66ea0390a4 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-interface.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-interface.java @@ -3,6 +3,9 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; +import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; /** @@ -19,6 +22,14 @@ default B endpointProvider(JsonEndpointProvider endpointProvider) { throw new UnsupportedOperationException(); } + /** + * Set the {@link JsonAuthSchemeProvider} 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(JsonAuthSchemeProvider authSchemeProvider) { + throw new UnsupportedOperationException(); + } + /** * Set the token provider to use for bearer token authorization. This is optional, if none is provided, the SDK will * use {@link software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider}. @@ -30,5 +41,22 @@ default B endpointProvider(JsonEndpointProvider endpointProvider) { * {@code software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.TOKEN_SIGNER} set on the client. By * default it is {@link software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner}. */ - B tokenProvider(SdkTokenProvider tokenProvider); + default B tokenProvider(SdkTokenProvider tokenProvider) { + return tokenProvider((IdentityProvider) tokenProvider); + } + + /** + * Set the token provider to use for bearer token authorization. This is optional, if none is provided, the SDK will + * use {@link software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider}. + *

+ * If the service, or any of its operations require Bearer Token Authorization, then the SDK will default to this + * token provider to retrieve the token to use for authorization. + *

+ * This provider works in conjunction with the + * {@code software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.TOKEN_SIGNER} set on the client. By + * default it is {@link software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner}. + */ + default B tokenProvider(IdentityProvider tokenProvider) { + throw new UnsupportedOperationException(); + } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java index dc3a1ed74d74..c5c623dec7f9 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java @@ -7,22 +7,27 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.auth.signer.Aws4Signer; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider; import software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner; import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; import software.amazon.awssdk.awscore.client.config.AwsClientOption; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; 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.signer.Signer; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.services.json.endpoints.JsonClientContextParams; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; -import software.amazon.awssdk.services.json.endpoints.internal.JsonEndpointAuthSchemeInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.json.internal.SdkClientConfigurationUtil; import software.amazon.awssdk.utils.AttributeMap; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Validate; @@ -49,7 +54,7 @@ protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfigurati .option(SdkAdvancedClientOption.SIGNER, defaultSigner()) .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false) .option(SdkClientOption.SERVICE_CONFIGURATION, ServiceConfiguration.builder().build()) - .option(AwsClientOption.TOKEN_PROVIDER, defaultTokenProvider()) + .option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, defaultTokenProvider()) .option(SdkAdvancedClientOption.TOKEN_SIGNER, defaultTokenSigner())); } @@ -57,7 +62,6 @@ protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfigurati protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { List endpointInterceptors = new ArrayList<>(); endpointInterceptors.add(new JsonResolveEndpointInterceptor()); - endpointInterceptors.add(new JsonEndpointAuthSchemeInterceptor()); endpointInterceptors.add(new JsonRequestSetEndpointInterceptor()); ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); List interceptors = interceptorFactory @@ -68,9 +72,8 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon interceptors = CollectionUtils.mergeLists(interceptors, config.option(SdkClientOption.EXECUTION_INTERCEPTORS)); ServiceConfiguration.Builder serviceConfigBuilder = ((ServiceConfiguration) config .option(SdkClientOption.SERVICE_CONFIGURATION)).toBuilder(); - serviceConfigBuilder.profileFile(serviceConfigBuilder.profileFileSupplier() != null ? - serviceConfigBuilder.profileFileSupplier() : - config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)); + serviceConfigBuilder.profileFile(serviceConfigBuilder.profileFileSupplier() != null ? serviceConfigBuilder + .profileFileSupplier() : config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)); serviceConfigBuilder.profileName(serviceConfigBuilder.profileName() != null ? serviceConfigBuilder.profileName() : config .option(SdkClientOption.PROFILE_NAME)); if (serviceConfigBuilder.dualstackEnabled() != null) { @@ -122,11 +125,19 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon !finalServiceConfig.multiRegionEnabled()); clientContextParams.put(JsonClientContextParams.FORCE_PATH_STYLE, finalServiceConfig.pathStyleAccessEnabled()); clientContextParams.put(JsonClientContextParams.ACCELERATE, finalServiceConfig.accelerateModeEnabled()); - return config.toBuilder().option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED, finalServiceConfig.dualstackEnabled()) - .option(AwsClientOption.FIPS_ENDPOINT_ENABLED, finalServiceConfig.fipsModeEnabled()) - .option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors) - .option(SdkClientOption.RETRY_POLICY, MyServiceRetryPolicy.resolveRetryPolicy(config)) - .option(SdkClientOption.SERVICE_CONFIGURATION, finalServiceConfig).build(); + SdkClientConfiguration.Builder builder = config.toBuilder(); + IdentityProvider identityProvider = config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER); + if (identityProvider != null) { + IdentityProviders identityProviders = config.option(SdkClientOption.IDENTITY_PROVIDERS); + builder.option(SdkClientOption.IDENTITY_PROVIDERS, identityProviders.toBuilder() + .putIdentityProvider(identityProvider).build()); + } + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors) + .option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED, finalServiceConfig.dualstackEnabled()) + .option(AwsClientOption.FIPS_ENDPOINT_ENABLED, finalServiceConfig.fipsModeEnabled()) + .option(SdkClientOption.RETRY_POLICY, MyServiceRetryPolicy.resolveRetryPolicy(config)) + .option(SdkClientOption.SERVICE_CONFIGURATION, finalServiceConfig); + return builder.build(); } private Signer defaultSigner() { @@ -151,7 +162,7 @@ public void setServiceConfiguration(ServiceConfiguration serviceConfiguration) { serviceConfiguration(serviceConfiguration); } - private SdkTokenProvider defaultTokenProvider() { + private IdentityProvider defaultTokenProvider() { return DefaultAwsTokenProvider.create(); } @@ -159,18 +170,43 @@ private Signer defaultTokenSigner() { return BearerTokenSigner.create(); } + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + @Override protected final AttributeMap serviceHttpConfig() { AttributeMap result = MyServiceHttpConfig.defaultHttpConfig(); return result; } + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + protected static void validateClientOptions(SdkClientConfiguration c) { Validate.notNull(c.option(SdkAdvancedClientOption.SIGNER), "The 'overrideConfiguration.advancedOption[SIGNER]' must be configured in the client builder."); Validate.notNull(c.option(SdkAdvancedClientOption.TOKEN_SIGNER), "The 'overrideConfiguration.advancedOption[TOKEN_SIGNER]' must be configured in the client builder."); - Validate.notNull(c.option(AwsClientOption.TOKEN_PROVIDER), - "The 'overrideConfiguration.advancedOption[TOKEN_PROVIDER]' must be configured in the client builder."); + Validate.notNull(c.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER), + "The 'tokenProvider' must be configured in the client builder."); } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-endpoints-auth-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-endpoints-auth-params.java new file mode 100644 index 000000000000..15a3b171ec0f --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-endpoints-auth-params.java @@ -0,0 +1,149 @@ +package software.amazon.awssdk.services.query; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.signer.Aws4Signer; +import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider; +import software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +import software.amazon.awssdk.awscore.client.config.AwsClientOption; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +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.signer.Signer; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.identity.spi.TokenIdentity; +import software.amazon.awssdk.protocols.query.interceptor.QueryParametersToBodyInterceptor; +import software.amazon.awssdk.services.query.endpoints.QueryClientContextParams; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; +import software.amazon.awssdk.services.query.endpoints.internal.QueryRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.query.endpoints.internal.QueryResolveEndpointInterceptor; +import software.amazon.awssdk.services.query.internal.QueryServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.query.internal.SdkClientConfigurationUtil; +import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.Validate; + +/** + * Internal base class for {@link DefaultQueryClientBuilder} and {@link DefaultQueryAsyncClientBuilder}. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +abstract class DefaultQueryBaseClientBuilder, C> extends AwsDefaultClientBuilder { + @Override + protected final String serviceEndpointPrefix() { + return "query-service"; + } + + @Override + protected final String serviceName() { + return "Query"; + } + + @Override + protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfiguration config) { + return config.merge(c -> c.option(SdkClientOption.ENDPOINT_PROVIDER, defaultEndpointProvider()) + .option(SdkAdvancedClientOption.SIGNER, defaultSigner()) + .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false) + .option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, defaultTokenProvider()) + .option(SdkAdvancedClientOption.TOKEN_SIGNER, defaultTokenSigner())); + } + + @Override + protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { + List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new QueryResolveEndpointInterceptor()); + endpointInterceptors.add(new QueryRequestSetEndpointInterceptor()); + ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); + List interceptors = interceptorFactory + .getInterceptors("software/amazon/awssdk/services/query/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)); + List protocolInterceptors = Collections.singletonList(new QueryParametersToBodyInterceptor()); + interceptors = CollectionUtils.mergeLists(interceptors, protocolInterceptors); + SdkClientConfiguration.Builder builder = config.toBuilder(); + IdentityProvider identityProvider = config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER); + if (identityProvider != null) { + IdentityProviders identityProviders = config.option(SdkClientOption.IDENTITY_PROVIDERS); + builder.option(SdkClientOption.IDENTITY_PROVIDERS, identityProviders.toBuilder() + .putIdentityProvider(identityProvider).build()); + } + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors).option(SdkClientOption.CLIENT_CONTEXT_PARAMS, + clientContextParams.build()); + return builder.build(); + } + + private Signer defaultSigner() { + return Aws4Signer.create(); + } + + @Override + protected final String signingName() { + return "query-service"; + } + + private QueryEndpointProvider defaultEndpointProvider() { + return QueryEndpointProvider.defaultProvider(); + } + + public B booleanContextParam(Boolean booleanContextParam) { + clientContextParams.put(QueryClientContextParams.BOOLEAN_CONTEXT_PARAM, booleanContextParam); + return thisBuilder(); + } + + public B stringContextParam(String stringContextParam) { + clientContextParams.put(QueryClientContextParams.STRING_CONTEXT_PARAM, stringContextParam); + return thisBuilder(); + } + + private IdentityProvider defaultTokenProvider() { + return DefaultAwsTokenProvider.create(); + } + + private Signer defaultTokenSigner() { + return BearerTokenSigner.create(); + } + + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + QueryServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = QueryServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + Validate.notNull(c.option(SdkAdvancedClientOption.SIGNER), + "The 'overrideConfiguration.advancedOption[SIGNER]' must be configured in the client builder."); + Validate.notNull(c.option(SdkAdvancedClientOption.TOKEN_SIGNER), + "The 'overrideConfiguration.advancedOption[TOKEN_SIGNER]' must be configured in the client builder."); + Validate.notNull(c.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER), + "The 'tokenProvider' must be configured in the client builder."); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-interface.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-interface.java index 2de699ac4aff..b715a8080c94 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-interface.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-interface.java @@ -4,6 +4,9 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; +import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; /** @@ -26,6 +29,14 @@ default B endpointProvider(JsonEndpointProvider endpointProvider) { throw new UnsupportedOperationException(); } + /** + * Set the {@link JsonAuthSchemeProvider} 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(JsonAuthSchemeProvider authSchemeProvider) { + throw new UnsupportedOperationException(); + } + /** * Set the token provider to use for bearer token authorization. This is optional, if none is provided, the SDK will * use {@link software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider}. @@ -37,5 +48,22 @@ default B endpointProvider(JsonEndpointProvider endpointProvider) { * {@code software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.TOKEN_SIGNER} set on the client. By * default it is {@link software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner}. */ - B tokenProvider(SdkTokenProvider tokenProvider); + default B tokenProvider(SdkTokenProvider tokenProvider) { + return tokenProvider((IdentityProvider) tokenProvider); + } + + /** + * Set the token provider to use for bearer token authorization. This is optional, if none is provided, the SDK will + * use {@link software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider}. + *

+ * If the service, or any of its operations require Bearer Token Authorization, then the SDK will default to this + * token provider to retrieve the token to use for authorization. + *

+ * This provider works in conjunction with the + * {@code software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.TOKEN_SIGNER} set on the client. By + * default it is {@link software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner}. + */ + default B tokenProvider(IdentityProvider tokenProvider) { + throw new UnsupportedOperationException(); + } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java index 71980c2daeb1..bf5b9701d42f 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java @@ -6,6 +6,8 @@ import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.auth.signer.Aws4Signer; import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; @@ -14,9 +16,10 @@ import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; -import software.amazon.awssdk.services.json.endpoints.internal.JsonEndpointAuthSchemeInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.json.internal.SdkClientConfigurationUtil; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Validate; @@ -55,7 +58,6 @@ protected final SdkClientConfiguration mergeInternalDefaults(SdkClientConfigurat protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { List endpointInterceptors = new ArrayList<>(); endpointInterceptors.add(new JsonResolveEndpointInterceptor()); - endpointInterceptors.add(new JsonEndpointAuthSchemeInterceptor()); endpointInterceptors.add(new JsonRequestSetEndpointInterceptor()); ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); List interceptors = interceptorFactory @@ -64,7 +66,9 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon interceptors = CollectionUtils.mergeLists(endpointInterceptors, interceptors); interceptors = CollectionUtils.mergeLists(interceptors, additionalInterceptors); interceptors = CollectionUtils.mergeLists(interceptors, config.option(SdkClientOption.EXECUTION_INTERCEPTORS)); - return config.toBuilder().option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors).build(); + SdkClientConfiguration.Builder builder = config.toBuilder(); + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); + return builder.build(); } private Signer defaultSigner() { @@ -80,6 +84,31 @@ private JsonEndpointProvider defaultEndpointProvider() { return JsonEndpointProvider.defaultProvider(); } + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + protected static void validateClientOptions(SdkClientConfiguration c) { Validate.notNull(c.option(SdkAdvancedClientOption.SIGNER), "The 'overrideConfiguration.advancedOption[SIGNER]' must be configured in the client builder."); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-async-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-async-client-builder-class.java index a95cb03bd177..210c9dce33de 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-async-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-async-client-builder-class.java @@ -3,11 +3,11 @@ import java.net.URI; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; -import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.services.builder.AsyncClientDecorator; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; @@ -17,7 +17,7 @@ @Generated("software.amazon.awssdk:codegen") @SdkInternalApi final class DefaultJsonAsyncClientBuilder extends DefaultJsonBaseClientBuilder implements - JsonAsyncClientBuilder { + JsonAsyncClientBuilder { @Override public DefaultJsonAsyncClientBuilder endpointProvider(JsonEndpointProvider endpointProvider) { clientConfiguration.option(SdkClientOption.ENDPOINT_PROVIDER, endpointProvider); @@ -25,8 +25,8 @@ public DefaultJsonAsyncClientBuilder endpointProvider(JsonEndpointProvider endpo } @Override - public DefaultJsonAsyncClientBuilder tokenProvider(SdkTokenProvider tokenProvider) { - clientConfiguration.option(AwsClientOption.TOKEN_PROVIDER, tokenProvider); + public DefaultJsonAsyncClientBuilder tokenProvider(IdentityProvider tokenProvider) { + clientConfiguration.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, tokenProvider); return this; } @@ -41,13 +41,11 @@ protected final JsonAsyncClient buildClient() { private JsonServiceClientConfiguration initializeServiceClientConfig(SdkClientConfiguration clientConfig) { URI endpointOverride = null; - EndpointProvider endpointProvider = clientConfig.option(SdkClientOption.ENDPOINT_PROVIDER); - if (clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN) != null - && Boolean.TRUE.equals(clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN))) { + if (Boolean.TRUE.equals(clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN))) { endpointOverride = clientConfig.option(SdkClientOption.ENDPOINT); } return JsonServiceClientConfiguration.builder().overrideConfiguration(overrideConfiguration()) - .region(clientConfig.option(AwsClientOption.AWS_REGION)).endpointOverride(endpointOverride) - .endpointProvider(endpointProvider).build(); + .region(clientConfig.option(AwsClientOption.AWS_REGION)).endpointOverride(endpointOverride) + .endpointProvider(clientConfig.option(SdkClientOption.ENDPOINT_PROVIDER)).build(); } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-client-builder-class.java index 47887e3135c2..1c73c05fc729 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-client-builder-class.java @@ -3,11 +3,11 @@ import java.net.URI; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; -import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.services.builder.SyncClientDecorator; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; @@ -17,7 +17,7 @@ @Generated("software.amazon.awssdk:codegen") @SdkInternalApi final class DefaultJsonClientBuilder extends DefaultJsonBaseClientBuilder implements - JsonClientBuilder { + JsonClientBuilder { @Override public DefaultJsonClientBuilder endpointProvider(JsonEndpointProvider endpointProvider) { clientConfiguration.option(SdkClientOption.ENDPOINT_PROVIDER, endpointProvider); @@ -25,8 +25,8 @@ public DefaultJsonClientBuilder endpointProvider(JsonEndpointProvider endpointPr } @Override - public DefaultJsonClientBuilder tokenProvider(SdkTokenProvider tokenProvider) { - clientConfiguration.option(AwsClientOption.TOKEN_PROVIDER, tokenProvider); + public DefaultJsonClientBuilder tokenProvider(IdentityProvider tokenProvider) { + clientConfiguration.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, tokenProvider); return this; } @@ -41,13 +41,11 @@ protected final JsonClient buildClient() { private JsonServiceClientConfiguration initializeServiceClientConfig(SdkClientConfiguration clientConfig) { URI endpointOverride = null; - EndpointProvider endpointProvider = clientConfig.option(SdkClientOption.ENDPOINT_PROVIDER); - if (clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN) != null - && Boolean.TRUE.equals(clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN))) { + if (Boolean.TRUE.equals(clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN))) { endpointOverride = clientConfig.option(SdkClientOption.ENDPOINT); } return JsonServiceClientConfiguration.builder().overrideConfiguration(overrideConfiguration()) - .region(clientConfig.option(AwsClientOption.AWS_REGION)).endpointOverride(endpointOverride) - .endpointProvider(endpointProvider).build(); + .region(clientConfig.option(AwsClientOption.AWS_REGION)).endpointOverride(endpointOverride) + .endpointProvider(clientConfig.option(SdkClientOption.ENDPOINT_PROVIDER)).build(); } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-default-client-builder.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-default-client-builder.java index b352d7b685dd..da13e25a7e0b 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-default-client-builder.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-default-client-builder.java @@ -5,22 +5,27 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.auth.signer.Aws4Signer; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider; import software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner; import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; import software.amazon.awssdk.awscore.client.config.AwsClientOption; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; 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.signer.Signer; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.services.json.endpoints.JsonClientContextParams; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; -import software.amazon.awssdk.services.json.endpoints.internal.JsonEndpointAuthSchemeInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.json.internal.SdkClientConfigurationUtil; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Validate; @@ -46,7 +51,7 @@ protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfigurati .option(SdkAdvancedClientOption.SIGNER, defaultSigner()) .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false) .option(SdkClientOption.SERVICE_CONFIGURATION, ServiceConfiguration.builder().build()) - .option(AwsClientOption.TOKEN_PROVIDER, defaultTokenProvider()) + .option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, defaultTokenProvider()) .option(SdkAdvancedClientOption.TOKEN_SIGNER, defaultTokenSigner())); } @@ -54,7 +59,6 @@ protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfigurati protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { List endpointInterceptors = new ArrayList<>(); endpointInterceptors.add(new JsonResolveEndpointInterceptor()); - endpointInterceptors.add(new JsonEndpointAuthSchemeInterceptor()); endpointInterceptors.add(new JsonRequestSetEndpointInterceptor()); ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); List interceptors = interceptorFactory @@ -70,8 +74,16 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon serviceConfigBuilder.profileName(serviceConfigBuilder.profileName() != null ? serviceConfigBuilder.profileName() : config .option(SdkClientOption.PROFILE_NAME)); ServiceConfiguration finalServiceConfig = serviceConfigBuilder.build(); - return config.toBuilder().option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors) - .option(SdkClientOption.SERVICE_CONFIGURATION, finalServiceConfig).build(); + SdkClientConfiguration.Builder builder = config.toBuilder(); + IdentityProvider identityProvider = config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER); + if (identityProvider != null) { + IdentityProviders identityProviders = config.option(SdkClientOption.IDENTITY_PROVIDERS); + builder.option(SdkClientOption.IDENTITY_PROVIDERS, identityProviders.toBuilder() + .putIdentityProvider(identityProvider).build()); + } + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors).option(SdkClientOption.SERVICE_CONFIGURATION, + finalServiceConfig); + return builder.build(); } private Signer defaultSigner() { @@ -101,7 +113,7 @@ public void setServiceConfiguration(ServiceConfiguration serviceConfiguration) { serviceConfiguration(serviceConfiguration); } - private SdkTokenProvider defaultTokenProvider() { + private IdentityProvider defaultTokenProvider() { return DefaultAwsTokenProvider.create(); } @@ -109,12 +121,37 @@ private Signer defaultTokenSigner() { return BearerTokenSigner.create(); } + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + protected static void validateClientOptions(SdkClientConfiguration c) { Validate.notNull(c.option(SdkAdvancedClientOption.SIGNER), "The 'overrideConfiguration.advancedOption[SIGNER]' must be configured in the client builder."); Validate.notNull(c.option(SdkAdvancedClientOption.TOKEN_SIGNER), "The 'overrideConfiguration.advancedOption[TOKEN_SIGNER]' must be configured in the client builder."); - Validate.notNull(c.option(AwsClientOption.TOKEN_PROVIDER), - "The 'overrideConfiguration.advancedOption[TOKEN_PROVIDER]' must be configured in the client builder."); + Validate.notNull(c.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER), + "The 'tokenProvider' must be configured in the client builder."); } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-customcontextparams-sync-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-customcontextparams-sync-client-builder-class.java index 3c39bb9fc9c0..2dfe82dca860 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-customcontextparams-sync-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-customcontextparams-sync-client-builder-class.java @@ -4,6 +4,9 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; +import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; /** @@ -26,6 +29,14 @@ default B endpointProvider(JsonEndpointProvider endpointProvider) { throw new UnsupportedOperationException(); } + /** + * Set the {@link JsonAuthSchemeProvider} 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(JsonAuthSchemeProvider authSchemeProvider) { + throw new UnsupportedOperationException(); + } + /** * Enables this client to use Custom Parameter */ @@ -42,5 +53,22 @@ default B endpointProvider(JsonEndpointProvider endpointProvider) { * {@code software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.TOKEN_SIGNER} set on the client. By * default it is {@link software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner}. */ - B tokenProvider(SdkTokenProvider tokenProvider); + default B tokenProvider(SdkTokenProvider tokenProvider) { + return tokenProvider((IdentityProvider) tokenProvider); + } + + /** + * Set the token provider to use for bearer token authorization. This is optional, if none is provided, the SDK will + * use {@link software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider}. + *

+ * If the service, or any of its operations require Bearer Token Authorization, then the SDK will default to this + * token provider to retrieve the token to use for authorization. + *

+ * This provider works in conjunction with the + * {@code software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.TOKEN_SIGNER} set on the client. By + * default it is {@link software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner}. + */ + default B tokenProvider(IdentityProvider tokenProvider) { + throw new UnsupportedOperationException(); + } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-ops-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-ops-client-builder-class.java new file mode 100644 index 000000000000..c8e43faa5e44 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-ops-client-builder-class.java @@ -0,0 +1,108 @@ +package software.amazon.awssdk.services.database; + +import java.util.ArrayList; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.signer.Aws4Signer; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +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.signer.Signer; +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.services.database.internal.SdkClientConfigurationUtil; +import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.Validate; + +/** + * Internal base class for {@link DefaultDatabaseClientBuilder} and {@link DefaultDatabaseAsyncClientBuilder}. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +abstract class DefaultDatabaseBaseClientBuilder, C> extends + AwsDefaultClientBuilder { + @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(SdkAdvancedClientOption.SIGNER, defaultSigner()) + .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false)); + } + + @Override + protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { + List endpointInterceptors = new ArrayList<>(); + 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.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); + return builder.build(); + } + + private Signer defaultSigner() { + return Aws4Signer.create(); + } + + @Override + protected final String signingName() { + return "database-service"; + } + + private DatabaseEndpointProvider defaultEndpointProvider() { + return DatabaseEndpointProvider.defaultProvider(); + } + + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + DatabaseServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = DatabaseServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + Validate.notNull(c.option(SdkAdvancedClientOption.SIGNER), + "The 'overrideConfiguration.advancedOption[SIGNER]' must be configured in the client builder."); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-service-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-service-client-builder-class.java new file mode 100644 index 000000000000..cc5f0b4838d8 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-service-client-builder-class.java @@ -0,0 +1,97 @@ +package software.amazon.awssdk.services.database; + +import java.util.ArrayList; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +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.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.services.database.internal.SdkClientConfigurationUtil; +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 { + @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.CRC32_FROM_COMPRESSED_DATA_ENABLED, false)); + } + + @Override + protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { + List endpointInterceptors = new ArrayList<>(); + 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.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); + return builder.build(); + } + + @Override + protected final String signingName() { + return "database-service"; + } + + private DatabaseEndpointProvider defaultEndpointProvider() { + return DatabaseEndpointProvider.defaultProvider(); + } + + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + DatabaseServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = DatabaseServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-query-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-query-client-builder-class.java index 141b27f6cfe0..15a3b171ec0f 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-query-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-query-client-builder-class.java @@ -6,23 +6,28 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.auth.signer.Aws4Signer; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider; import software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner; import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; import software.amazon.awssdk.awscore.client.config.AwsClientOption; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; 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.signer.Signer; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.protocols.query.interceptor.QueryParametersToBodyInterceptor; import software.amazon.awssdk.services.query.endpoints.QueryClientContextParams; import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; -import software.amazon.awssdk.services.query.endpoints.internal.QueryEndpointAuthSchemeInterceptor; import software.amazon.awssdk.services.query.endpoints.internal.QueryRequestSetEndpointInterceptor; import software.amazon.awssdk.services.query.endpoints.internal.QueryResolveEndpointInterceptor; +import software.amazon.awssdk.services.query.internal.QueryServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.query.internal.SdkClientConfigurationUtil; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Validate; @@ -47,7 +52,7 @@ protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfigurati return config.merge(c -> c.option(SdkClientOption.ENDPOINT_PROVIDER, defaultEndpointProvider()) .option(SdkAdvancedClientOption.SIGNER, defaultSigner()) .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false) - .option(AwsClientOption.TOKEN_PROVIDER, defaultTokenProvider()) + .option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, defaultTokenProvider()) .option(SdkAdvancedClientOption.TOKEN_SIGNER, defaultTokenSigner())); } @@ -55,7 +60,6 @@ protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfigurati protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { List endpointInterceptors = new ArrayList<>(); endpointInterceptors.add(new QueryResolveEndpointInterceptor()); - endpointInterceptors.add(new QueryEndpointAuthSchemeInterceptor()); endpointInterceptors.add(new QueryRequestSetEndpointInterceptor()); ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); List interceptors = interceptorFactory @@ -66,8 +70,16 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon interceptors = CollectionUtils.mergeLists(interceptors, config.option(SdkClientOption.EXECUTION_INTERCEPTORS)); List protocolInterceptors = Collections.singletonList(new QueryParametersToBodyInterceptor()); interceptors = CollectionUtils.mergeLists(interceptors, protocolInterceptors); - return config.toBuilder().option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors) - .option(SdkClientOption.CLIENT_CONTEXT_PARAMS, clientContextParams.build()).build(); + SdkClientConfiguration.Builder builder = config.toBuilder(); + IdentityProvider identityProvider = config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER); + if (identityProvider != null) { + IdentityProviders identityProviders = config.option(SdkClientOption.IDENTITY_PROVIDERS); + builder.option(SdkClientOption.IDENTITY_PROVIDERS, identityProviders.toBuilder() + .putIdentityProvider(identityProvider).build()); + } + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors).option(SdkClientOption.CLIENT_CONTEXT_PARAMS, + clientContextParams.build()); + return builder.build(); } private Signer defaultSigner() { @@ -93,7 +105,7 @@ public B stringContextParam(String stringContextParam) { return thisBuilder(); } - private SdkTokenProvider defaultTokenProvider() { + private IdentityProvider defaultTokenProvider() { return DefaultAwsTokenProvider.create(); } @@ -101,12 +113,37 @@ private Signer defaultTokenSigner() { return BearerTokenSigner.create(); } + @Override + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + if (overrideConfiguration == null) { + return configuration; + } + return SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, configuration.toBuilder()).build(); + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List plugins = plugins(); + if (plugins.isEmpty()) { + return config; + } + QueryServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = QueryServiceClientConfigurationBuilder + .builder(config.toBuilder()); + serviceConfigBuilder.overrideConfiguration(overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + overrideConfiguration(serviceConfigBuilder.overrideConfiguration()); + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + protected static void validateClientOptions(SdkClientConfiguration c) { Validate.notNull(c.option(SdkAdvancedClientOption.SIGNER), "The 'overrideConfiguration.advancedOption[SIGNER]' must be configured in the client builder."); Validate.notNull(c.option(SdkAdvancedClientOption.TOKEN_SIGNER), "The 'overrideConfiguration.advancedOption[TOKEN_SIGNER]' must be configured in the client builder."); - Validate.notNull(c.option(AwsClientOption.TOKEN_PROVIDER), - "The 'overrideConfiguration.advancedOption[TOKEN_PROVIDER]' must be configured in the client builder."); + Validate.notNull(c.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER), + "The 'tokenProvider' must be configured in the client builder."); } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-sync-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-sync-client-builder-class.java index 724519ba9aa3..f5e86474a8bd 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-sync-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-sync-client-builder-class.java @@ -3,11 +3,11 @@ import java.net.URI; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; -import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; /** @@ -16,7 +16,7 @@ @Generated("software.amazon.awssdk:codegen") @SdkInternalApi final class DefaultJsonClientBuilder extends DefaultJsonBaseClientBuilder implements - JsonClientBuilder { + JsonClientBuilder { @Override public DefaultJsonClientBuilder endpointProvider(JsonEndpointProvider endpointProvider) { clientConfiguration.option(SdkClientOption.ENDPOINT_PROVIDER, endpointProvider); @@ -24,8 +24,8 @@ public DefaultJsonClientBuilder endpointProvider(JsonEndpointProvider endpointPr } @Override - public DefaultJsonClientBuilder tokenProvider(SdkTokenProvider tokenProvider) { - clientConfiguration.option(AwsClientOption.TOKEN_PROVIDER, tokenProvider); + public DefaultJsonClientBuilder tokenProvider(IdentityProvider tokenProvider) { + clientConfiguration.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER, tokenProvider); return this; } @@ -40,13 +40,11 @@ protected final JsonClient buildClient() { private JsonServiceClientConfiguration initializeServiceClientConfig(SdkClientConfiguration clientConfig) { URI endpointOverride = null; - EndpointProvider endpointProvider = clientConfig.option(SdkClientOption.ENDPOINT_PROVIDER); - if (clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN) != null - && Boolean.TRUE.equals(clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN))) { + if (Boolean.TRUE.equals(clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN))) { endpointOverride = clientConfig.option(SdkClientOption.ENDPOINT); } return JsonServiceClientConfiguration.builder().overrideConfiguration(overrideConfiguration()) - .region(clientConfig.option(AwsClientOption.AWS_REGION)).endpointOverride(endpointOverride) - .endpointProvider(endpointProvider).build(); + .region(clientConfig.option(AwsClientOption.AWS_REGION)).endpointOverride(endpointOverride) + .endpointProvider(clientConfig.option(SdkClientOption.ENDPOINT_PROVIDER)).build(); } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-different-value/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-different-value/customization.config new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-different-value/customization.config @@ -0,0 +1,2 @@ +{ +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-different-value/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-different-value/service-2.json new file mode 100644 index 000000000000..94e6e0281ae6 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-different-value/service-2.json @@ -0,0 +1,153 @@ +{ + "version": "2.0", + "metadata": { + "apiVersion": "2023-06-08", + "endpointPrefix": "database-service-endpoint", + "globalEndpoint": "database-service.amazonaws.com", + "protocol": "rest-json", + "serviceAbbreviation": "Database Service", + "serviceFullName": "Some Service That Uses AWS Database Protocol", + "serviceId": "Database Service", + "signingName": "database-service", + "signatureVersion": "v4", + "auth": ["v4", "bearer"], + "uid": "database-service-2023-06-08", + "xmlNamespace": "https://database-service.amazonaws.com/doc/2023-06-08/" + }, + "operations": { + "GetRow": { + "name": "GetRow", + "auth": ["bearer"], + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row operation no output

" + }, + "PutRow": { + "name": "PutRow", + "auth": ["v4"], + "http": { + "method": "PUT", + "requestUri": "/put-row/" + }, + "input": { + "shape": "PutRowRequest" + }, + "output": { + "shape": "PutRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row operation no output

" + }, + "DeleteRow": { + "name": "DeleteRow", + "auth": [ "bearer", "v4"], + "http": { + "method": "DELETE", + "requestUri": "/delete-row/" + }, + "input": { + "shape": "DeleteRowRequest" + }, + "output": { + "shape": "DeleteRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row operation no output

" + } + }, + "shapes": { + "GetRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "GetRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "PutRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "PutRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "DeleteRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "DeleteRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "InvalidInputException": { + "type": "structure", + "members": { + "message": { + "shape": "invalidInputMessage" + } + }, + "documentation": "

The request was rejected because an invalid or out-of-range value was supplied for an input parameter.

", + "error": { + "code": "InvalidInput", + "httpStatusCode": 400, + "senderFault": true + }, + "exception": true + }, + "String":{"type":"string"}, + "invalidInputMessage":{"type":"string"} + }, + "documentation": "A Database Service with Fine granularity authorization schemes" +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-same-value/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-same-value/customization.config new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-same-value/customization.config @@ -0,0 +1,2 @@ +{ +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-same-value/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-same-value/service-2.json new file mode 100644 index 000000000000..d5a17473113d --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-same-value/service-2.json @@ -0,0 +1,153 @@ +{ + "version": "2.0", + "metadata": { + "apiVersion": "2023-06-08", + "endpointPrefix": "database-service-endpoint", + "globalEndpoint": "database-service.amazonaws.com", + "protocol": "rest-json", + "serviceAbbreviation": "Database Service", + "serviceFullName": "Some Service That Uses AWS Database Protocol", + "serviceId": "Database Service", + "signingName": "database-service", + "signatureVersion": "v4", + "auth": ["v4", "bearer"], + "uid": "database-service-2023-06-08", + "xmlNamespace": "https://database-service.amazonaws.com/doc/2023-06-08/" + }, + "operations": { + "GetRow": { + "name": "GetRow", + "auth": ["v4", "bearer"], + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row operation no output

" + }, + "PutRow": { + "name": "PutRow", + "auth": ["v4", "bearer"], + "http": { + "method": "PUT", + "requestUri": "/put-row/" + }, + "input": { + "shape": "PutRowRequest" + }, + "output": { + "shape": "PutRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row operation no output

" + }, + "DeleteRow": { + "name": "DeleteRow", + "auth": ["v4", "bearer"], + "http": { + "method": "DELETE", + "requestUri": "/delete-row/" + }, + "input": { + "shape": "DeleteRowRequest" + }, + "output": { + "shape": "DeleteRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row operation no output

" + } + }, + "shapes": { + "GetRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "GetRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "PutRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "PutRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "DeleteRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "DeleteRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "InvalidInputException": { + "type": "structure", + "members": { + "message": { + "shape": "invalidInputMessage" + } + }, + "documentation": "

The request was rejected because an invalid or out-of-range value was supplied for an input parameter.

", + "error": { + "code": "InvalidInput", + "httpStatusCode": 400, + "senderFault": true + }, + "exception": true + }, + "String":{"type":"string"}, + "invalidInputMessage":{"type":"string"} + }, + "documentation": "A Database Service with Fine granularity authorization schemes" +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth-legacy-trait/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth-legacy-trait/customization.config new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth-legacy-trait/customization.config @@ -0,0 +1,2 @@ +{ +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth-legacy-trait/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth-legacy-trait/service-2.json new file mode 100644 index 000000000000..a3035305456b --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth-legacy-trait/service-2.json @@ -0,0 +1,58 @@ +{ + "version":"2.0", + "metadata":{ + "apiVersion":"2023-07-24", + "endpointPrefix":"authwithlegacytraitservice", + "jsonVersion":"1.1", + "protocol":"rest-json", + "serviceAbbreviation":"AuthWithLegacyTraitService", + "serviceFullName":"Amazon Auth With Legacy Trait Service", + "serviceId":"AmazonAuthWithLegacyTraitService", + "signatureVersion":"v4", + "targetPrefix":"ProtocolTestsService", + "timestampFormat":"unixTimestamp", + "uid":"authwithlegacytraitservice-2023-07-24" + }, + "operations":{ + "OperationWithDefaultAuth":{ + "name":"OperationWithDefaultAuth", + "http":{ + "method":"POST", + "requestUri":"/2023-07-24/withDefaultAuth" + }, + "input":{"shape":"SomeStructure"}, + "output":{"shape":"SomeStructure"} + }, + "OperationWithBearerAuth":{ + "name":"OperationWithDefaultAuth", + "http":{ + "method":"POST", + "requestUri":"/2023-07-24/withBearerAuth" + }, + "input":{"shape":"SomeStructure"}, + "output":{"shape":"SomeStructure"}, + "authtype": "bearer" + }, + "OperationWithNoAuth":{ + "name":"OperationWithDefaultAuth", + "http":{ + "method":"POST", + "requestUri":"/2023-07-24/withNoAuth" + }, + "input":{"shape":"SomeStructure"}, + "output":{"shape":"SomeStructure"}, + "authtype": "none" + } + }, + "shapes":{ + "SomeStructure":{ + "type":"structure", + "members":{ + "StringMember":{"shape":"String"} + } + }, + "String": { + "type": "string" + } + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/customization.config new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/customization.config @@ -0,0 +1,2 @@ +{ +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/paginators.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/paginators.json new file mode 100644 index 000000000000..492e5283d730 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/paginators.json @@ -0,0 +1,4 @@ +{ + "pagination": { + } +} \ No newline at end of file diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/service-2.json new file mode 100644 index 000000000000..7b3a0efc911e --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/service-2.json @@ -0,0 +1,231 @@ +{ + "version": "2.0", + "metadata": { + "apiVersion": "2023-06-08", + "endpointPrefix": "database-service-endpoint", + "globalEndpoint": "database-service.amazonaws.com", + "protocol": "rest-json", + "serviceAbbreviation": "Database Service", + "serviceFullName": "Some Service That Uses AWS Database Protocol", + "serviceId": "Database Service", + "signingName": "database-service", + "signatureVersion": "v4", + "auth": ["bearer","v4"], + "uid": "database-service-2023-06-08", + "xmlNamespace": "https://database-service.amazonaws.com/doc/2023-06-08/" + }, + "operations": { + "GetRow": { + "name": "GetRow", + "auth": ["v4", "bearer"], + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row operation no output

" + }, + "GetRowV2": { + "name": "GetRow", + "auth": ["v4", "bearer"], + "http": { + "method": "GET", + "requestUri": "/get-row-v2/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row V2 operation no output

" + }, + "ListRows": { + "name": "ListRows", + "auth": ["bearer"], + "http": { + "method": "GET", + "requestUri": "/list-rows/" + }, + "input": { + "shape": "ListRowsRequest" + }, + "output": { + "shape": "ListRowsResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a list rows operation no output

" + }, + "PutRow": { + "name": "PutRow", + "auth": ["v4"], + "http": { + "method": "PUT", + "requestUri": "/put-row/" + }, + "input": { + "shape": "PutRowRequest" + }, + "output": { + "shape": "PutRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row operation no output

" + }, + "DeleteRow": { + "name": "DeleteRow", + "http": { + "method": "DELETE", + "requestUri": "/delete-row/" + }, + "input": { + "shape": "DeleteRowRequest" + }, + "output": { + "shape": "DeleteRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row operation no output

" + }, + "WriteRowResponse": { + "name": "DeleteRow", + "http": { + "method": "POST", + "requestUri": "/write-row-response/" + }, + "authtype":"v4-unsigned-body", + "input": { + "shape": "WriteRowResponseRequest" + }, + "documentation": "

Operation similar to S3#WriteGetObjectResponse with v4-unsigned-body auth type.

" + } + }, + "shapes": { + "GetRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "GetRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "ListRowsRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "ListRowsResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "PutRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "PutRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "DeleteRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "DeleteRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "WriteRowResponseRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "InvalidInputException": { + "type": "structure", + "members": { + "message": { + "shape": "invalidInputMessage" + } + }, + "documentation": "

The request was rejected because an invalid or out-of-range value was supplied for an input parameter.

", + "error": { + "code": "InvalidInput", + "httpStatusCode": 400, + "senderFault": true + }, + "exception": true + }, + "String":{"type":"string"}, + "invalidInputMessage":{"type":"string"} + }, + "documentation": "A Database Service with Fine granularity authorization schemes" +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/mini-s3/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/mini-s3/customization.config new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/mini-s3/customization.config @@ -0,0 +1,2 @@ +{ +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/mini-s3/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/mini-s3/service-2.json new file mode 100644 index 000000000000..c51218925b18 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/mini-s3/service-2.json @@ -0,0 +1,149 @@ +{ + "version": "2.0", + "metadata": { + "apiVersion": "2023-06-08", + "endpointPrefix": "mini-s3-service-endpoint", + "globalEndpoint": "mini-s3-service.amazonaws.com", + "protocol": "rest-json", + "serviceAbbreviation": "Mini-S3 Service", + "serviceFullName": "Some Service That Uses AWS Mini-S3 Protocol", + "serviceId": "Mini-S3 Service", + "signingName": "mini-s3-service", + "signatureVersion": "s3", + "uid": "mini-s3-service-2023-06-08", + "xmlNamespace": "https://mini-s3-service.amazonaws.com/doc/2023-06-08/" + }, + "operations": { + "GetObject": { + "name": "GetObject", + "http": { + "method": "GET", + "requestUri": "/get-object/" + }, + "input": { + "shape": "GetObjectRequest" + }, + "output": { + "shape": "GetObjectResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get object operation no output

" + }, + "PutObject": { + "name": "PutObject", + "http": { + "method": "PUT", + "requestUri": "/put-object/" + }, + "input": { + "shape": "PutObjectRequest" + }, + "output": { + "shape": "PutObjectResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get object operation no output

" + }, + "DeleteObject": { + "name": "DeleteObject", + "http": { + "method": "DELETE", + "requestUri": "/delete-object/" + }, + "input": { + "shape": "DeleteObjectRequest" + }, + "output": { + "shape": "DeleteObjectResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get object operation no output

" + } + }, + "shapes": { + "GetObjectRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "GetObjectResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "PutObjectRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "PutObjectResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "DeleteObjectRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "DeleteObjectResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "InvalidInputException": { + "type": "structure", + "members": { + "message": { + "shape": "invalidInputMessage" + } + }, + "documentation": "

The request was rejected because an invalid or out-of-range value was supplied for an input parameter.

", + "error": { + "code": "InvalidInput", + "httpStatusCode": 400, + "senderFault": true + }, + "exception": true + }, + "String":{"type":"string"}, + "invalidInputMessage":{"type":"string"} + }, + "documentation": "A Mini-S3 Service with Fine granularity authorization schemes" +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-no-auth/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-no-auth/customization.config new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-no-auth/customization.config @@ -0,0 +1,2 @@ +{ +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-no-auth/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-no-auth/service-2.json new file mode 100644 index 000000000000..4384401baaba --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-no-auth/service-2.json @@ -0,0 +1,173 @@ +{ + "version": "2.0", + "metadata": { + "apiVersion": "2023-06-08", + "endpointPrefix": "database-service-endpoint", + "globalEndpoint": "database-service.amazonaws.com", + "protocol": "rest-json", + "serviceAbbreviation": "Database Service", + "serviceFullName": "Some Service That Uses AWS Database Protocol", + "serviceId": "Database Service", + "signingName": "database-service", + "signatureVersion": "v4", + "auth": ["v4", "bearer"], + "uid": "database-service-2023-06-08", + "xmlNamespace": "https://database-service.amazonaws.com/doc/2023-06-08/" + }, + "operations": { + "GetDatabaseVersion": { + "name": "GetDatabaseVersion", + "auth": ["none"], + "http": { + "method": "GET", + "requestUri": "/get-version/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get database version op, no output and no auth.

" + }, + "GetRow": { + "name": "GetRow", + "auth": ["v4", "bearer", "none"], + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row operation no output

" + }, + "PutRow": { + "name": "PutRow", + "auth": ["v4", "bearer"], + "http": { + "method": "PUT", + "requestUri": "/put-row/" + }, + "input": { + "shape": "PutRowRequest" + }, + "output": { + "shape": "PutRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row operation no output

" + }, + "DeleteRow": { + "name": "DeleteRow", + "auth": ["v4", "bearer"], + "http": { + "method": "DELETE", + "requestUri": "/delete-row/" + }, + "input": { + "shape": "DeleteRowRequest" + }, + "output": { + "shape": "DeleteRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a get row operation no output

" + } + }, + "shapes": { + "GetRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "GetRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "PutRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "PutRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "DeleteRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "DeleteRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "InvalidInputException": { + "type": "structure", + "members": { + "message": { + "shape": "invalidInputMessage" + } + }, + "documentation": "

The request was rejected because an invalid or out-of-range value was supplied for an input parameter.

", + "error": { + "code": "InvalidInput", + "httpStatusCode": 400, + "senderFault": true + }, + "exception": true + }, + "String":{"type":"string"}, + "invalidInputMessage":{"type":"string"} + }, + "documentation": "A Database Service with Fine granularity authorization schemes" +} 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 new file mode 100644 index 000000000000..789a83e3d7ae --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-with-allowed.config @@ -0,0 +1,17 @@ +{ + "authPolicyActions": { + "skip": true + }, + "skipEndpointTests": { + "test case 4": "Does not work" + }, + "enableEndpointAuthSchemeParams": true, + "allowedEndpointAuthSchemeParams": [ + "defaultTrueParam", + "defaultStringParam", + "deprecatedParam", + "booleanContextParam", + "stringContextParam", + "operationContextParam" + ] +} 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 new file mode 100644 index 000000000000..fb6ef28ddd4b --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-without-allowed.config @@ -0,0 +1,9 @@ +{ + "authPolicyActions": { + "skip": true + }, + "skipEndpointTests": { + "test case 4": "Does not work" + }, + "enableEndpointAuthSchemeParams": true +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/endpoint-rule-set.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/endpoint-rule-set.json index e90f2aec3560..66e25ec855f1 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/endpoint-rule-set.json +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/endpoint-rule-set.json @@ -21,7 +21,8 @@ }, "defaultTrueParam": { "type": "boolean", - "default": true + "default": true, + "documentation": "A param that defauls to true" }, "defaultStringParam": { "type": "string", @@ -336,4 +337,4 @@ "type": "tree" } ] -} \ No newline at end of file +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/service-with-no-auth/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/service-with-no-auth/customization.config new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/service-with-no-auth/customization.config @@ -0,0 +1,2 @@ +{ +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/service-with-no-auth/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/service-with-no-auth/service-2.json new file mode 100644 index 000000000000..c7a4987ed99c --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/service-with-no-auth/service-2.json @@ -0,0 +1,55 @@ +{ + "version": "2.0", + "metadata": { + "apiVersion": "2023-06-08", + "endpointPrefix": "database-service-endpoint", + "globalEndpoint": "database-service.amazonaws.com", + "protocol": "rest-json", + "serviceAbbreviation": "Database Service", + "serviceFullName": "Some Service That Has No Auth At All", + "serviceId": "Database Service", + "signingName": "database-service", + "signatureVersion": "none", + "auth": [], + "uid": "database-service-2023-06-08", + "xmlNamespace": "https://database-service.amazonaws.com/doc/2023-06-08/" + }, + "operations": { + "GetDatabaseVersion": { + "name": "GetDatabaseVersion", + "http": { + "method": "GET", + "requestUri": "/get-version/" + }, + "input": { + "shape": "GetDatabaseVersionRequest" + }, + "output": { + "shape": "GetDatabaseVersionResponse" + }, + "documentation": "

Performs a get database version operation

" + } + }, + "shapes": { + "GetDatabaseVersionRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Member

" + } + } + }, + "GetDatabaseVersionResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Member

" + } + } + }, + "String":{"type":"string"} + }, + "documentation": "A Database Service with no auth on the service at all" +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-aws-json-async-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-aws-json-async-client-class.java new file mode 100644 index 000000000000..91796708f1fa --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-aws-json-async-client-class.java @@ -0,0 +1,1174 @@ +package software.amazon.awssdk.services.json; + +import static software.amazon.awssdk.utils.FunctionalUtils.runAndLogError; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import org.reactivestreams.Publisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.handler.AwsAsyncClientHandler; +import software.amazon.awssdk.awscore.client.handler.AwsClientHandlerUtils; +import software.amazon.awssdk.awscore.eventstream.EventStreamAsyncResponseTransformer; +import software.amazon.awssdk.awscore.eventstream.EventStreamTaggedUnionJsonMarshaller; +import software.amazon.awssdk.awscore.eventstream.EventStreamTaggedUnionPojoSupplier; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkPojoBuilder; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkResponse; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.core.async.AsyncResponseTransformerUtils; +import software.amazon.awssdk.core.async.SdkPublisher; +import software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.client.handler.AsyncClientHandler; +import software.amazon.awssdk.core.client.handler.AttachHttpMetadataResponseHandler; +import software.amazon.awssdk.core.client.handler.ClientExecutionParams; +import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksumRequired; +import software.amazon.awssdk.core.internal.interceptor.trait.RequestCompression; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.core.protocol.VoidSdkResponse; +import software.amazon.awssdk.core.runtime.transform.AsyncStreamingRequestMarshaller; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.NoOpMetricCollector; +import software.amazon.awssdk.protocols.core.ExceptionMetadata; +import software.amazon.awssdk.protocols.json.AwsJsonProtocol; +import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory; +import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory; +import software.amazon.awssdk.protocols.json.JsonOperationMetadata; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.json.model.APostOperationRequest; +import software.amazon.awssdk.services.json.model.APostOperationResponse; +import software.amazon.awssdk.services.json.model.APostOperationWithOutputRequest; +import software.amazon.awssdk.services.json.model.APostOperationWithOutputResponse; +import software.amazon.awssdk.services.json.model.EventStream; +import software.amazon.awssdk.services.json.model.EventStreamOperationRequest; +import software.amazon.awssdk.services.json.model.EventStreamOperationResponse; +import software.amazon.awssdk.services.json.model.EventStreamOperationResponseHandler; +import software.amazon.awssdk.services.json.model.EventStreamOperationWithOnlyInputRequest; +import software.amazon.awssdk.services.json.model.EventStreamOperationWithOnlyInputResponse; +import software.amazon.awssdk.services.json.model.EventStreamOperationWithOnlyOutputRequest; +import software.amazon.awssdk.services.json.model.EventStreamOperationWithOnlyOutputResponse; +import software.amazon.awssdk.services.json.model.EventStreamOperationWithOnlyOutputResponseHandler; +import software.amazon.awssdk.services.json.model.GetWithoutRequiredMembersRequest; +import software.amazon.awssdk.services.json.model.GetWithoutRequiredMembersResponse; +import software.amazon.awssdk.services.json.model.InputEventStream; +import software.amazon.awssdk.services.json.model.InputEventStreamTwo; +import software.amazon.awssdk.services.json.model.InvalidInputException; +import software.amazon.awssdk.services.json.model.JsonException; +import software.amazon.awssdk.services.json.model.OperationWithChecksumRequiredRequest; +import software.amazon.awssdk.services.json.model.OperationWithChecksumRequiredResponse; +import software.amazon.awssdk.services.json.model.OperationWithNoneAuthTypeRequest; +import software.amazon.awssdk.services.json.model.OperationWithNoneAuthTypeResponse; +import software.amazon.awssdk.services.json.model.OperationWithRequestCompressionRequest; +import software.amazon.awssdk.services.json.model.OperationWithRequestCompressionResponse; +import software.amazon.awssdk.services.json.model.PaginatedOperationWithResultKeyRequest; +import software.amazon.awssdk.services.json.model.PaginatedOperationWithResultKeyResponse; +import software.amazon.awssdk.services.json.model.PaginatedOperationWithoutResultKeyRequest; +import software.amazon.awssdk.services.json.model.PaginatedOperationWithoutResultKeyResponse; +import software.amazon.awssdk.services.json.model.ServiceFaultException; +import software.amazon.awssdk.services.json.model.StreamingInputOperationRequest; +import software.amazon.awssdk.services.json.model.StreamingInputOperationResponse; +import software.amazon.awssdk.services.json.model.StreamingInputOutputOperationRequest; +import software.amazon.awssdk.services.json.model.StreamingInputOutputOperationResponse; +import software.amazon.awssdk.services.json.model.StreamingOutputOperationRequest; +import software.amazon.awssdk.services.json.model.StreamingOutputOperationResponse; +import software.amazon.awssdk.services.json.model.inputeventstream.DefaultInputEvent; +import software.amazon.awssdk.services.json.model.inputeventstreamtwo.DefaultInputEventOne; +import software.amazon.awssdk.services.json.model.inputeventstreamtwo.DefaultInputEventTwo; +import software.amazon.awssdk.services.json.transform.APostOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.APostOperationWithOutputRequestMarshaller; +import software.amazon.awssdk.services.json.transform.EventStreamOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.EventStreamOperationWithOnlyInputRequestMarshaller; +import software.amazon.awssdk.services.json.transform.EventStreamOperationWithOnlyOutputRequestMarshaller; +import software.amazon.awssdk.services.json.transform.GetWithoutRequiredMembersRequestMarshaller; +import software.amazon.awssdk.services.json.transform.InputEventMarshaller; +import software.amazon.awssdk.services.json.transform.InputEventTwoMarshaller; +import software.amazon.awssdk.services.json.transform.OperationWithChecksumRequiredRequestMarshaller; +import software.amazon.awssdk.services.json.transform.OperationWithNoneAuthTypeRequestMarshaller; +import software.amazon.awssdk.services.json.transform.OperationWithRequestCompressionRequestMarshaller; +import software.amazon.awssdk.services.json.transform.PaginatedOperationWithResultKeyRequestMarshaller; +import software.amazon.awssdk.services.json.transform.PaginatedOperationWithoutResultKeyRequestMarshaller; +import software.amazon.awssdk.services.json.transform.StreamingInputOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.StreamingInputOutputOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.StreamingOutputOperationRequestMarshaller; +import software.amazon.awssdk.utils.CompletableFutureUtils; +import software.amazon.awssdk.utils.HostnameValidator; +import software.amazon.awssdk.utils.Pair; + +/** + * Internal implementation of {@link JsonAsyncClient}. + * + * @see JsonAsyncClient#builder() + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +final class DefaultJsonAsyncClient implements JsonAsyncClient { + private static final Logger log = LoggerFactory.getLogger(DefaultJsonAsyncClient.class); + + private final AsyncClientHandler clientHandler; + + private final AwsJsonProtocolFactory protocolFactory; + + private final SdkClientConfiguration clientConfiguration; + + private final JsonServiceClientConfiguration serviceClientConfiguration; + + private final Executor executor; + + protected DefaultJsonAsyncClient(JsonServiceClientConfiguration serviceClientConfiguration, + SdkClientConfiguration clientConfiguration) { + this.clientHandler = new AwsAsyncClientHandler(clientConfiguration); + this.clientConfiguration = clientConfiguration; + this.serviceClientConfiguration = serviceClientConfiguration; + this.protocolFactory = init(AwsJsonProtocolFactory.builder()).build(); + this.executor = clientConfiguration.option(SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR); + } + + @Override + public JsonUtilities utilities() { + return JsonUtilities.create(param1, param2, param3); + } + + /** + *

+ * Performs a post operation to the query service and has no output + *

+ * + * @param aPostOperationRequest + * @return A Java Future containing the result of the APostOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • InvalidInputException The request was rejected because an invalid or out-of-range value was supplied + * for an input parameter.
  • + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.APostOperation + * @see AWS + * API Documentation + */ + @Override + public CompletableFuture aPostOperation(APostOperationRequest aPostOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperation"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, APostOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + String hostPrefix = "{StringMember}-foo."; + HostnameValidator.validateHostnameCompliant(aPostOperationRequest.stringMember(), "StringMember", + "aPostOperationRequest"); + String resolvedHostExpression = String.format("%s-foo.", aPostOperationRequest.stringMember()); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("APostOperation") + .withMarshaller(new APostOperationRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .hostPrefixExpression(resolvedHostExpression).withInput(aPostOperationRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + *

+ * Performs a post operation to the query service and has modelled output + *

+ * + * @param aPostOperationWithOutputRequest + * @return A Java Future containing the result of the APostOperationWithOutput operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • InvalidInputException The request was rejected because an invalid or out-of-range value was supplied + * for an input parameter.
  • + *
  • ServiceFaultException
  • + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.APostOperationWithOutput + * @see AWS API Documentation + */ + @Override + public CompletableFuture aPostOperationWithOutput( + APostOperationWithOutputRequest aPostOperationWithOutputRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperationWithOutput"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, APostOperationWithOutputResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("APostOperationWithOutput") + .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(aPostOperationWithOutputRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the EventStreamOperation operation asynchronously. + * + * @param eventStreamOperationRequest + * @return A Java Future containing the result of the EventStreamOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.EventStreamOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture eventStreamOperation(EventStreamOperationRequest eventStreamOperationRequest, + Publisher requestStream, EventStreamOperationResponseHandler asyncResponseHandler) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, eventStreamOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "EventStreamOperation"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = new AttachHttpMetadataResponseHandler( + protocolFactory.createResponseHandler(operationMetadata, EventStreamOperationResponse::builder)); + + HttpResponseHandler voidResponseHandler = protocolFactory.createResponseHandler(JsonOperationMetadata + .builder().isPayloadJson(false).hasStreamingSuccessResponse(true).build(), VoidSdkResponse::builder); + + HttpResponseHandler eventResponseHandler = protocolFactory.createResponseHandler( + JsonOperationMetadata.builder().isPayloadJson(true).hasStreamingSuccessResponse(false).build(), + EventStreamTaggedUnionPojoSupplier.builder().putSdkPojoSupplier("EventOne", EventStream::eventOneBuilder) + .putSdkPojoSupplier("EventTheSecond", EventStream::eventTheSecondBuilder) + .putSdkPojoSupplier("secondEventOne", EventStream::secondEventOneBuilder) + .putSdkPojoSupplier("eventThree", EventStream::eventThreeBuilder) + .defaultSdkPojoSupplier(() -> new SdkPojoBuilder(EventStream.UNKNOWN)).build()); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + EventStreamTaggedUnionJsonMarshaller eventMarshaller = EventStreamTaggedUnionJsonMarshaller.builder() + .putMarshaller(DefaultInputEvent.class, new InputEventMarshaller(protocolFactory)).build(); + SdkPublisher eventPublisher = SdkPublisher.adapt(requestStream); + Publisher adapted = eventPublisher.map(event -> eventMarshaller.marshall(event)).map( + AwsClientHandlerUtils::encodeEventStreamRequestToByteBuffer); + CompletableFuture future = new CompletableFuture<>(); + EventStreamAsyncResponseTransformer asyncResponseTransformer = EventStreamAsyncResponseTransformer + . builder().eventStreamResponseHandler(asyncResponseHandler) + .eventResponseHandler(eventResponseHandler).initialResponseHandler(responseHandler) + .exceptionResponseHandler(errorResponseHandler).future(future).executor(executor).serviceName(serviceName()) + .build(); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("EventStreamOperation") + .withMarshaller(new EventStreamOperationRequestMarshaller(protocolFactory)) + .withAsyncRequestBody(AsyncRequestBody.fromPublisher(adapted)).withFullDuplex(true) + .withInitialRequestEvent(true).withResponseHandler(voidResponseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector).withInput(eventStreamOperationRequest), + asyncResponseTransformer); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + if (e != null) { + try { + asyncResponseHandler.exceptionOccurred(e); + } finally { + future.completeExceptionally(e); + } + } + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return CompletableFutureUtils.forwardExceptionTo(future, executeFuture); + } catch (Throwable t) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> asyncResponseHandler.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the EventStreamOperationWithOnlyInput operation asynchronously. + * + * @param eventStreamOperationWithOnlyInputRequest + * @return A Java Future containing the result of the EventStreamOperationWithOnlyInput operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.EventStreamOperationWithOnlyInput + * @see AWS API Documentation + */ + @Override + public CompletableFuture eventStreamOperationWithOnlyInput( + EventStreamOperationWithOnlyInputRequest eventStreamOperationWithOnlyInputRequest, + Publisher requestStream) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationWithOnlyInputRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + eventStreamOperationWithOnlyInputRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "EventStreamOperationWithOnlyInput"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(operationMetadata, EventStreamOperationWithOnlyInputResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + EventStreamTaggedUnionJsonMarshaller eventMarshaller = EventStreamTaggedUnionJsonMarshaller.builder() + .putMarshaller(DefaultInputEventOne.class, new InputEventMarshaller(protocolFactory)) + .putMarshaller(DefaultInputEventTwo.class, new InputEventTwoMarshaller(protocolFactory)).build(); + SdkPublisher eventPublisher = SdkPublisher.adapt(requestStream); + Publisher adapted = eventPublisher.map(event -> eventMarshaller.marshall(event)).map( + AwsClientHandlerUtils::encodeEventStreamRequestToByteBuffer); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("EventStreamOperationWithOnlyInput") + .withMarshaller(new EventStreamOperationWithOnlyInputRequestMarshaller(protocolFactory)) + .withAsyncRequestBody(AsyncRequestBody.fromPublisher(adapted)).withInitialRequestEvent(true) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(eventStreamOperationWithOnlyInputRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the EventStreamOperationWithOnlyOutput operation asynchronously. + * + * @param eventStreamOperationWithOnlyOutputRequest + * @return A Java Future containing the result of the EventStreamOperationWithOnlyOutput operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.EventStreamOperationWithOnlyOutput + * @see AWS API Documentation + */ + @Override + public CompletableFuture eventStreamOperationWithOnlyOutput( + EventStreamOperationWithOnlyOutputRequest eventStreamOperationWithOnlyOutputRequest, + EventStreamOperationWithOnlyOutputResponseHandler asyncResponseHandler) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationWithOnlyOutputRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + eventStreamOperationWithOnlyOutputRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "EventStreamOperationWithOnlyOutput"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = new AttachHttpMetadataResponseHandler( + protocolFactory.createResponseHandler(operationMetadata, EventStreamOperationWithOnlyOutputResponse::builder)); + + HttpResponseHandler voidResponseHandler = protocolFactory.createResponseHandler(JsonOperationMetadata + .builder().isPayloadJson(false).hasStreamingSuccessResponse(true).build(), VoidSdkResponse::builder); + + HttpResponseHandler eventResponseHandler = protocolFactory.createResponseHandler( + JsonOperationMetadata.builder().isPayloadJson(true).hasStreamingSuccessResponse(false).build(), + EventStreamTaggedUnionPojoSupplier.builder().putSdkPojoSupplier("EventOne", EventStream::eventOneBuilder) + .putSdkPojoSupplier("EventTheSecond", EventStream::eventTheSecondBuilder) + .putSdkPojoSupplier("secondEventOne", EventStream::secondEventOneBuilder) + .putSdkPojoSupplier("eventThree", EventStream::eventThreeBuilder) + .defaultSdkPojoSupplier(() -> new SdkPojoBuilder(EventStream.UNKNOWN)).build()); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + CompletableFuture future = new CompletableFuture<>(); + EventStreamAsyncResponseTransformer asyncResponseTransformer = EventStreamAsyncResponseTransformer + . builder() + .eventStreamResponseHandler(asyncResponseHandler).eventResponseHandler(eventResponseHandler) + .initialResponseHandler(responseHandler).exceptionResponseHandler(errorResponseHandler).future(future) + .executor(executor).serviceName(serviceName()).build(); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("EventStreamOperationWithOnlyOutput") + .withMarshaller(new EventStreamOperationWithOnlyOutputRequestMarshaller(protocolFactory)) + .withResponseHandler(voidResponseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(eventStreamOperationWithOnlyOutputRequest), asyncResponseTransformer); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + if (e != null) { + try { + asyncResponseHandler.exceptionOccurred(e); + } finally { + future.completeExceptionally(e); + } + } + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return CompletableFutureUtils.forwardExceptionTo(future, executeFuture); + } catch (Throwable t) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> asyncResponseHandler.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + *

+ * Performs a post operation to the query service and has no output + *

+ * + * @param getWithoutRequiredMembersRequest + * @return A Java Future containing the result of the GetWithoutRequiredMembers operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • InvalidInputException The request was rejected because an invalid or out-of-range value was supplied + * for an input parameter.
  • + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.GetWithoutRequiredMembers + * @see AWS API Documentation + */ + @Override + public CompletableFuture getWithoutRequiredMembers( + GetWithoutRequiredMembersRequest getWithoutRequiredMembersRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getWithoutRequiredMembersRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, getWithoutRequiredMembersRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetWithoutRequiredMembers"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, GetWithoutRequiredMembersResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("GetWithoutRequiredMembers") + .withMarshaller(new GetWithoutRequiredMembersRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(getWithoutRequiredMembersRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithChecksumRequired operation asynchronously. + * + * @param operationWithChecksumRequiredRequest + * @return A Java Future containing the result of the OperationWithChecksumRequired operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.OperationWithChecksumRequired + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithChecksumRequired( + OperationWithChecksumRequiredRequest operationWithChecksumRequiredRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithChecksumRequired"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, OperationWithChecksumRequiredResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithChecksumRequired") + .withMarshaller(new OperationWithChecksumRequiredRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, + HttpChecksumRequired.create()).withInput(operationWithChecksumRequiredRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithNoneAuthType operation asynchronously. + * + * @param operationWithNoneAuthTypeRequest + * @return A Java Future containing the result of the OperationWithNoneAuthType operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.OperationWithNoneAuthType + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithNoneAuthType( + OperationWithNoneAuthTypeRequest operationWithNoneAuthTypeRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithNoneAuthTypeRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithNoneAuthTypeRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithNoneAuthType"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, OperationWithNoneAuthTypeResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithNoneAuthType") + .withMarshaller(new OperationWithNoneAuthTypeRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(operationWithNoneAuthTypeRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithRequestCompression operation asynchronously. + * + * @param operationWithRequestCompressionRequest + * @return A Java Future containing the result of the OperationWithRequestCompression operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.OperationWithRequestCompression + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithRequestCompression( + OperationWithRequestCompressionRequest operationWithRequestCompressionRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithRequestCompression"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, OperationWithRequestCompressionResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithRequestCompression") + .withMarshaller(new OperationWithRequestCompressionRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, + RequestCompression.builder().encodings("gzip").isStreaming(false).build()) + .withInput(operationWithRequestCompressionRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some paginated operation with result_key in paginators.json file + * + * @param paginatedOperationWithResultKeyRequest + * @return A Java Future containing the result of the PaginatedOperationWithResultKey operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.PaginatedOperationWithResultKey + * @see AWS API Documentation + */ + @Override + public CompletableFuture paginatedOperationWithResultKey( + PaginatedOperationWithResultKeyRequest paginatedOperationWithResultKeyRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(paginatedOperationWithResultKeyRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + paginatedOperationWithResultKeyRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PaginatedOperationWithResultKey"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, PaginatedOperationWithResultKeyResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("PaginatedOperationWithResultKey") + .withMarshaller(new PaginatedOperationWithResultKeyRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(paginatedOperationWithResultKeyRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some paginated operation without result_key in paginators.json file + * + * @param paginatedOperationWithoutResultKeyRequest + * @return A Java Future containing the result of the PaginatedOperationWithoutResultKey operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.PaginatedOperationWithoutResultKey + * @see AWS API Documentation + */ + @Override + public CompletableFuture paginatedOperationWithoutResultKey( + PaginatedOperationWithoutResultKeyRequest paginatedOperationWithoutResultKeyRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(paginatedOperationWithoutResultKeyRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + paginatedOperationWithoutResultKeyRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PaginatedOperationWithoutResultKey"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(operationMetadata, PaginatedOperationWithoutResultKeyResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("PaginatedOperationWithoutResultKey") + .withMarshaller(new PaginatedOperationWithoutResultKeyRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(paginatedOperationWithoutResultKeyRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some operation with a streaming input + * + * @param streamingInputOperationRequest + * @param requestBody + * Functional interface that can be implemented to produce the request content in a non-blocking manner. The + * size of the content is expected to be known up front. See {@link AsyncRequestBody} for specific details on + * implementing this interface as well as links to precanned implementations for common scenarios like + * uploading from a file. The service documentation for the request content is as follows 'This be a stream' + * @return A Java Future containing the result of the StreamingInputOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.StreamingInputOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture streamingInputOperation( + StreamingInputOperationRequest streamingInputOperationRequest, AsyncRequestBody requestBody) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOperation"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, StreamingInputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("StreamingInputOperation") + .withMarshaller( + AsyncStreamingRequestMarshaller.builder() + .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) + .asyncRequestBody(requestBody).build()).withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector).withAsyncRequestBody(requestBody) + .withInput(streamingInputOperationRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some operation with streaming input and streaming output + * + * @param streamingInputOutputOperationRequest + * @param requestBody + * Functional interface that can be implemented to produce the request content in a non-blocking manner. The + * size of the content is expected to be known up front. See {@link AsyncRequestBody} for specific details on + * implementing this interface as well as links to precanned implementations for common scenarios like + * uploading from a file. The service documentation for the request content is as follows 'This be a stream' + * @param asyncResponseTransformer + * The response transformer for processing the streaming response in a non-blocking manner. See + * {@link AsyncResponseTransformer} for details on how this callback should be implemented and for links to + * precanned implementations for common scenarios like downloading to a file. The service documentation for + * the response content is as follows 'This be a stream'. + * @return A future to the transformed result of the AsyncResponseTransformer.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.StreamingInputOutputOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture streamingInputOutputOperation( + StreamingInputOutputOperationRequest streamingInputOutputOperationRequest, AsyncRequestBody requestBody, + AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOutputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + streamingInputOutputOperationRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOutputOperation"); + Pair, CompletableFuture> pair = AsyncResponseTransformerUtils + .wrapWithEndOfStreamFuture(asyncResponseTransformer); + asyncResponseTransformer = pair.left(); + CompletableFuture endOfStreamFuture = pair.right(); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(true) + .isPayloadJson(false).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, StreamingInputOutputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("StreamingInputOutputOperation") + .withMarshaller( + AsyncStreamingRequestMarshaller + .builder() + .delegateMarshaller( + new StreamingInputOutputOperationRequestMarshaller(protocolFactory)) + .asyncRequestBody(requestBody).transferEncoding(true).build()) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withAsyncRequestBody(requestBody).withInput(streamingInputOutputOperationRequest), + asyncResponseTransformer); + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + if (e != null) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(e)); + } + endOfStreamFuture.whenComplete((r2, e2) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some operation with a streaming output + * + * @param streamingOutputOperationRequest + * @param asyncResponseTransformer + * The response transformer for processing the streaming response in a non-blocking manner. See + * {@link AsyncResponseTransformer} for details on how this callback should be implemented and for links to + * precanned implementations for common scenarios like downloading to a file. The service documentation for + * the response content is as follows 'This be a stream'. + * @return A future to the transformed result of the AsyncResponseTransformer.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.StreamingOutputOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture streamingOutputOperation( + StreamingOutputOperationRequest streamingOutputOperationRequest, + AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingOutputOperation"); + Pair, CompletableFuture> pair = AsyncResponseTransformerUtils + .wrapWithEndOfStreamFuture(asyncResponseTransformer); + asyncResponseTransformer = pair.left(); + CompletableFuture endOfStreamFuture = pair.right(); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(true) + .isPayloadJson(false).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, StreamingOutputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("StreamingOutputOperation") + .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(streamingOutputOperationRequest), asyncResponseTransformer); + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + if (e != null) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(e)); + } + endOfStreamFuture.whenComplete((r2, e2) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + @Override + public final JsonServiceClientConfiguration serviceClientConfiguration() { + return this.serviceClientConfiguration; + } + + @Override + public final String serviceName() { + return SERVICE_NAME; + } + + private > T init(T builder) { + return builder + .clientConfiguration(clientConfiguration) + .defaultServiceExceptionSupplier(JsonException::builder) + .protocol(AwsJsonProtocol.AWS_JSON) + .protocolVersion("1.1") + .registerModeledException( + ExceptionMetadata.builder().errorCode("InvalidInputException") + .exceptionBuilderSupplier(InvalidInputException::builder).httpStatusCode(400).build()) + .registerModeledException( + ExceptionMetadata.builder().errorCode("ServiceFaultException") + .exceptionBuilderSupplier(ServiceFaultException::builder).httpStatusCode(500).build()); + } + + private static List resolveMetricPublishers(SdkClientConfiguration clientConfiguration, + RequestOverrideConfiguration requestOverrideConfiguration) { + List publishers = null; + if (requestOverrideConfiguration != null) { + publishers = requestOverrideConfiguration.metricPublishers(); + } + if (publishers == null || publishers.isEmpty()) { + publishers = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHERS); + } + if (publishers == null) { + publishers = Collections.emptyList(); + } + return publishers; + } + + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + private HttpResponseHandler createErrorResponseHandler(BaseAwsJsonProtocolFactory protocolFactory, + JsonOperationMetadata operationMetadata) { + return protocolFactory.createErrorResponseHandler(operationMetadata); + } + + @Override + public void close() { + clientHandler.close(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-json-async-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-json-async-client-class.java new file mode 100644 index 000000000000..20865f65db29 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-json-async-client-class.java @@ -0,0 +1,1351 @@ +package software.amazon.awssdk.services.json; + +import static software.amazon.awssdk.utils.FunctionalUtils.runAndLogError; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import org.reactivestreams.Publisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.handler.AwsAsyncClientHandler; +import software.amazon.awssdk.awscore.client.handler.AwsClientHandlerUtils; +import software.amazon.awssdk.awscore.eventstream.EventStreamAsyncResponseTransformer; +import software.amazon.awssdk.awscore.eventstream.EventStreamTaggedUnionJsonMarshaller; +import software.amazon.awssdk.awscore.eventstream.EventStreamTaggedUnionPojoSupplier; +import software.amazon.awssdk.awscore.eventstream.RestEventStreamAsyncResponseTransformer; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.CredentialType; +import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkPojoBuilder; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkResponse; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.core.async.AsyncResponseTransformerUtils; +import software.amazon.awssdk.core.async.SdkPublisher; +import software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.client.handler.AsyncClientHandler; +import software.amazon.awssdk.core.client.handler.AttachHttpMetadataResponseHandler; +import software.amazon.awssdk.core.client.handler.ClientExecutionParams; +import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksum; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksumRequired; +import software.amazon.awssdk.core.internal.interceptor.trait.RequestCompression; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.core.protocol.VoidSdkResponse; +import software.amazon.awssdk.core.runtime.transform.AsyncStreamingRequestMarshaller; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.NoOpMetricCollector; +import software.amazon.awssdk.protocols.core.ExceptionMetadata; +import software.amazon.awssdk.protocols.json.AwsJsonProtocol; +import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory; +import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory; +import software.amazon.awssdk.protocols.json.JsonOperationMetadata; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.json.model.APostOperationRequest; +import software.amazon.awssdk.services.json.model.APostOperationResponse; +import software.amazon.awssdk.services.json.model.APostOperationWithOutputRequest; +import software.amazon.awssdk.services.json.model.APostOperationWithOutputResponse; +import software.amazon.awssdk.services.json.model.BearerAuthOperationRequest; +import software.amazon.awssdk.services.json.model.BearerAuthOperationResponse; +import software.amazon.awssdk.services.json.model.EventStream; +import software.amazon.awssdk.services.json.model.EventStreamOperationRequest; +import software.amazon.awssdk.services.json.model.EventStreamOperationResponse; +import software.amazon.awssdk.services.json.model.EventStreamOperationResponseHandler; +import software.amazon.awssdk.services.json.model.EventStreamOperationWithOnlyInputRequest; +import software.amazon.awssdk.services.json.model.EventStreamOperationWithOnlyInputResponse; +import software.amazon.awssdk.services.json.model.EventStreamOperationWithOnlyOutputRequest; +import software.amazon.awssdk.services.json.model.EventStreamOperationWithOnlyOutputResponse; +import software.amazon.awssdk.services.json.model.EventStreamOperationWithOnlyOutputResponseHandler; +import software.amazon.awssdk.services.json.model.GetOperationWithChecksumRequest; +import software.amazon.awssdk.services.json.model.GetOperationWithChecksumResponse; +import software.amazon.awssdk.services.json.model.GetWithoutRequiredMembersRequest; +import software.amazon.awssdk.services.json.model.GetWithoutRequiredMembersResponse; +import software.amazon.awssdk.services.json.model.InputEventStream; +import software.amazon.awssdk.services.json.model.InputEventStreamTwo; +import software.amazon.awssdk.services.json.model.InvalidInputException; +import software.amazon.awssdk.services.json.model.JsonException; +import software.amazon.awssdk.services.json.model.OperationWithChecksumRequiredRequest; +import software.amazon.awssdk.services.json.model.OperationWithChecksumRequiredResponse; +import software.amazon.awssdk.services.json.model.OperationWithRequestCompressionRequest; +import software.amazon.awssdk.services.json.model.OperationWithRequestCompressionResponse; +import software.amazon.awssdk.services.json.model.PaginatedOperationWithResultKeyRequest; +import software.amazon.awssdk.services.json.model.PaginatedOperationWithResultKeyResponse; +import software.amazon.awssdk.services.json.model.PaginatedOperationWithoutResultKeyRequest; +import software.amazon.awssdk.services.json.model.PaginatedOperationWithoutResultKeyResponse; +import software.amazon.awssdk.services.json.model.PutOperationWithChecksumRequest; +import software.amazon.awssdk.services.json.model.PutOperationWithChecksumResponse; +import software.amazon.awssdk.services.json.model.StreamingInputOperationRequest; +import software.amazon.awssdk.services.json.model.StreamingInputOperationResponse; +import software.amazon.awssdk.services.json.model.StreamingInputOutputOperationRequest; +import software.amazon.awssdk.services.json.model.StreamingInputOutputOperationResponse; +import software.amazon.awssdk.services.json.model.StreamingOutputOperationRequest; +import software.amazon.awssdk.services.json.model.StreamingOutputOperationResponse; +import software.amazon.awssdk.services.json.model.inputeventstream.DefaultInputEvent; +import software.amazon.awssdk.services.json.model.inputeventstreamtwo.DefaultInputEventOne; +import software.amazon.awssdk.services.json.model.inputeventstreamtwo.DefaultInputEventTwo; +import software.amazon.awssdk.services.json.transform.APostOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.APostOperationWithOutputRequestMarshaller; +import software.amazon.awssdk.services.json.transform.BearerAuthOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.EventStreamOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.EventStreamOperationWithOnlyInputRequestMarshaller; +import software.amazon.awssdk.services.json.transform.EventStreamOperationWithOnlyOutputRequestMarshaller; +import software.amazon.awssdk.services.json.transform.GetOperationWithChecksumRequestMarshaller; +import software.amazon.awssdk.services.json.transform.GetWithoutRequiredMembersRequestMarshaller; +import software.amazon.awssdk.services.json.transform.InputEventMarshaller; +import software.amazon.awssdk.services.json.transform.InputEventTwoMarshaller; +import software.amazon.awssdk.services.json.transform.OperationWithChecksumRequiredRequestMarshaller; +import software.amazon.awssdk.services.json.transform.OperationWithRequestCompressionRequestMarshaller; +import software.amazon.awssdk.services.json.transform.PaginatedOperationWithResultKeyRequestMarshaller; +import software.amazon.awssdk.services.json.transform.PaginatedOperationWithoutResultKeyRequestMarshaller; +import software.amazon.awssdk.services.json.transform.PutOperationWithChecksumRequestMarshaller; +import software.amazon.awssdk.services.json.transform.StreamingInputOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.StreamingInputOutputOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.StreamingOutputOperationRequestMarshaller; +import software.amazon.awssdk.utils.CompletableFutureUtils; +import software.amazon.awssdk.utils.HostnameValidator; +import software.amazon.awssdk.utils.Pair; + +/** + * Internal implementation of {@link JsonAsyncClient}. + * + * @see JsonAsyncClient#builder() + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +final class DefaultJsonAsyncClient implements JsonAsyncClient { + private static final Logger log = LoggerFactory.getLogger(DefaultJsonAsyncClient.class); + + private final AsyncClientHandler clientHandler; + + private final AwsJsonProtocolFactory protocolFactory; + + private final SdkClientConfiguration clientConfiguration; + + private final JsonServiceClientConfiguration serviceClientConfiguration; + + private final Executor executor; + + protected DefaultJsonAsyncClient(JsonServiceClientConfiguration serviceClientConfiguration, + SdkClientConfiguration clientConfiguration) { + this.clientHandler = new AwsAsyncClientHandler(clientConfiguration); + this.clientConfiguration = clientConfiguration; + this.serviceClientConfiguration = serviceClientConfiguration; + this.protocolFactory = init(AwsJsonProtocolFactory.builder()).build(); + this.executor = clientConfiguration.option(SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR); + } + + @Override + public JsonUtilities utilities() { + return JsonUtilities.create(param1, param2, param3); + } + + /** + *

+ * Performs a post operation to the query service and has no output + *

+ * + * @param aPostOperationRequest + * @return A Java Future containing the result of the APostOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • InvalidInputException The request was rejected because an invalid or out-of-range value was supplied + * for an input parameter.
  • + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.APostOperation + * @see AWS + * API Documentation + */ + @Override + public CompletableFuture aPostOperation(APostOperationRequest aPostOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperation"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, APostOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + String hostPrefix = "{StringMember}-foo."; + HostnameValidator.validateHostnameCompliant(aPostOperationRequest.stringMember(), "StringMember", + "aPostOperationRequest"); + String resolvedHostExpression = String.format("%s-foo.", aPostOperationRequest.stringMember()); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("APostOperation") + .withMarshaller(new APostOperationRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .hostPrefixExpression(resolvedHostExpression).withInput(aPostOperationRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + *

+ * Performs a post operation to the query service and has modelled output + *

+ * + * @param aPostOperationWithOutputRequest + * @return A Java Future containing the result of the APostOperationWithOutput operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • InvalidInputException The request was rejected because an invalid or out-of-range value was supplied + * for an input parameter.
  • + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.APostOperationWithOutput + * @see AWS API Documentation + */ + @Override + public CompletableFuture aPostOperationWithOutput( + APostOperationWithOutputRequest aPostOperationWithOutputRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperationWithOutput"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, APostOperationWithOutputResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("APostOperationWithOutput") + .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(aPostOperationWithOutputRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the BearerAuthOperation operation asynchronously. + * + * @param bearerAuthOperationRequest + * @return A Java Future containing the result of the BearerAuthOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.BearerAuthOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture bearerAuthOperation( + BearerAuthOperationRequest bearerAuthOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(bearerAuthOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, bearerAuthOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "BearerAuthOperation"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, BearerAuthOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("BearerAuthOperation") + .withMarshaller(new BearerAuthOperationRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .credentialType(CredentialType.TOKEN).withInput(bearerAuthOperationRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the EventStreamOperation operation asynchronously. + * + * @param eventStreamOperationRequest + * @return A Java Future containing the result of the EventStreamOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.EventStreamOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture eventStreamOperation(EventStreamOperationRequest eventStreamOperationRequest, + Publisher requestStream, EventStreamOperationResponseHandler asyncResponseHandler) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, eventStreamOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "EventStreamOperation"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = new AttachHttpMetadataResponseHandler( + protocolFactory.createResponseHandler(operationMetadata, EventStreamOperationResponse::builder)); + + HttpResponseHandler voidResponseHandler = protocolFactory.createResponseHandler(JsonOperationMetadata + .builder().isPayloadJson(false).hasStreamingSuccessResponse(true).build(), VoidSdkResponse::builder); + + HttpResponseHandler eventResponseHandler = protocolFactory.createResponseHandler( + JsonOperationMetadata.builder().isPayloadJson(true).hasStreamingSuccessResponse(false).build(), + EventStreamTaggedUnionPojoSupplier.builder().putSdkPojoSupplier("EventOne", EventStream::eventOneBuilder) + .putSdkPojoSupplier("EventTheSecond", EventStream::eventTheSecondBuilder) + .putSdkPojoSupplier("secondEventOne", EventStream::secondEventOneBuilder) + .putSdkPojoSupplier("eventThree", EventStream::eventThreeBuilder) + .defaultSdkPojoSupplier(() -> new SdkPojoBuilder(EventStream.UNKNOWN)).build()); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + EventStreamTaggedUnionJsonMarshaller eventMarshaller = EventStreamTaggedUnionJsonMarshaller.builder() + .putMarshaller(DefaultInputEvent.class, new InputEventMarshaller(protocolFactory)).build(); + SdkPublisher eventPublisher = SdkPublisher.adapt(requestStream); + Publisher adapted = eventPublisher.map(event -> eventMarshaller.marshall(event)).map( + AwsClientHandlerUtils::encodeEventStreamRequestToByteBuffer); + CompletableFuture future = new CompletableFuture<>(); + EventStreamAsyncResponseTransformer asyncResponseTransformer = EventStreamAsyncResponseTransformer + . builder().eventStreamResponseHandler(asyncResponseHandler) + .eventResponseHandler(eventResponseHandler).initialResponseHandler(responseHandler) + .exceptionResponseHandler(errorResponseHandler).future(future).executor(executor).serviceName(serviceName()) + .build(); + RestEventStreamAsyncResponseTransformer restAsyncResponseTransformer = RestEventStreamAsyncResponseTransformer + . builder() + .eventStreamAsyncResponseTransformer(asyncResponseTransformer) + .eventStreamResponseHandler(asyncResponseHandler).build(); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("EventStreamOperation") + .withMarshaller(new EventStreamOperationRequestMarshaller(protocolFactory)) + .withAsyncRequestBody(AsyncRequestBody.fromPublisher(adapted)).withFullDuplex(true) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(eventStreamOperationRequest), restAsyncResponseTransformer); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + if (e != null) { + try { + asyncResponseHandler.exceptionOccurred(e); + } finally { + future.completeExceptionally(e); + } + } + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return CompletableFutureUtils.forwardExceptionTo(future, executeFuture); + } catch (Throwable t) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> asyncResponseHandler.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the EventStreamOperationWithOnlyInput operation asynchronously. + * + * @param eventStreamOperationWithOnlyInputRequest + * @return A Java Future containing the result of the EventStreamOperationWithOnlyInput operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.EventStreamOperationWithOnlyInput + * @see AWS API Documentation + */ + @Override + public CompletableFuture eventStreamOperationWithOnlyInput( + EventStreamOperationWithOnlyInputRequest eventStreamOperationWithOnlyInputRequest, + Publisher requestStream) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationWithOnlyInputRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + eventStreamOperationWithOnlyInputRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "EventStreamOperationWithOnlyInput"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(operationMetadata, EventStreamOperationWithOnlyInputResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + EventStreamTaggedUnionJsonMarshaller eventMarshaller = EventStreamTaggedUnionJsonMarshaller.builder() + .putMarshaller(DefaultInputEventOne.class, new InputEventMarshaller(protocolFactory)) + .putMarshaller(DefaultInputEventTwo.class, new InputEventTwoMarshaller(protocolFactory)).build(); + SdkPublisher eventPublisher = SdkPublisher.adapt(requestStream); + Publisher adapted = eventPublisher.map(event -> eventMarshaller.marshall(event)).map( + AwsClientHandlerUtils::encodeEventStreamRequestToByteBuffer); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("EventStreamOperationWithOnlyInput") + .withMarshaller(new EventStreamOperationWithOnlyInputRequestMarshaller(protocolFactory)) + .withAsyncRequestBody(AsyncRequestBody.fromPublisher(adapted)).withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector).withInput(eventStreamOperationWithOnlyInputRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the EventStreamOperationWithOnlyOutput operation asynchronously. + * + * @param eventStreamOperationWithOnlyOutputRequest + * @return A Java Future containing the result of the EventStreamOperationWithOnlyOutput operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.EventStreamOperationWithOnlyOutput + * @see AWS API Documentation + */ + @Override + public CompletableFuture eventStreamOperationWithOnlyOutput( + EventStreamOperationWithOnlyOutputRequest eventStreamOperationWithOnlyOutputRequest, + EventStreamOperationWithOnlyOutputResponseHandler asyncResponseHandler) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationWithOnlyOutputRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + eventStreamOperationWithOnlyOutputRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "EventStreamOperationWithOnlyOutput"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = new AttachHttpMetadataResponseHandler( + protocolFactory.createResponseHandler(operationMetadata, EventStreamOperationWithOnlyOutputResponse::builder)); + + HttpResponseHandler voidResponseHandler = protocolFactory.createResponseHandler(JsonOperationMetadata + .builder().isPayloadJson(false).hasStreamingSuccessResponse(true).build(), VoidSdkResponse::builder); + + HttpResponseHandler eventResponseHandler = protocolFactory.createResponseHandler( + JsonOperationMetadata.builder().isPayloadJson(true).hasStreamingSuccessResponse(false).build(), + EventStreamTaggedUnionPojoSupplier.builder().putSdkPojoSupplier("EventOne", EventStream::eventOneBuilder) + .putSdkPojoSupplier("EventTheSecond", EventStream::eventTheSecondBuilder) + .putSdkPojoSupplier("secondEventOne", EventStream::secondEventOneBuilder) + .putSdkPojoSupplier("eventThree", EventStream::eventThreeBuilder) + .defaultSdkPojoSupplier(() -> new SdkPojoBuilder(EventStream.UNKNOWN)).build()); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + CompletableFuture future = new CompletableFuture<>(); + EventStreamAsyncResponseTransformer asyncResponseTransformer = EventStreamAsyncResponseTransformer + . builder() + .eventStreamResponseHandler(asyncResponseHandler).eventResponseHandler(eventResponseHandler) + .initialResponseHandler(responseHandler).exceptionResponseHandler(errorResponseHandler).future(future) + .executor(executor).serviceName(serviceName()).build(); + RestEventStreamAsyncResponseTransformer restAsyncResponseTransformer = RestEventStreamAsyncResponseTransformer + . builder() + .eventStreamAsyncResponseTransformer(asyncResponseTransformer) + .eventStreamResponseHandler(asyncResponseHandler).build(); + + CompletableFuture executeFuture = clientHandler + .execute( + new ClientExecutionParams() + .withOperationName("EventStreamOperationWithOnlyOutput") + .withMarshaller(new EventStreamOperationWithOnlyOutputRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(eventStreamOperationWithOnlyOutputRequest), restAsyncResponseTransformer); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + if (e != null) { + try { + asyncResponseHandler.exceptionOccurred(e); + } finally { + future.completeExceptionally(e); + } + } + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return CompletableFutureUtils.forwardExceptionTo(future, executeFuture); + } catch (Throwable t) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> asyncResponseHandler.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the GetOperationWithChecksum operation asynchronously. + * + * @param getOperationWithChecksumRequest + * @return A Java Future containing the result of the GetOperationWithChecksum operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.GetOperationWithChecksum + * @see AWS API Documentation + */ + @Override + public CompletableFuture getOperationWithChecksum( + GetOperationWithChecksumRequest getOperationWithChecksumRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getOperationWithChecksumRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, getOperationWithChecksumRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetOperationWithChecksum"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(false).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, GetOperationWithChecksumResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("GetOperationWithChecksum") + .withMarshaller(new GetOperationWithChecksumRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute( + SdkInternalExecutionAttribute.HTTP_CHECKSUM, + HttpChecksum.builder().requestChecksumRequired(true) + .requestAlgorithm(getOperationWithChecksumRequest.checksumAlgorithmAsString()) + .isRequestStreaming(false).build()).withInput(getOperationWithChecksumRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + *

+ * Performs a post operation to the query service and has no output + *

+ * + * @param getWithoutRequiredMembersRequest + * @return A Java Future containing the result of the GetWithoutRequiredMembers operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • InvalidInputException The request was rejected because an invalid or out-of-range value was supplied + * for an input parameter.
  • + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.GetWithoutRequiredMembers + * @see AWS API Documentation + */ + @Override + public CompletableFuture getWithoutRequiredMembers( + GetWithoutRequiredMembersRequest getWithoutRequiredMembersRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getWithoutRequiredMembersRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, getWithoutRequiredMembersRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetWithoutRequiredMembers"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, GetWithoutRequiredMembersResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("GetWithoutRequiredMembers") + .withMarshaller(new GetWithoutRequiredMembersRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(getWithoutRequiredMembersRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithChecksumRequired operation asynchronously. + * + * @param operationWithChecksumRequiredRequest + * @return A Java Future containing the result of the OperationWithChecksumRequired operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.OperationWithChecksumRequired + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithChecksumRequired( + OperationWithChecksumRequiredRequest operationWithChecksumRequiredRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithChecksumRequired"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, OperationWithChecksumRequiredResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithChecksumRequired") + .withMarshaller(new OperationWithChecksumRequiredRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, + HttpChecksumRequired.create()).withInput(operationWithChecksumRequiredRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithRequestCompression operation asynchronously. + * + * @param operationWithRequestCompressionRequest + * @return A Java Future containing the result of the OperationWithRequestCompression operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.OperationWithRequestCompression + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithRequestCompression( + OperationWithRequestCompressionRequest operationWithRequestCompressionRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithRequestCompression"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, OperationWithRequestCompressionResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithRequestCompression") + .withMarshaller(new OperationWithRequestCompressionRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, + RequestCompression.builder().encodings("gzip").isStreaming(false).build()) + .withInput(operationWithRequestCompressionRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some paginated operation with result_key in paginators.json file + * + * @param paginatedOperationWithResultKeyRequest + * @return A Java Future containing the result of the PaginatedOperationWithResultKey operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.PaginatedOperationWithResultKey + * @see AWS API Documentation + */ + @Override + public CompletableFuture paginatedOperationWithResultKey( + PaginatedOperationWithResultKeyRequest paginatedOperationWithResultKeyRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(paginatedOperationWithResultKeyRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + paginatedOperationWithResultKeyRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PaginatedOperationWithResultKey"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, PaginatedOperationWithResultKeyResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("PaginatedOperationWithResultKey") + .withMarshaller(new PaginatedOperationWithResultKeyRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(paginatedOperationWithResultKeyRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some paginated operation without result_key in paginators.json file + * + * @param paginatedOperationWithoutResultKeyRequest + * @return A Java Future containing the result of the PaginatedOperationWithoutResultKey operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.PaginatedOperationWithoutResultKey + * @see AWS API Documentation + */ + @Override + public CompletableFuture paginatedOperationWithoutResultKey( + PaginatedOperationWithoutResultKeyRequest paginatedOperationWithoutResultKeyRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(paginatedOperationWithoutResultKeyRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + paginatedOperationWithoutResultKeyRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PaginatedOperationWithoutResultKey"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(operationMetadata, PaginatedOperationWithoutResultKeyResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("PaginatedOperationWithoutResultKey") + .withMarshaller(new PaginatedOperationWithoutResultKeyRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(paginatedOperationWithoutResultKeyRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the PutOperationWithChecksum operation asynchronously. + * + * @param putOperationWithChecksumRequest + * @param requestBody + * Functional interface that can be implemented to produce the request content in a non-blocking manner. The + * size of the content is expected to be known up front. See {@link AsyncRequestBody} for specific details on + * implementing this interface as well as links to precanned implementations for common scenarios like + * uploading from a file. The service documentation for the request content is as follows ' + *

+ * Object data. + *

+ * ' + * @param asyncResponseTransformer + * The response transformer for processing the streaming response in a non-blocking manner. See + * {@link AsyncResponseTransformer} for details on how this callback should be implemented and for links to + * precanned implementations for common scenarios like downloading to a file. The service documentation for + * the response content is as follows ' + *

+ * Object data. + *

+ * '. + * @return A future to the transformed result of the AsyncResponseTransformer.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.PutOperationWithChecksum + * @see AWS API Documentation + */ + @Override + public CompletableFuture putOperationWithChecksum( + PutOperationWithChecksumRequest putOperationWithChecksumRequest, AsyncRequestBody requestBody, + AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(putOperationWithChecksumRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, putOperationWithChecksumRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PutOperationWithChecksum"); + Pair, CompletableFuture> pair = AsyncResponseTransformerUtils + .wrapWithEndOfStreamFuture(asyncResponseTransformer); + asyncResponseTransformer = pair.left(); + CompletableFuture endOfStreamFuture = pair.right(); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(true) + .isPayloadJson(false).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, PutOperationWithChecksumResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("PutOperationWithChecksum") + .withMarshaller( + AsyncStreamingRequestMarshaller.builder() + .delegateMarshaller(new PutOperationWithChecksumRequestMarshaller(protocolFactory)) + .asyncRequestBody(requestBody).build()) + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector) + .withAsyncRequestBody(requestBody) + .putExecutionAttribute( + SdkInternalExecutionAttribute.HTTP_CHECKSUM, + HttpChecksum.builder().requestChecksumRequired(false) + .requestValidationMode(putOperationWithChecksumRequest.checksumModeAsString()) + .responseAlgorithms("CRC32C", "CRC32", "SHA1", "SHA256").isRequestStreaming(true) + .build()).withInput(putOperationWithChecksumRequest), asyncResponseTransformer); + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + if (e != null) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(e)); + } + endOfStreamFuture.whenComplete((r2, e2) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some operation with a streaming input + * + * @param streamingInputOperationRequest + * @param requestBody + * Functional interface that can be implemented to produce the request content in a non-blocking manner. The + * size of the content is expected to be known up front. See {@link AsyncRequestBody} for specific details on + * implementing this interface as well as links to precanned implementations for common scenarios like + * uploading from a file. The service documentation for the request content is as follows 'This be a stream' + * @return A Java Future containing the result of the StreamingInputOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.StreamingInputOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture streamingInputOperation( + StreamingInputOperationRequest streamingInputOperationRequest, AsyncRequestBody requestBody) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOperation"); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, StreamingInputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("StreamingInputOperation") + .withMarshaller( + AsyncStreamingRequestMarshaller.builder() + .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) + .asyncRequestBody(requestBody).build()).withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector).withAsyncRequestBody(requestBody) + .withInput(streamingInputOperationRequest)); + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some operation with streaming input and streaming output + * + * @param streamingInputOutputOperationRequest + * @param requestBody + * Functional interface that can be implemented to produce the request content in a non-blocking manner. The + * size of the content is expected to be known up front. See {@link AsyncRequestBody} for specific details on + * implementing this interface as well as links to precanned implementations for common scenarios like + * uploading from a file. The service documentation for the request content is as follows 'This be a stream' + * @param asyncResponseTransformer + * The response transformer for processing the streaming response in a non-blocking manner. See + * {@link AsyncResponseTransformer} for details on how this callback should be implemented and for links to + * precanned implementations for common scenarios like downloading to a file. The service documentation for + * the response content is as follows 'This be a stream'. + * @return A future to the transformed result of the AsyncResponseTransformer.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.StreamingInputOutputOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture streamingInputOutputOperation( + StreamingInputOutputOperationRequest streamingInputOutputOperationRequest, AsyncRequestBody requestBody, + AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOutputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + streamingInputOutputOperationRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOutputOperation"); + Pair, CompletableFuture> pair = AsyncResponseTransformerUtils + .wrapWithEndOfStreamFuture(asyncResponseTransformer); + asyncResponseTransformer = pair.left(); + CompletableFuture endOfStreamFuture = pair.right(); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(true) + .isPayloadJson(false).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, StreamingInputOutputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("StreamingInputOutputOperation") + .withMarshaller( + AsyncStreamingRequestMarshaller + .builder() + .delegateMarshaller( + new StreamingInputOutputOperationRequestMarshaller(protocolFactory)) + .asyncRequestBody(requestBody).transferEncoding(true).build()) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withAsyncRequestBody(requestBody).withInput(streamingInputOutputOperationRequest), + asyncResponseTransformer); + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + if (e != null) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(e)); + } + endOfStreamFuture.whenComplete((r2, e2) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some operation with a streaming output + * + * @param streamingOutputOperationRequest + * @param asyncResponseTransformer + * The response transformer for processing the streaming response in a non-blocking manner. See + * {@link AsyncResponseTransformer} for details on how this callback should be implemented and for links to + * precanned implementations for common scenarios like downloading to a file. The service documentation for + * the response content is as follows 'This be a stream'. + * @return A future to the transformed result of the AsyncResponseTransformer.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample JsonAsyncClient.StreamingOutputOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture streamingOutputOperation( + StreamingOutputOperationRequest streamingOutputOperationRequest, + AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingOutputOperation"); + Pair, CompletableFuture> pair = AsyncResponseTransformerUtils + .wrapWithEndOfStreamFuture(asyncResponseTransformer); + asyncResponseTransformer = pair.left(); + CompletableFuture endOfStreamFuture = pair.right(); + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(true) + .isPayloadJson(false).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, StreamingOutputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("StreamingOutputOperation") + .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(streamingOutputOperationRequest), asyncResponseTransformer); + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { + if (e != null) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(e)); + } + endOfStreamFuture.whenComplete((r2, e2) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + }); + executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture); + return executeFuture; + } catch (Throwable t) { + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + @Override + public final JsonServiceClientConfiguration serviceClientConfiguration() { + return this.serviceClientConfiguration; + } + + @Override + public final String serviceName() { + return SERVICE_NAME; + } + + private > T init(T builder) { + return builder + .clientConfiguration(clientConfiguration) + .defaultServiceExceptionSupplier(JsonException::builder) + .protocol(AwsJsonProtocol.REST_JSON) + .protocolVersion("1.1") + .registerModeledException( + ExceptionMetadata.builder().errorCode("InvalidInput") + .exceptionBuilderSupplier(InvalidInputException::builder).httpStatusCode(400).build()); + } + + private static List resolveMetricPublishers(SdkClientConfiguration clientConfiguration, + RequestOverrideConfiguration requestOverrideConfiguration) { + List publishers = null; + if (requestOverrideConfiguration != null) { + publishers = requestOverrideConfiguration.metricPublishers(); + } + if (publishers == null || publishers.isEmpty()) { + publishers = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHERS); + } + if (publishers == null) { + publishers = Collections.emptyList(); + } + return publishers; + } + + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + private HttpResponseHandler createErrorResponseHandler(BaseAwsJsonProtocolFactory protocolFactory, + JsonOperationMetadata operationMetadata) { + return protocolFactory.createErrorResponseHandler(operationMetadata); + } + + @Override + public void close() { + clientHandler.close(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-json-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-json-client-class.java new file mode 100644 index 000000000000..39a5e56c52a3 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-json-client-class.java @@ -0,0 +1,932 @@ +package software.amazon.awssdk.services.json; + +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.CredentialType; +import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.client.handler.ClientExecutionParams; +import software.amazon.awssdk.core.client.handler.SyncClientHandler; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksum; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksumRequired; +import software.amazon.awssdk.core.internal.interceptor.trait.RequestCompression; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.core.runtime.transform.StreamingRequestMarshaller; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.NoOpMetricCollector; +import software.amazon.awssdk.protocols.core.ExceptionMetadata; +import software.amazon.awssdk.protocols.json.AwsJsonProtocol; +import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory; +import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory; +import software.amazon.awssdk.protocols.json.JsonOperationMetadata; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.json.model.APostOperationRequest; +import software.amazon.awssdk.services.json.model.APostOperationResponse; +import software.amazon.awssdk.services.json.model.APostOperationWithOutputRequest; +import software.amazon.awssdk.services.json.model.APostOperationWithOutputResponse; +import software.amazon.awssdk.services.json.model.BearerAuthOperationRequest; +import software.amazon.awssdk.services.json.model.BearerAuthOperationResponse; +import software.amazon.awssdk.services.json.model.GetOperationWithChecksumRequest; +import software.amazon.awssdk.services.json.model.GetOperationWithChecksumResponse; +import software.amazon.awssdk.services.json.model.GetWithoutRequiredMembersRequest; +import software.amazon.awssdk.services.json.model.GetWithoutRequiredMembersResponse; +import software.amazon.awssdk.services.json.model.InvalidInputException; +import software.amazon.awssdk.services.json.model.JsonException; +import software.amazon.awssdk.services.json.model.OperationWithChecksumRequiredRequest; +import software.amazon.awssdk.services.json.model.OperationWithChecksumRequiredResponse; +import software.amazon.awssdk.services.json.model.OperationWithRequestCompressionRequest; +import software.amazon.awssdk.services.json.model.OperationWithRequestCompressionResponse; +import software.amazon.awssdk.services.json.model.PaginatedOperationWithResultKeyRequest; +import software.amazon.awssdk.services.json.model.PaginatedOperationWithResultKeyResponse; +import software.amazon.awssdk.services.json.model.PaginatedOperationWithoutResultKeyRequest; +import software.amazon.awssdk.services.json.model.PaginatedOperationWithoutResultKeyResponse; +import software.amazon.awssdk.services.json.model.PutOperationWithChecksumRequest; +import software.amazon.awssdk.services.json.model.PutOperationWithChecksumResponse; +import software.amazon.awssdk.services.json.model.StreamingInputOperationRequest; +import software.amazon.awssdk.services.json.model.StreamingInputOperationResponse; +import software.amazon.awssdk.services.json.model.StreamingInputOutputOperationRequest; +import software.amazon.awssdk.services.json.model.StreamingInputOutputOperationResponse; +import software.amazon.awssdk.services.json.model.StreamingOutputOperationRequest; +import software.amazon.awssdk.services.json.model.StreamingOutputOperationResponse; +import software.amazon.awssdk.services.json.transform.APostOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.APostOperationWithOutputRequestMarshaller; +import software.amazon.awssdk.services.json.transform.BearerAuthOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.GetOperationWithChecksumRequestMarshaller; +import software.amazon.awssdk.services.json.transform.GetWithoutRequiredMembersRequestMarshaller; +import software.amazon.awssdk.services.json.transform.OperationWithChecksumRequiredRequestMarshaller; +import software.amazon.awssdk.services.json.transform.OperationWithRequestCompressionRequestMarshaller; +import software.amazon.awssdk.services.json.transform.PaginatedOperationWithResultKeyRequestMarshaller; +import software.amazon.awssdk.services.json.transform.PaginatedOperationWithoutResultKeyRequestMarshaller; +import software.amazon.awssdk.services.json.transform.PutOperationWithChecksumRequestMarshaller; +import software.amazon.awssdk.services.json.transform.StreamingInputOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.StreamingInputOutputOperationRequestMarshaller; +import software.amazon.awssdk.services.json.transform.StreamingOutputOperationRequestMarshaller; +import software.amazon.awssdk.utils.HostnameValidator; +import software.amazon.awssdk.utils.Logger; + +/** + * Internal implementation of {@link JsonClient}. + * + * @see JsonClient#builder() + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +final class DefaultJsonClient implements JsonClient { + private static final Logger log = Logger.loggerFor(DefaultJsonClient.class); + + private final SyncClientHandler clientHandler; + + private final AwsJsonProtocolFactory protocolFactory; + + private final SdkClientConfiguration clientConfiguration; + + private final JsonServiceClientConfiguration serviceClientConfiguration; + + protected DefaultJsonClient(JsonServiceClientConfiguration serviceClientConfiguration, + SdkClientConfiguration clientConfiguration) { + this.clientHandler = new AwsSyncClientHandler(clientConfiguration); + this.clientConfiguration = clientConfiguration; + this.serviceClientConfiguration = serviceClientConfiguration; + this.protocolFactory = init(AwsJsonProtocolFactory.builder()).build(); + } + + /** + *

+ * Performs a post operation to the query service and has no output + *

+ * + * @param aPostOperationRequest + * @return Result of the APostOperation operation returned by the service. + * @throws InvalidInputException + * The request was rejected because an invalid or out-of-range value was supplied for an input parameter. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.APostOperation + * @see AWS + * API Documentation + */ + @Override + public APostOperationResponse aPostOperation(APostOperationRequest aPostOperationRequest) throws InvalidInputException, + AwsServiceException, SdkClientException, JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler(operationMetadata, + APostOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperation"); + String hostPrefix = "{StringMember}-foo."; + HostnameValidator.validateHostnameCompliant(aPostOperationRequest.stringMember(), "StringMember", + "aPostOperationRequest"); + String resolvedHostExpression = String.format("%s-foo.", aPostOperationRequest.stringMember()); + + return clientHandler.execute(new ClientExecutionParams() + .withOperationName("APostOperation").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).hostPrefixExpression(resolvedHostExpression) + .withRequestConfiguration(clientConfiguration).withInput(aPostOperationRequest) + .withMetricCollector(apiCallMetricCollector) + .withMarshaller(new APostOperationRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + *

+ * Performs a post operation to the query service and has modelled output + *

+ * + * @param aPostOperationWithOutputRequest + * @return Result of the APostOperationWithOutput operation returned by the service. + * @throws InvalidInputException + * The request was rejected because an invalid or out-of-range value was supplied for an input parameter. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.APostOperationWithOutput + * @see AWS API Documentation + */ + @Override + public APostOperationWithOutputResponse aPostOperationWithOutput( + APostOperationWithOutputRequest aPostOperationWithOutputRequest) throws InvalidInputException, AwsServiceException, + SdkClientException, JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, APostOperationWithOutputResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperationWithOutput"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("APostOperationWithOutput").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(aPostOperationWithOutputRequest).withMetricCollector(apiCallMetricCollector) + .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the BearerAuthOperation operation. + * + * @param bearerAuthOperationRequest + * @return Result of the BearerAuthOperation operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.BearerAuthOperation + * @see AWS API Documentation + */ + @Override + public BearerAuthOperationResponse bearerAuthOperation(BearerAuthOperationRequest bearerAuthOperationRequest) + throws AwsServiceException, SdkClientException, JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, BearerAuthOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(bearerAuthOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, bearerAuthOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "BearerAuthOperation"); + + return clientHandler.execute(new ClientExecutionParams() + .withOperationName("BearerAuthOperation").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).credentialType(CredentialType.TOKEN) + .withRequestConfiguration(clientConfiguration).withInput(bearerAuthOperationRequest) + .withMetricCollector(apiCallMetricCollector) + .withMarshaller(new BearerAuthOperationRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the GetOperationWithChecksum operation. + * + * @param getOperationWithChecksumRequest + * @return Result of the GetOperationWithChecksum operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.GetOperationWithChecksum + * @see AWS API Documentation + */ + @Override + public GetOperationWithChecksumResponse getOperationWithChecksum( + GetOperationWithChecksumRequest getOperationWithChecksumRequest) throws AwsServiceException, SdkClientException, + JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(false).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, GetOperationWithChecksumResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getOperationWithChecksumRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, getOperationWithChecksumRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetOperationWithChecksum"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("GetOperationWithChecksum") + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withInput(getOperationWithChecksumRequest) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute( + SdkInternalExecutionAttribute.HTTP_CHECKSUM, + HttpChecksum.builder().requestChecksumRequired(true) + .requestAlgorithm(getOperationWithChecksumRequest.checksumAlgorithmAsString()) + .isRequestStreaming(false).build()) + .withMarshaller(new GetOperationWithChecksumRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + *

+ * Performs a post operation to the query service and has no output + *

+ * + * @param getWithoutRequiredMembersRequest + * @return Result of the GetWithoutRequiredMembers operation returned by the service. + * @throws InvalidInputException + * The request was rejected because an invalid or out-of-range value was supplied for an input parameter. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.GetWithoutRequiredMembers + * @see AWS API Documentation + */ + @Override + public GetWithoutRequiredMembersResponse getWithoutRequiredMembers( + GetWithoutRequiredMembersRequest getWithoutRequiredMembersRequest) throws InvalidInputException, AwsServiceException, + SdkClientException, JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, GetWithoutRequiredMembersResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getWithoutRequiredMembersRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, getWithoutRequiredMembersRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetWithoutRequiredMembers"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("GetWithoutRequiredMembers").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(getWithoutRequiredMembersRequest).withMetricCollector(apiCallMetricCollector) + .withMarshaller(new GetWithoutRequiredMembersRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the OperationWithChecksumRequired operation. + * + * @param operationWithChecksumRequiredRequest + * @return Result of the OperationWithChecksumRequired operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.OperationWithChecksumRequired + * @see AWS API Documentation + */ + @Override + public OperationWithChecksumRequiredResponse operationWithChecksumRequired( + OperationWithChecksumRequiredRequest operationWithChecksumRequiredRequest) throws AwsServiceException, + SdkClientException, JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, OperationWithChecksumRequiredResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithChecksumRequired"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithChecksumRequired") + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withInput(operationWithChecksumRequiredRequest) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, + HttpChecksumRequired.create()) + .withMarshaller(new OperationWithChecksumRequiredRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the OperationWithRequestCompression operation. + * + * @param operationWithRequestCompressionRequest + * @return Result of the OperationWithRequestCompression operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.OperationWithRequestCompression + * @see AWS API Documentation + */ + @Override + public OperationWithRequestCompressionResponse operationWithRequestCompression( + OperationWithRequestCompressionRequest operationWithRequestCompressionRequest) throws AwsServiceException, + SdkClientException, JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, OperationWithRequestCompressionResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithRequestCompression"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithRequestCompression") + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withInput(operationWithRequestCompressionRequest) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, + RequestCompression.builder().encodings("gzip").isStreaming(false).build()) + .withMarshaller(new OperationWithRequestCompressionRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Some paginated operation with result_key in paginators.json file + * + * @param paginatedOperationWithResultKeyRequest + * @return Result of the PaginatedOperationWithResultKey operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.PaginatedOperationWithResultKey + * @see AWS API Documentation + */ + @Override + public PaginatedOperationWithResultKeyResponse paginatedOperationWithResultKey( + PaginatedOperationWithResultKeyRequest paginatedOperationWithResultKeyRequest) throws AwsServiceException, + SdkClientException, JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, PaginatedOperationWithResultKeyResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(paginatedOperationWithResultKeyRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + paginatedOperationWithResultKeyRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PaginatedOperationWithResultKey"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("PaginatedOperationWithResultKey").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(paginatedOperationWithResultKeyRequest).withMetricCollector(apiCallMetricCollector) + .withMarshaller(new PaginatedOperationWithResultKeyRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Some paginated operation without result_key in paginators.json file + * + * @param paginatedOperationWithoutResultKeyRequest + * @return Result of the PaginatedOperationWithoutResultKey operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.PaginatedOperationWithoutResultKey + * @see AWS API Documentation + */ + @Override + public PaginatedOperationWithoutResultKeyResponse paginatedOperationWithoutResultKey( + PaginatedOperationWithoutResultKeyRequest paginatedOperationWithoutResultKeyRequest) throws AwsServiceException, + SdkClientException, JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, PaginatedOperationWithoutResultKeyResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(paginatedOperationWithoutResultKeyRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + paginatedOperationWithoutResultKeyRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PaginatedOperationWithoutResultKey"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("PaginatedOperationWithoutResultKey").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(paginatedOperationWithoutResultKeyRequest).withMetricCollector(apiCallMetricCollector) + .withMarshaller(new PaginatedOperationWithoutResultKeyRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the PutOperationWithChecksum operation. + * + * @param putOperationWithChecksumRequest + * @param requestBody + * The content to send to the service. A {@link RequestBody} can be created using one of several factory + * methods for various sources of data. For example, to create a request body from a file you can do the + * following. + * + *
+     * {@code RequestBody.fromFile(new File("myfile.txt"))}
+     * 
+ * + * See documentation in {@link RequestBody} for additional details and which sources of data are supported. + * The service documentation for the request content is as follows ' + *

+ * Object data. + *

+ * ' + * @param responseTransformer + * Functional interface for processing the streamed response content. The unmarshalled + * PutOperationWithChecksumResponse and an InputStream to the response content are provided as parameters to + * the callback. The callback may return a transformed type which will be the return value of this method. + * See {@link software.amazon.awssdk.core.sync.ResponseTransformer} for details on implementing this + * interface and for links to pre-canned implementations for common scenarios like downloading to a file. The + * service documentation for the response content is as follows ' + *

+ * Object data. + *

+ * '. + * @return The transformed result of the ResponseTransformer. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.PutOperationWithChecksum + * @see AWS API Documentation + */ + @Override + public ReturnT putOperationWithChecksum(PutOperationWithChecksumRequest putOperationWithChecksumRequest, + RequestBody requestBody, ResponseTransformer responseTransformer) + throws AwsServiceException, SdkClientException, JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(true) + .isPayloadJson(false).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, PutOperationWithChecksumResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(putOperationWithChecksumRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, putOperationWithChecksumRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PutOperationWithChecksum"); + + return clientHandler.execute( + new ClientExecutionParams() + .withOperationName("PutOperationWithChecksum") + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withInput(putOperationWithChecksumRequest) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute( + SdkInternalExecutionAttribute.HTTP_CHECKSUM, + HttpChecksum.builder().requestChecksumRequired(false) + .requestValidationMode(putOperationWithChecksumRequest.checksumModeAsString()) + .responseAlgorithms("CRC32C", "CRC32", "SHA1", "SHA256").isRequestStreaming(true) + .build()) + .withRequestBody(requestBody) + .withMarshaller( + StreamingRequestMarshaller.builder() + .delegateMarshaller(new PutOperationWithChecksumRequestMarshaller(protocolFactory)) + .requestBody(requestBody).build()), responseTransformer); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Some operation with a streaming input + * + * @param streamingInputOperationRequest + * @param requestBody + * The content to send to the service. A {@link RequestBody} can be created using one of several factory + * methods for various sources of data. For example, to create a request body from a file you can do the + * following. + * + *
+     * {@code RequestBody.fromFile(new File("myfile.txt"))}
+     * 
+ * + * See documentation in {@link RequestBody} for additional details and which sources of data are supported. + * The service documentation for the request content is as follows 'This be a stream' + * @return Result of the StreamingInputOperation operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.StreamingInputOperation + * @see AWS API Documentation + */ + @Override + public StreamingInputOperationResponse streamingInputOperation(StreamingInputOperationRequest streamingInputOperationRequest, + RequestBody requestBody) throws AwsServiceException, SdkClientException, JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false) + .isPayloadJson(true).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, StreamingInputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOperation"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("StreamingInputOperation") + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withInput(streamingInputOperationRequest) + .withMetricCollector(apiCallMetricCollector) + .withRequestBody(requestBody) + .withMarshaller( + StreamingRequestMarshaller.builder() + .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) + .requestBody(requestBody).build())); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Some operation with streaming input and streaming output + * + * @param streamingInputOutputOperationRequest + * @param requestBody + * The content to send to the service. A {@link RequestBody} can be created using one of several factory + * methods for various sources of data. For example, to create a request body from a file you can do the + * following. + * + *
+     * {@code RequestBody.fromFile(new File("myfile.txt"))}
+     * 
+ * + * See documentation in {@link RequestBody} for additional details and which sources of data are supported. + * The service documentation for the request content is as follows 'This be a stream' + * @param responseTransformer + * Functional interface for processing the streamed response content. The unmarshalled + * StreamingInputOutputOperationResponse and an InputStream to the response content are provided as + * parameters to the callback. The callback may return a transformed type which will be the return value of + * this method. See {@link software.amazon.awssdk.core.sync.ResponseTransformer} for details on implementing + * this interface and for links to pre-canned implementations for common scenarios like downloading to a + * file. The service documentation for the response content is as follows 'This be a stream'. + * @return The transformed result of the ResponseTransformer. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.StreamingInputOutputOperation + * @see AWS API Documentation + */ + @Override + public ReturnT streamingInputOutputOperation( + StreamingInputOutputOperationRequest streamingInputOutputOperationRequest, RequestBody requestBody, + ResponseTransformer responseTransformer) throws AwsServiceException, + SdkClientException, JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(true) + .isPayloadJson(false).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, StreamingInputOutputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOutputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + streamingInputOutputOperationRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOutputOperation"); + + return clientHandler.execute( + new ClientExecutionParams() + .withOperationName("StreamingInputOutputOperation") + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withInput(streamingInputOutputOperationRequest) + .withMetricCollector(apiCallMetricCollector) + .withRequestBody(requestBody) + .withMarshaller( + StreamingRequestMarshaller + .builder() + .delegateMarshaller( + new StreamingInputOutputOperationRequestMarshaller(protocolFactory)) + .requestBody(requestBody).transferEncoding(true).build()), responseTransformer); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Some operation with a streaming output + * + * @param streamingOutputOperationRequest + * @param responseTransformer + * Functional interface for processing the streamed response content. The unmarshalled + * StreamingOutputOperationResponse and an InputStream to the response content are provided as parameters to + * the callback. The callback may return a transformed type which will be the return value of this method. + * See {@link software.amazon.awssdk.core.sync.ResponseTransformer} for details on implementing this + * interface and for links to pre-canned implementations for common scenarios like downloading to a file. The + * service documentation for the response content is as follows 'This be a stream'. + * @return The transformed result of the ResponseTransformer. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws JsonException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample JsonClient.StreamingOutputOperation + * @see AWS API Documentation + */ + @Override + public ReturnT streamingOutputOperation(StreamingOutputOperationRequest streamingOutputOperationRequest, + ResponseTransformer responseTransformer) throws AwsServiceException, + SdkClientException, JsonException { + JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(true) + .isPayloadJson(false).build(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + operationMetadata, StreamingOutputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, + operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingOutputOperation"); + + return clientHandler.execute( + new ClientExecutionParams() + .withOperationName("StreamingOutputOperation").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(streamingOutputOperationRequest).withMetricCollector(apiCallMetricCollector) + .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)), responseTransformer); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Creates an instance of {@link JsonUtilities} object with the configuration set on this client. + */ + @Override + public JsonUtilities utilities() { + return JsonUtilities.create(param1, param2, param3); + } + + @Override + public final String serviceName() { + return SERVICE_NAME; + } + + private static List resolveMetricPublishers(SdkClientConfiguration clientConfiguration, + RequestOverrideConfiguration requestOverrideConfiguration) { + List publishers = null; + if (requestOverrideConfiguration != null) { + publishers = requestOverrideConfiguration.metricPublishers(); + } + if (publishers == null || publishers.isEmpty()) { + publishers = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHERS); + } + if (publishers == null) { + publishers = Collections.emptyList(); + } + return publishers; + } + + private HttpResponseHandler createErrorResponseHandler(BaseAwsJsonProtocolFactory protocolFactory, + JsonOperationMetadata operationMetadata) { + return protocolFactory.createErrorResponseHandler(operationMetadata); + } + + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + private > T init(T builder) { + return builder + .clientConfiguration(clientConfiguration) + .defaultServiceExceptionSupplier(JsonException::builder) + .protocol(AwsJsonProtocol.REST_JSON) + .protocolVersion("1.1") + .registerModeledException( + ExceptionMetadata.builder().errorCode("InvalidInput") + .exceptionBuilderSupplier(InvalidInputException::builder).httpStatusCode(400).build()); + } + + @Override + public final JsonServiceClientConfiguration serviceClientConfiguration() { + return this.serviceClientConfiguration; + } + + @Override + public void close() { + clientHandler.close(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-query-async-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-query-async-client-class.java new file mode 100644 index 000000000000..2a4d4cbcadce --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-query-async-client-class.java @@ -0,0 +1,923 @@ +package software.amazon.awssdk.services.query; + +import static software.amazon.awssdk.utils.FunctionalUtils.runAndLogError; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.handler.AwsAsyncClientHandler; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.CredentialType; +import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.core.async.AsyncResponseTransformerUtils; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.client.handler.AsyncClientHandler; +import software.amazon.awssdk.core.client.handler.ClientExecutionParams; +import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksum; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksumRequired; +import software.amazon.awssdk.core.internal.interceptor.trait.RequestCompression; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.core.runtime.transform.AsyncStreamingRequestMarshaller; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.NoOpMetricCollector; +import software.amazon.awssdk.protocols.core.ExceptionMetadata; +import software.amazon.awssdk.protocols.query.AwsQueryProtocolFactory; +import software.amazon.awssdk.services.query.internal.QueryServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.query.model.APostOperationRequest; +import software.amazon.awssdk.services.query.model.APostOperationResponse; +import software.amazon.awssdk.services.query.model.APostOperationWithOutputRequest; +import software.amazon.awssdk.services.query.model.APostOperationWithOutputResponse; +import software.amazon.awssdk.services.query.model.BearerAuthOperationRequest; +import software.amazon.awssdk.services.query.model.BearerAuthOperationResponse; +import software.amazon.awssdk.services.query.model.GetOperationWithChecksumRequest; +import software.amazon.awssdk.services.query.model.GetOperationWithChecksumResponse; +import software.amazon.awssdk.services.query.model.InvalidInputException; +import software.amazon.awssdk.services.query.model.OperationWithChecksumRequiredRequest; +import software.amazon.awssdk.services.query.model.OperationWithChecksumRequiredResponse; +import software.amazon.awssdk.services.query.model.OperationWithContextParamRequest; +import software.amazon.awssdk.services.query.model.OperationWithContextParamResponse; +import software.amazon.awssdk.services.query.model.OperationWithNoneAuthTypeRequest; +import software.amazon.awssdk.services.query.model.OperationWithNoneAuthTypeResponse; +import software.amazon.awssdk.services.query.model.OperationWithRequestCompressionRequest; +import software.amazon.awssdk.services.query.model.OperationWithRequestCompressionResponse; +import software.amazon.awssdk.services.query.model.OperationWithStaticContextParamsRequest; +import software.amazon.awssdk.services.query.model.OperationWithStaticContextParamsResponse; +import software.amazon.awssdk.services.query.model.PutOperationWithChecksumRequest; +import software.amazon.awssdk.services.query.model.PutOperationWithChecksumResponse; +import software.amazon.awssdk.services.query.model.QueryException; +import software.amazon.awssdk.services.query.model.StreamingInputOperationRequest; +import software.amazon.awssdk.services.query.model.StreamingInputOperationResponse; +import software.amazon.awssdk.services.query.model.StreamingOutputOperationRequest; +import software.amazon.awssdk.services.query.model.StreamingOutputOperationResponse; +import software.amazon.awssdk.services.query.transform.APostOperationRequestMarshaller; +import software.amazon.awssdk.services.query.transform.APostOperationWithOutputRequestMarshaller; +import software.amazon.awssdk.services.query.transform.BearerAuthOperationRequestMarshaller; +import software.amazon.awssdk.services.query.transform.GetOperationWithChecksumRequestMarshaller; +import software.amazon.awssdk.services.query.transform.OperationWithChecksumRequiredRequestMarshaller; +import software.amazon.awssdk.services.query.transform.OperationWithContextParamRequestMarshaller; +import software.amazon.awssdk.services.query.transform.OperationWithNoneAuthTypeRequestMarshaller; +import software.amazon.awssdk.services.query.transform.OperationWithRequestCompressionRequestMarshaller; +import software.amazon.awssdk.services.query.transform.OperationWithStaticContextParamsRequestMarshaller; +import software.amazon.awssdk.services.query.transform.PutOperationWithChecksumRequestMarshaller; +import software.amazon.awssdk.services.query.transform.StreamingInputOperationRequestMarshaller; +import software.amazon.awssdk.services.query.transform.StreamingOutputOperationRequestMarshaller; +import software.amazon.awssdk.services.query.waiters.QueryAsyncWaiter; +import software.amazon.awssdk.utils.CompletableFutureUtils; +import software.amazon.awssdk.utils.Pair; + +/** + * Internal implementation of {@link QueryAsyncClient}. + * + * @see QueryAsyncClient#builder() + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +final class DefaultQueryAsyncClient implements QueryAsyncClient { + private static final Logger log = LoggerFactory.getLogger(DefaultQueryAsyncClient.class); + + private final AsyncClientHandler clientHandler; + + private final AwsQueryProtocolFactory protocolFactory; + + private final SdkClientConfiguration clientConfiguration; + + private final QueryServiceClientConfiguration serviceClientConfiguration; + + private final ScheduledExecutorService executorService; + + protected DefaultQueryAsyncClient(QueryServiceClientConfiguration serviceClientConfiguration, + SdkClientConfiguration clientConfiguration) { + this.clientHandler = new AwsAsyncClientHandler(clientConfiguration); + this.clientConfiguration = clientConfiguration; + this.serviceClientConfiguration = serviceClientConfiguration; + this.protocolFactory = init(); + this.executorService = clientConfiguration.option(SdkClientOption.SCHEDULED_EXECUTOR_SERVICE); + } + + /** + *

+ * Performs a post operation to the query service and has no output + *

+ * + * @param aPostOperationRequest + * @return A Java Future containing the result of the APostOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • InvalidInputException The request was rejected because an invalid or out-of-range value was supplied + * for an input parameter.
  • + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an + * instance of this type.
  • + *
+ * @sample QueryAsyncClient.APostOperation + * @see AWS + * API Documentation + */ + @Override + public CompletableFuture aPostOperation(APostOperationRequest aPostOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperation"); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(APostOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + String hostPrefix = "foo-"; + String resolvedHostExpression = "foo-"; + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("APostOperation") + .withMarshaller(new APostOperationRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .hostPrefixExpression(resolvedHostExpression).withInput(aPostOperationRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + *

+ * Performs a post operation to the query service and has modelled output + *

+ * + * @param aPostOperationWithOutputRequest + * @return A Java Future containing the result of the APostOperationWithOutput operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • InvalidInputException The request was rejected because an invalid or out-of-range value was supplied + * for an input parameter.
  • + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an + * instance of this type.
  • + *
+ * @sample QueryAsyncClient.APostOperationWithOutput + * @see AWS API Documentation + */ + @Override + public CompletableFuture aPostOperationWithOutput( + APostOperationWithOutputRequest aPostOperationWithOutputRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperationWithOutput"); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(APostOperationWithOutputResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("APostOperationWithOutput") + .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(aPostOperationWithOutputRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the BearerAuthOperation operation asynchronously. + * + * @param bearerAuthOperationRequest + * @return A Java Future containing the result of the BearerAuthOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an + * instance of this type.
  • + *
+ * @sample QueryAsyncClient.BearerAuthOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture bearerAuthOperation( + BearerAuthOperationRequest bearerAuthOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(bearerAuthOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, bearerAuthOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "BearerAuthOperation"); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(BearerAuthOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("BearerAuthOperation") + .withMarshaller(new BearerAuthOperationRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .credentialType(CredentialType.TOKEN).withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector).withInput(bearerAuthOperationRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the GetOperationWithChecksum operation asynchronously. + * + * @param getOperationWithChecksumRequest + * @return A Java Future containing the result of the GetOperationWithChecksum operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an + * instance of this type.
  • + *
+ * @sample QueryAsyncClient.GetOperationWithChecksum + * @see AWS API Documentation + */ + @Override + public CompletableFuture getOperationWithChecksum( + GetOperationWithChecksumRequest getOperationWithChecksumRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getOperationWithChecksumRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, getOperationWithChecksumRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetOperationWithChecksum"); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(GetOperationWithChecksumResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("GetOperationWithChecksum") + .withMarshaller(new GetOperationWithChecksumRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute( + SdkInternalExecutionAttribute.HTTP_CHECKSUM, + HttpChecksum.builder().requestChecksumRequired(true) + .requestAlgorithm(getOperationWithChecksumRequest.checksumAlgorithmAsString()) + .isRequestStreaming(false).build()).withInput(getOperationWithChecksumRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithChecksumRequired operation asynchronously. + * + * @param operationWithChecksumRequiredRequest + * @return A Java Future containing the result of the OperationWithChecksumRequired operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an + * instance of this type.
  • + *
+ * @sample QueryAsyncClient.OperationWithChecksumRequired + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithChecksumRequired( + OperationWithChecksumRequiredRequest operationWithChecksumRequiredRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithChecksumRequired"); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(OperationWithChecksumRequiredResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithChecksumRequired") + .withMarshaller(new OperationWithChecksumRequiredRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, + HttpChecksumRequired.create()).withInput(operationWithChecksumRequiredRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithContextParam operation asynchronously. + * + * @param operationWithContextParamRequest + * @return A Java Future containing the result of the OperationWithContextParam operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an + * instance of this type.
  • + *
+ * @sample QueryAsyncClient.OperationWithContextParam + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithContextParam( + OperationWithContextParamRequest operationWithContextParamRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithContextParamRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithContextParamRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithContextParam"); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(OperationWithContextParamResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithContextParam") + .withMarshaller(new OperationWithContextParamRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(operationWithContextParamRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithNoneAuthType operation asynchronously. + * + * @param operationWithNoneAuthTypeRequest + * @return A Java Future containing the result of the OperationWithNoneAuthType operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an + * instance of this type.
  • + *
+ * @sample QueryAsyncClient.OperationWithNoneAuthType + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithNoneAuthType( + OperationWithNoneAuthTypeRequest operationWithNoneAuthTypeRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithNoneAuthTypeRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithNoneAuthTypeRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithNoneAuthType"); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(OperationWithNoneAuthTypeResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithNoneAuthType") + .withMarshaller(new OperationWithNoneAuthTypeRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(operationWithNoneAuthTypeRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithRequestCompression operation asynchronously. + * + * @param operationWithRequestCompressionRequest + * @return A Java Future containing the result of the OperationWithRequestCompression operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an + * instance of this type.
  • + *
+ * @sample QueryAsyncClient.OperationWithRequestCompression + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithRequestCompression( + OperationWithRequestCompressionRequest operationWithRequestCompressionRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithRequestCompression"); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(OperationWithRequestCompressionResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithRequestCompression") + .withMarshaller(new OperationWithRequestCompressionRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, + RequestCompression.builder().encodings("gzip").isStreaming(false).build()) + .withInput(operationWithRequestCompressionRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithStaticContextParams operation asynchronously. + * + * @param operationWithStaticContextParamsRequest + * @return A Java Future containing the result of the OperationWithStaticContextParams operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an + * instance of this type.
  • + *
+ * @sample QueryAsyncClient.OperationWithStaticContextParams + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithStaticContextParams( + OperationWithStaticContextParamsRequest operationWithStaticContextParamsRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithStaticContextParamsRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithStaticContextParamsRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithStaticContextParams"); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(OperationWithStaticContextParamsResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithStaticContextParams") + .withMarshaller(new OperationWithStaticContextParamsRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(operationWithStaticContextParamsRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the PutOperationWithChecksum operation asynchronously. + * + * @param putOperationWithChecksumRequest + * @param requestBody + * Functional interface that can be implemented to produce the request content in a non-blocking manner. The + * size of the content is expected to be known up front. See {@link AsyncRequestBody} for specific details on + * implementing this interface as well as links to precanned implementations for common scenarios like + * uploading from a file. The service documentation for the request content is as follows ' + *

+ * Object data. + *

+ * ' + * @param asyncResponseTransformer + * The response transformer for processing the streaming response in a non-blocking manner. See + * {@link AsyncResponseTransformer} for details on how this callback should be implemented and for links to + * precanned implementations for common scenarios like downloading to a file. The service documentation for + * the response content is as follows ' + *

+ * Object data. + *

+ * '. + * @return A future to the transformed result of the AsyncResponseTransformer.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an + * instance of this type.
  • + *
+ * @sample QueryAsyncClient.PutOperationWithChecksum + * @see AWS API Documentation + */ + @Override + public CompletableFuture putOperationWithChecksum( + PutOperationWithChecksumRequest putOperationWithChecksumRequest, AsyncRequestBody requestBody, + AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(putOperationWithChecksumRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, putOperationWithChecksumRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PutOperationWithChecksum"); + Pair, CompletableFuture> pair = AsyncResponseTransformerUtils + .wrapWithEndOfStreamFuture(asyncResponseTransformer); + asyncResponseTransformer = pair.left(); + CompletableFuture endOfStreamFuture = pair.right(); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(PutOperationWithChecksumResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("PutOperationWithChecksum") + .withMarshaller( + AsyncStreamingRequestMarshaller.builder() + .delegateMarshaller(new PutOperationWithChecksumRequestMarshaller(protocolFactory)) + .asyncRequestBody(requestBody).build()) + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute( + SdkInternalExecutionAttribute.HTTP_CHECKSUM, + HttpChecksum.builder().requestChecksumRequired(false) + .requestValidationMode(putOperationWithChecksumRequest.checksumModeAsString()) + .responseAlgorithms("CRC32C", "CRC32", "SHA1", "SHA256").isRequestStreaming(true) + .build()).withAsyncRequestBody(requestBody) + .withInput(putOperationWithChecksumRequest), asyncResponseTransformer); + CompletableFuture whenCompleteFuture = null; + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + if (e != null) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(e)); + } + endOfStreamFuture.whenComplete((r2, e2) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some operation with a streaming input + * + * @param streamingInputOperationRequest + * @param requestBody + * Functional interface that can be implemented to produce the request content in a non-blocking manner. The + * size of the content is expected to be known up front. See {@link AsyncRequestBody} for specific details on + * implementing this interface as well as links to precanned implementations for common scenarios like + * uploading from a file. The service documentation for the request content is as follows 'This be a stream' + * @return A Java Future containing the result of the StreamingInputOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an + * instance of this type.
  • + *
+ * @sample QueryAsyncClient.StreamingInputOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture streamingInputOperation( + StreamingInputOperationRequest streamingInputOperationRequest, AsyncRequestBody requestBody) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOperation"); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(StreamingInputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("StreamingInputOperation") + .withMarshaller( + AsyncStreamingRequestMarshaller.builder() + .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) + .asyncRequestBody(requestBody).build()).withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector).withAsyncRequestBody(requestBody) + .withInput(streamingInputOperationRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some operation with a streaming output + * + * @param streamingOutputOperationRequest + * @param asyncResponseTransformer + * The response transformer for processing the streaming response in a non-blocking manner. See + * {@link AsyncResponseTransformer} for details on how this callback should be implemented and for links to + * precanned implementations for common scenarios like downloading to a file. The service documentation for + * the response content is as follows 'This be a stream'. + * @return A future to the transformed result of the AsyncResponseTransformer.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an + * instance of this type.
  • + *
+ * @sample QueryAsyncClient.StreamingOutputOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture streamingOutputOperation( + StreamingOutputOperationRequest streamingOutputOperationRequest, + AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingOutputOperation"); + Pair, CompletableFuture> pair = AsyncResponseTransformerUtils + .wrapWithEndOfStreamFuture(asyncResponseTransformer); + asyncResponseTransformer = pair.left(); + CompletableFuture endOfStreamFuture = pair.right(); + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(StreamingOutputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("StreamingOutputOperation") + .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(streamingOutputOperationRequest), asyncResponseTransformer); + CompletableFuture whenCompleteFuture = null; + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + if (e != null) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(e)); + } + endOfStreamFuture.whenComplete((r2, e2) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + @Override + public QueryAsyncWaiter waiter() { + return QueryAsyncWaiter.builder().client(this).scheduledExecutorService(executorService).build(); + } + + @Override + public final QueryServiceClientConfiguration serviceClientConfiguration() { + return this.serviceClientConfiguration; + } + + @Override + public final String serviceName() { + return SERVICE_NAME; + } + + private AwsQueryProtocolFactory init() { + return AwsQueryProtocolFactory + .builder() + .registerModeledException( + ExceptionMetadata.builder().errorCode("InvalidInput") + .exceptionBuilderSupplier(InvalidInputException::builder).httpStatusCode(400).build()) + .clientConfiguration(clientConfiguration).defaultServiceExceptionSupplier(QueryException::builder).build(); + } + + private static List resolveMetricPublishers(SdkClientConfiguration clientConfiguration, + RequestOverrideConfiguration requestOverrideConfiguration) { + List publishers = null; + if (requestOverrideConfiguration != null) { + publishers = requestOverrideConfiguration.metricPublishers(); + } + if (publishers == null || publishers.isEmpty()) { + publishers = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHERS); + } + if (publishers == null) { + publishers = Collections.emptyList(); + } + return publishers; + } + + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + QueryServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = QueryServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + @Override + public void close() { + clientHandler.close(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-query-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-query-client-class.java new file mode 100644 index 000000000000..7b97aeeba6c1 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-query-client-class.java @@ -0,0 +1,805 @@ +package software.amazon.awssdk.services.query; + +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.CredentialType; +import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.client.handler.ClientExecutionParams; +import software.amazon.awssdk.core.client.handler.SyncClientHandler; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksum; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksumRequired; +import software.amazon.awssdk.core.internal.interceptor.trait.RequestCompression; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.core.runtime.transform.StreamingRequestMarshaller; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.NoOpMetricCollector; +import software.amazon.awssdk.protocols.core.ExceptionMetadata; +import software.amazon.awssdk.protocols.query.AwsQueryProtocolFactory; +import software.amazon.awssdk.services.query.internal.QueryServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.query.model.APostOperationRequest; +import software.amazon.awssdk.services.query.model.APostOperationResponse; +import software.amazon.awssdk.services.query.model.APostOperationWithOutputRequest; +import software.amazon.awssdk.services.query.model.APostOperationWithOutputResponse; +import software.amazon.awssdk.services.query.model.BearerAuthOperationRequest; +import software.amazon.awssdk.services.query.model.BearerAuthOperationResponse; +import software.amazon.awssdk.services.query.model.GetOperationWithChecksumRequest; +import software.amazon.awssdk.services.query.model.GetOperationWithChecksumResponse; +import software.amazon.awssdk.services.query.model.InvalidInputException; +import software.amazon.awssdk.services.query.model.OperationWithChecksumRequiredRequest; +import software.amazon.awssdk.services.query.model.OperationWithChecksumRequiredResponse; +import software.amazon.awssdk.services.query.model.OperationWithContextParamRequest; +import software.amazon.awssdk.services.query.model.OperationWithContextParamResponse; +import software.amazon.awssdk.services.query.model.OperationWithNoneAuthTypeRequest; +import software.amazon.awssdk.services.query.model.OperationWithNoneAuthTypeResponse; +import software.amazon.awssdk.services.query.model.OperationWithRequestCompressionRequest; +import software.amazon.awssdk.services.query.model.OperationWithRequestCompressionResponse; +import software.amazon.awssdk.services.query.model.OperationWithStaticContextParamsRequest; +import software.amazon.awssdk.services.query.model.OperationWithStaticContextParamsResponse; +import software.amazon.awssdk.services.query.model.PutOperationWithChecksumRequest; +import software.amazon.awssdk.services.query.model.PutOperationWithChecksumResponse; +import software.amazon.awssdk.services.query.model.QueryException; +import software.amazon.awssdk.services.query.model.StreamingInputOperationRequest; +import software.amazon.awssdk.services.query.model.StreamingInputOperationResponse; +import software.amazon.awssdk.services.query.model.StreamingOutputOperationRequest; +import software.amazon.awssdk.services.query.model.StreamingOutputOperationResponse; +import software.amazon.awssdk.services.query.transform.APostOperationRequestMarshaller; +import software.amazon.awssdk.services.query.transform.APostOperationWithOutputRequestMarshaller; +import software.amazon.awssdk.services.query.transform.BearerAuthOperationRequestMarshaller; +import software.amazon.awssdk.services.query.transform.GetOperationWithChecksumRequestMarshaller; +import software.amazon.awssdk.services.query.transform.OperationWithChecksumRequiredRequestMarshaller; +import software.amazon.awssdk.services.query.transform.OperationWithContextParamRequestMarshaller; +import software.amazon.awssdk.services.query.transform.OperationWithNoneAuthTypeRequestMarshaller; +import software.amazon.awssdk.services.query.transform.OperationWithRequestCompressionRequestMarshaller; +import software.amazon.awssdk.services.query.transform.OperationWithStaticContextParamsRequestMarshaller; +import software.amazon.awssdk.services.query.transform.PutOperationWithChecksumRequestMarshaller; +import software.amazon.awssdk.services.query.transform.StreamingInputOperationRequestMarshaller; +import software.amazon.awssdk.services.query.transform.StreamingOutputOperationRequestMarshaller; +import software.amazon.awssdk.services.query.waiters.QueryWaiter; +import software.amazon.awssdk.utils.Logger; + +/** + * Internal implementation of {@link QueryClient}. + * + * @see QueryClient#builder() + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +final class DefaultQueryClient implements QueryClient { + private static final Logger log = Logger.loggerFor(DefaultQueryClient.class); + + private final SyncClientHandler clientHandler; + + private final AwsQueryProtocolFactory protocolFactory; + + private final SdkClientConfiguration clientConfiguration; + + private final QueryServiceClientConfiguration serviceClientConfiguration; + + protected DefaultQueryClient(QueryServiceClientConfiguration serviceClientConfiguration, + SdkClientConfiguration clientConfiguration) { + this.clientHandler = new AwsSyncClientHandler(clientConfiguration); + this.clientConfiguration = clientConfiguration; + this.serviceClientConfiguration = serviceClientConfiguration; + this.protocolFactory = init(); + } + + /** + *

+ * Performs a post operation to the query service and has no output + *

+ * + * @param aPostOperationRequest + * @return Result of the APostOperation operation returned by the service. + * @throws InvalidInputException + * The request was rejected because an invalid or out-of-range value was supplied for an input parameter. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws QueryException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample QueryClient.APostOperation + * @see AWS + * API Documentation + */ + @Override + public APostOperationResponse aPostOperation(APostOperationRequest aPostOperationRequest) throws InvalidInputException, + AwsServiceException, SdkClientException, QueryException { + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(APostOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperation"); + String hostPrefix = "foo-"; + String resolvedHostExpression = "foo-"; + + return clientHandler.execute(new ClientExecutionParams() + .withOperationName("APostOperation").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).hostPrefixExpression(resolvedHostExpression) + .withRequestConfiguration(clientConfiguration).withInput(aPostOperationRequest) + .withMetricCollector(apiCallMetricCollector) + .withMarshaller(new APostOperationRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + *

+ * Performs a post operation to the query service and has modelled output + *

+ * + * @param aPostOperationWithOutputRequest + * @return Result of the APostOperationWithOutput operation returned by the service. + * @throws InvalidInputException + * The request was rejected because an invalid or out-of-range value was supplied for an input parameter. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws QueryException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample QueryClient.APostOperationWithOutput + * @see AWS API Documentation + */ + @Override + public APostOperationWithOutputResponse aPostOperationWithOutput( + APostOperationWithOutputRequest aPostOperationWithOutputRequest) throws InvalidInputException, AwsServiceException, + SdkClientException, QueryException { + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(APostOperationWithOutputResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperationWithOutput"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("APostOperationWithOutput").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(aPostOperationWithOutputRequest).withMetricCollector(apiCallMetricCollector) + .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the BearerAuthOperation operation. + * + * @param bearerAuthOperationRequest + * @return Result of the BearerAuthOperation operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws QueryException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample QueryClient.BearerAuthOperation + * @see AWS API Documentation + */ + @Override + public BearerAuthOperationResponse bearerAuthOperation(BearerAuthOperationRequest bearerAuthOperationRequest) + throws AwsServiceException, SdkClientException, QueryException { + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(BearerAuthOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(bearerAuthOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, bearerAuthOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "BearerAuthOperation"); + + return clientHandler.execute(new ClientExecutionParams() + .withOperationName("BearerAuthOperation").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).credentialType(CredentialType.TOKEN) + .withRequestConfiguration(clientConfiguration).withInput(bearerAuthOperationRequest) + .withMetricCollector(apiCallMetricCollector) + .withMarshaller(new BearerAuthOperationRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the GetOperationWithChecksum operation. + * + * @param getOperationWithChecksumRequest + * @return Result of the GetOperationWithChecksum operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws QueryException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample QueryClient.GetOperationWithChecksum + * @see AWS API Documentation + */ + @Override + public GetOperationWithChecksumResponse getOperationWithChecksum( + GetOperationWithChecksumRequest getOperationWithChecksumRequest) throws AwsServiceException, SdkClientException, + QueryException { + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(GetOperationWithChecksumResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getOperationWithChecksumRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, getOperationWithChecksumRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetOperationWithChecksum"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("GetOperationWithChecksum") + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withInput(getOperationWithChecksumRequest) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute( + SdkInternalExecutionAttribute.HTTP_CHECKSUM, + HttpChecksum.builder().requestChecksumRequired(true) + .requestAlgorithm(getOperationWithChecksumRequest.checksumAlgorithmAsString()) + .isRequestStreaming(false).build()) + .withMarshaller(new GetOperationWithChecksumRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the OperationWithChecksumRequired operation. + * + * @param operationWithChecksumRequiredRequest + * @return Result of the OperationWithChecksumRequired operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws QueryException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample QueryClient.OperationWithChecksumRequired + * @see AWS API Documentation + */ + @Override + public OperationWithChecksumRequiredResponse operationWithChecksumRequired( + OperationWithChecksumRequiredRequest operationWithChecksumRequiredRequest) throws AwsServiceException, + SdkClientException, QueryException { + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(OperationWithChecksumRequiredResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithChecksumRequired"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithChecksumRequired") + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withInput(operationWithChecksumRequiredRequest) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, + HttpChecksumRequired.create()) + .withMarshaller(new OperationWithChecksumRequiredRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the OperationWithContextParam operation. + * + * @param operationWithContextParamRequest + * @return Result of the OperationWithContextParam operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws QueryException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample QueryClient.OperationWithContextParam + * @see AWS API Documentation + */ + @Override + public OperationWithContextParamResponse operationWithContextParam( + OperationWithContextParamRequest operationWithContextParamRequest) throws AwsServiceException, SdkClientException, + QueryException { + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(OperationWithContextParamResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithContextParamRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithContextParamRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithContextParam"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithContextParam").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(operationWithContextParamRequest).withMetricCollector(apiCallMetricCollector) + .withMarshaller(new OperationWithContextParamRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the OperationWithNoneAuthType operation. + * + * @param operationWithNoneAuthTypeRequest + * @return Result of the OperationWithNoneAuthType operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws QueryException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample QueryClient.OperationWithNoneAuthType + * @see AWS API Documentation + */ + @Override + public OperationWithNoneAuthTypeResponse operationWithNoneAuthType( + OperationWithNoneAuthTypeRequest operationWithNoneAuthTypeRequest) throws AwsServiceException, SdkClientException, + QueryException { + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(OperationWithNoneAuthTypeResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithNoneAuthTypeRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithNoneAuthTypeRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithNoneAuthType"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithNoneAuthType").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(operationWithNoneAuthTypeRequest).withMetricCollector(apiCallMetricCollector) + .withMarshaller(new OperationWithNoneAuthTypeRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the OperationWithRequestCompression operation. + * + * @param operationWithRequestCompressionRequest + * @return Result of the OperationWithRequestCompression operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws QueryException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample QueryClient.OperationWithRequestCompression + * @see AWS API Documentation + */ + @Override + public OperationWithRequestCompressionResponse operationWithRequestCompression( + OperationWithRequestCompressionRequest operationWithRequestCompressionRequest) throws AwsServiceException, + SdkClientException, QueryException { + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(OperationWithRequestCompressionResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithRequestCompression"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithRequestCompression") + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withInput(operationWithRequestCompressionRequest) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, + RequestCompression.builder().encodings("gzip").isStreaming(false).build()) + .withMarshaller(new OperationWithRequestCompressionRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the OperationWithStaticContextParams operation. + * + * @param operationWithStaticContextParamsRequest + * @return Result of the OperationWithStaticContextParams operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws QueryException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample QueryClient.OperationWithStaticContextParams + * @see AWS API Documentation + */ + @Override + public OperationWithStaticContextParamsResponse operationWithStaticContextParams( + OperationWithStaticContextParamsRequest operationWithStaticContextParamsRequest) throws AwsServiceException, + SdkClientException, QueryException { + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(OperationWithStaticContextParamsResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithStaticContextParamsRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithStaticContextParamsRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithStaticContextParams"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithStaticContextParams").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(operationWithStaticContextParamsRequest).withMetricCollector(apiCallMetricCollector) + .withMarshaller(new OperationWithStaticContextParamsRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the PutOperationWithChecksum operation. + * + * @param putOperationWithChecksumRequest + * @param requestBody + * The content to send to the service. A {@link RequestBody} can be created using one of several factory + * methods for various sources of data. For example, to create a request body from a file you can do the + * following. + * + *
+     * {@code RequestBody.fromFile(new File("myfile.txt"))}
+     * 
+ * + * See documentation in {@link RequestBody} for additional details and which sources of data are supported. + * The service documentation for the request content is as follows ' + *

+ * Object data. + *

+ * ' + * @param responseTransformer + * Functional interface for processing the streamed response content. The unmarshalled + * PutOperationWithChecksumResponse and an InputStream to the response content are provided as parameters to + * the callback. The callback may return a transformed type which will be the return value of this method. + * See {@link software.amazon.awssdk.core.sync.ResponseTransformer} for details on implementing this + * interface and for links to pre-canned implementations for common scenarios like downloading to a file. The + * service documentation for the response content is as follows ' + *

+ * Object data. + *

+ * '. + * @return The transformed result of the ResponseTransformer. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws QueryException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample QueryClient.PutOperationWithChecksum + * @see AWS API Documentation + */ + @Override + public ReturnT putOperationWithChecksum(PutOperationWithChecksumRequest putOperationWithChecksumRequest, + RequestBody requestBody, ResponseTransformer responseTransformer) + throws AwsServiceException, SdkClientException, QueryException { + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(PutOperationWithChecksumResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(putOperationWithChecksumRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, putOperationWithChecksumRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PutOperationWithChecksum"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("PutOperationWithChecksum") + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withInput(putOperationWithChecksumRequest) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute( + SdkInternalExecutionAttribute.HTTP_CHECKSUM, + HttpChecksum.builder().requestChecksumRequired(false) + .requestValidationMode(putOperationWithChecksumRequest.checksumModeAsString()) + .responseAlgorithms("CRC32C", "CRC32", "SHA1", "SHA256").isRequestStreaming(true) + .build()) + .withRequestBody(requestBody) + .withMarshaller( + StreamingRequestMarshaller.builder() + .delegateMarshaller(new PutOperationWithChecksumRequestMarshaller(protocolFactory)) + .requestBody(requestBody).build())); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Some operation with a streaming input + * + * @param streamingInputOperationRequest + * @param requestBody + * The content to send to the service. A {@link RequestBody} can be created using one of several factory + * methods for various sources of data. For example, to create a request body from a file you can do the + * following. + * + *
+     * {@code RequestBody.fromFile(new File("myfile.txt"))}
+     * 
+ * + * See documentation in {@link RequestBody} for additional details and which sources of data are supported. + * The service documentation for the request content is as follows 'This be a stream' + * @return Result of the StreamingInputOperation operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws QueryException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample QueryClient.StreamingInputOperation + * @see AWS API Documentation + */ + @Override + public StreamingInputOperationResponse streamingInputOperation(StreamingInputOperationRequest streamingInputOperationRequest, + RequestBody requestBody) throws AwsServiceException, SdkClientException, QueryException { + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(StreamingInputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOperation"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("StreamingInputOperation") + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withInput(streamingInputOperationRequest) + .withMetricCollector(apiCallMetricCollector) + .withRequestBody(requestBody) + .withMarshaller( + StreamingRequestMarshaller.builder() + .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) + .requestBody(requestBody).build())); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Some operation with a streaming output + * + * @param streamingOutputOperationRequest + * @param responseTransformer + * Functional interface for processing the streamed response content. The unmarshalled + * StreamingOutputOperationResponse and an InputStream to the response content are provided as parameters to + * the callback. The callback may return a transformed type which will be the return value of this method. + * See {@link software.amazon.awssdk.core.sync.ResponseTransformer} for details on implementing this + * interface and for links to pre-canned implementations for common scenarios like downloading to a file. The + * service documentation for the response content is as follows 'This be a stream'. + * @return The transformed result of the ResponseTransformer. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws QueryException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample QueryClient.StreamingOutputOperation + * @see AWS API Documentation + */ + @Override + public ReturnT streamingOutputOperation(StreamingOutputOperationRequest streamingOutputOperationRequest, + ResponseTransformer responseTransformer) throws AwsServiceException, + SdkClientException, QueryException { + + HttpResponseHandler responseHandler = protocolFactory + .createResponseHandler(StreamingOutputOperationResponse::builder); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingOutputOperation"); + + return clientHandler.execute( + new ClientExecutionParams() + .withOperationName("StreamingOutputOperation").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(streamingOutputOperationRequest).withMetricCollector(apiCallMetricCollector) + .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)), responseTransformer); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Create an instance of {@link QueryWaiter} using this client. + *

+ * Waiters created via this method are managed by the SDK and resources will be released when the service client is + * closed. + * + * @return an instance of {@link QueryWaiter} + */ + @Override + public QueryWaiter waiter() { + return QueryWaiter.builder().client(this).build(); + } + + @Override + public final String serviceName() { + return SERVICE_NAME; + } + + private static List resolveMetricPublishers(SdkClientConfiguration clientConfiguration, + RequestOverrideConfiguration requestOverrideConfiguration) { + List publishers = null; + if (requestOverrideConfiguration != null) { + publishers = requestOverrideConfiguration.metricPublishers(); + } + if (publishers == null || publishers.isEmpty()) { + publishers = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHERS); + } + if (publishers == null) { + publishers = Collections.emptyList(); + } + return publishers; + } + + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + QueryServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = QueryServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + private AwsQueryProtocolFactory init() { + return AwsQueryProtocolFactory + .builder() + .registerModeledException( + ExceptionMetadata.builder().errorCode("InvalidInput") + .exceptionBuilderSupplier(InvalidInputException::builder).httpStatusCode(400).build()) + .clientConfiguration(clientConfiguration).defaultServiceExceptionSupplier(QueryException::builder).build(); + } + + @Override + public final QueryServiceClientConfiguration serviceClientConfiguration() { + return this.serviceClientConfiguration; + } + + @Override + public void close() { + clientHandler.close(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-xml-async-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-xml-async-client-class.java new file mode 100644 index 000000000000..27db7b8869d4 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-xml-async-client-class.java @@ -0,0 +1,884 @@ +package software.amazon.awssdk.services.xml; + +import static software.amazon.awssdk.utils.FunctionalUtils.runAndLogError; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.handler.AwsAsyncClientHandler; +import software.amazon.awssdk.awscore.eventstream.EventStreamAsyncResponseTransformer; +import software.amazon.awssdk.awscore.eventstream.EventStreamTaggedUnionPojoSupplier; +import software.amazon.awssdk.awscore.eventstream.RestEventStreamAsyncResponseTransformer; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.CredentialType; +import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.Response; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkPojoBuilder; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.core.async.AsyncResponseTransformerUtils; +import software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.client.handler.AsyncClientHandler; +import software.amazon.awssdk.core.client.handler.ClientExecutionParams; +import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksum; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksumRequired; +import software.amazon.awssdk.core.internal.interceptor.trait.RequestCompression; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.core.runtime.transform.AsyncStreamingRequestMarshaller; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.NoOpMetricCollector; +import software.amazon.awssdk.protocols.core.ExceptionMetadata; +import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory; +import software.amazon.awssdk.protocols.xml.XmlOperationMetadata; +import software.amazon.awssdk.services.xml.internal.XmlServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.xml.model.APostOperationRequest; +import software.amazon.awssdk.services.xml.model.APostOperationResponse; +import software.amazon.awssdk.services.xml.model.APostOperationWithOutputRequest; +import software.amazon.awssdk.services.xml.model.APostOperationWithOutputResponse; +import software.amazon.awssdk.services.xml.model.BearerAuthOperationRequest; +import software.amazon.awssdk.services.xml.model.BearerAuthOperationResponse; +import software.amazon.awssdk.services.xml.model.EventStream; +import software.amazon.awssdk.services.xml.model.EventStreamOperationRequest; +import software.amazon.awssdk.services.xml.model.EventStreamOperationResponse; +import software.amazon.awssdk.services.xml.model.EventStreamOperationResponseHandler; +import software.amazon.awssdk.services.xml.model.GetOperationWithChecksumRequest; +import software.amazon.awssdk.services.xml.model.GetOperationWithChecksumResponse; +import software.amazon.awssdk.services.xml.model.InvalidInputException; +import software.amazon.awssdk.services.xml.model.OperationWithChecksumRequiredRequest; +import software.amazon.awssdk.services.xml.model.OperationWithChecksumRequiredResponse; +import software.amazon.awssdk.services.xml.model.OperationWithNoneAuthTypeRequest; +import software.amazon.awssdk.services.xml.model.OperationWithNoneAuthTypeResponse; +import software.amazon.awssdk.services.xml.model.OperationWithRequestCompressionRequest; +import software.amazon.awssdk.services.xml.model.OperationWithRequestCompressionResponse; +import software.amazon.awssdk.services.xml.model.PutOperationWithChecksumRequest; +import software.amazon.awssdk.services.xml.model.PutOperationWithChecksumResponse; +import software.amazon.awssdk.services.xml.model.StreamingInputOperationRequest; +import software.amazon.awssdk.services.xml.model.StreamingInputOperationResponse; +import software.amazon.awssdk.services.xml.model.StreamingOutputOperationRequest; +import software.amazon.awssdk.services.xml.model.StreamingOutputOperationResponse; +import software.amazon.awssdk.services.xml.model.XmlException; +import software.amazon.awssdk.services.xml.transform.APostOperationRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.APostOperationWithOutputRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.BearerAuthOperationRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.EventStreamOperationRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.GetOperationWithChecksumRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.OperationWithChecksumRequiredRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.OperationWithNoneAuthTypeRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.OperationWithRequestCompressionRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.PutOperationWithChecksumRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.StreamingInputOperationRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.StreamingOutputOperationRequestMarshaller; +import software.amazon.awssdk.utils.CompletableFutureUtils; +import software.amazon.awssdk.utils.Pair; + +/** + * Internal implementation of {@link XmlAsyncClient}. + * + * @see XmlAsyncClient#builder() + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +final class DefaultXmlAsyncClient implements XmlAsyncClient { + private static final Logger log = LoggerFactory.getLogger(DefaultXmlAsyncClient.class); + + private final AsyncClientHandler clientHandler; + + private final AwsXmlProtocolFactory protocolFactory; + + private final SdkClientConfiguration clientConfiguration; + + private final XmlServiceClientConfiguration serviceClientConfiguration; + + private final Executor executor; + + protected DefaultXmlAsyncClient(XmlServiceClientConfiguration serviceClientConfiguration, + SdkClientConfiguration clientConfiguration) { + this.clientHandler = new AwsAsyncClientHandler(clientConfiguration); + this.clientConfiguration = clientConfiguration; + this.serviceClientConfiguration = serviceClientConfiguration; + this.protocolFactory = init(); + this.executor = clientConfiguration.option(SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR); + } + + /** + *

+ * Performs a post operation to the xml service and has no output + *

+ * + * @param aPostOperationRequest + * @return A Java Future containing the result of the APostOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • InvalidInputException The request was rejected because an invalid or out-of-range value was supplied + * for an input parameter.
  • + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • XmlException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample XmlAsyncClient.APostOperation + * @see AWS + * API Documentation + */ + @Override + public CompletableFuture aPostOperation(APostOperationRequest aPostOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperation"); + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(APostOperationResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + String hostPrefix = "foo-"; + String resolvedHostExpression = "foo-"; + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("APostOperation").withRequestConfiguration(clientConfiguration) + .withMarshaller(new APostOperationRequestMarshaller(protocolFactory)) + .withCombinedResponseHandler(responseHandler).hostPrefixExpression(resolvedHostExpression) + .withMetricCollector(apiCallMetricCollector).withInput(aPostOperationRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + return whenCompleteFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + *

+ * Performs a post operation to the xml service and has modelled output + *

+ * + * @param aPostOperationWithOutputRequest + * @return A Java Future containing the result of the APostOperationWithOutput operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • InvalidInputException The request was rejected because an invalid or out-of-range value was supplied + * for an input parameter.
  • + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • XmlException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample XmlAsyncClient.APostOperationWithOutput + * @see AWS API Documentation + */ + @Override + public CompletableFuture aPostOperationWithOutput( + APostOperationWithOutputRequest aPostOperationWithOutputRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperationWithOutput"); + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(APostOperationWithOutputResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("APostOperationWithOutput").withRequestConfiguration(clientConfiguration) + .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory)) + .withCombinedResponseHandler(responseHandler).withMetricCollector(apiCallMetricCollector) + .withInput(aPostOperationWithOutputRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + return whenCompleteFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the BearerAuthOperation operation asynchronously. + * + * @param bearerAuthOperationRequest + * @return A Java Future containing the result of the BearerAuthOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • XmlException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample XmlAsyncClient.BearerAuthOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture bearerAuthOperation( + BearerAuthOperationRequest bearerAuthOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(bearerAuthOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, bearerAuthOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "BearerAuthOperation"); + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(BearerAuthOperationResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("BearerAuthOperation").withRequestConfiguration(clientConfiguration) + .withMarshaller(new BearerAuthOperationRequestMarshaller(protocolFactory)) + .withCombinedResponseHandler(responseHandler).credentialType(CredentialType.TOKEN) + .withMetricCollector(apiCallMetricCollector).withInput(bearerAuthOperationRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + return whenCompleteFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the EventStreamOperation operation asynchronously. + * + * @param eventStreamOperationRequest + * @return A Java Future containing the result of the EventStreamOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • XmlException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample XmlAsyncClient.EventStreamOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture eventStreamOperation(EventStreamOperationRequest eventStreamOperationRequest, + EventStreamOperationResponseHandler asyncResponseHandler) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, eventStreamOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "EventStreamOperation"); + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + EventStreamOperationResponse::builder, XmlOperationMetadata.builder().hasStreamingSuccessResponse(true) + .build()); + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + HttpResponseHandler eventResponseHandler = protocolFactory.createResponseHandler( + EventStreamTaggedUnionPojoSupplier.builder() + .putSdkPojoSupplier("EventPayloadEvent", EventStream::eventPayloadEventBuilder) + .putSdkPojoSupplier("NonEventPayloadEvent", EventStream::nonEventPayloadEventBuilder) + .putSdkPojoSupplier("SecondEventPayloadEvent", EventStream::secondEventPayloadEventBuilder) + .defaultSdkPojoSupplier(() -> new SdkPojoBuilder(EventStream.UNKNOWN)).build(), XmlOperationMetadata + .builder().hasStreamingSuccessResponse(false).build()); + CompletableFuture eventStreamTransformFuture = new CompletableFuture<>(); + EventStreamAsyncResponseTransformer asyncResponseTransformer = EventStreamAsyncResponseTransformer + . builder().eventStreamResponseHandler(asyncResponseHandler) + .eventResponseHandler(eventResponseHandler).initialResponseHandler(responseHandler) + .exceptionResponseHandler(errorResponseHandler).future(eventStreamTransformFuture).executor(executor) + .serviceName(serviceName()).build(); + RestEventStreamAsyncResponseTransformer restAsyncResponseTransformer = RestEventStreamAsyncResponseTransformer + . builder() + .eventStreamAsyncResponseTransformer(asyncResponseTransformer) + .eventStreamResponseHandler(asyncResponseHandler).build(); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("EventStreamOperation").withRequestConfiguration(clientConfiguration) + .withMarshaller(new EventStreamOperationRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withMetricCollector(apiCallMetricCollector).withInput(eventStreamOperationRequest), + restAsyncResponseTransformer); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + if (e != null) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> asyncResponseHandler.exceptionOccurred(e)); + eventStreamTransformFuture.completeExceptionally(e); + } + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + return CompletableFutureUtils.forwardExceptionTo(eventStreamTransformFuture, executeFuture); + } catch (Throwable t) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> asyncResponseHandler.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the GetOperationWithChecksum operation asynchronously. + * + * @param getOperationWithChecksumRequest + * @return A Java Future containing the result of the GetOperationWithChecksum operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • XmlException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample XmlAsyncClient.GetOperationWithChecksum + * @see AWS API Documentation + */ + @Override + public CompletableFuture getOperationWithChecksum( + GetOperationWithChecksumRequest getOperationWithChecksumRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getOperationWithChecksumRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, getOperationWithChecksumRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetOperationWithChecksum"); + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(GetOperationWithChecksumResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("GetOperationWithChecksum") + .withRequestConfiguration(clientConfiguration) + .withMarshaller(new GetOperationWithChecksumRequestMarshaller(protocolFactory)) + .withCombinedResponseHandler(responseHandler) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute( + SdkInternalExecutionAttribute.HTTP_CHECKSUM, + HttpChecksum.builder().requestChecksumRequired(true) + .requestAlgorithm(getOperationWithChecksumRequest.checksumAlgorithmAsString()) + .isRequestStreaming(false).build()).withInput(getOperationWithChecksumRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + return whenCompleteFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithChecksumRequired operation asynchronously. + * + * @param operationWithChecksumRequiredRequest + * @return A Java Future containing the result of the OperationWithChecksumRequired operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • XmlException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample XmlAsyncClient.OperationWithChecksumRequired + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithChecksumRequired( + OperationWithChecksumRequiredRequest operationWithChecksumRequiredRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithChecksumRequired"); + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(OperationWithChecksumRequiredResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithChecksumRequired") + .withRequestConfiguration(clientConfiguration) + .withMarshaller(new OperationWithChecksumRequiredRequestMarshaller(protocolFactory)) + .withCombinedResponseHandler(responseHandler) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, + HttpChecksumRequired.create()).withInput(operationWithChecksumRequiredRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + return whenCompleteFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithNoneAuthType operation asynchronously. + * + * @param operationWithNoneAuthTypeRequest + * @return A Java Future containing the result of the OperationWithNoneAuthType operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • XmlException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample XmlAsyncClient.OperationWithNoneAuthType + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithNoneAuthType( + OperationWithNoneAuthTypeRequest operationWithNoneAuthTypeRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithNoneAuthTypeRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithNoneAuthTypeRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithNoneAuthType"); + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(OperationWithNoneAuthTypeResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithNoneAuthType").withRequestConfiguration(clientConfiguration) + .withMarshaller(new OperationWithNoneAuthTypeRequestMarshaller(protocolFactory)) + .withCombinedResponseHandler(responseHandler).withMetricCollector(apiCallMetricCollector) + .withInput(operationWithNoneAuthTypeRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + return whenCompleteFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the OperationWithRequestCompression operation asynchronously. + * + * @param operationWithRequestCompressionRequest + * @return A Java Future containing the result of the OperationWithRequestCompression operation returned by the + * service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • XmlException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample XmlAsyncClient.OperationWithRequestCompression + * @see AWS API Documentation + */ + @Override + public CompletableFuture operationWithRequestCompression( + OperationWithRequestCompressionRequest operationWithRequestCompressionRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithRequestCompression"); + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(OperationWithRequestCompressionResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithRequestCompression") + .withRequestConfiguration(clientConfiguration) + .withMarshaller(new OperationWithRequestCompressionRequestMarshaller(protocolFactory)) + .withCombinedResponseHandler(responseHandler) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, + RequestCompression.builder().encodings("gzip").isStreaming(false).build()) + .withInput(operationWithRequestCompressionRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + return whenCompleteFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Invokes the PutOperationWithChecksum operation asynchronously. + * + * @param putOperationWithChecksumRequest + * @param requestBody + * Functional interface that can be implemented to produce the request content in a non-blocking manner. The + * size of the content is expected to be known up front. See {@link AsyncRequestBody} for specific details on + * implementing this interface as well as links to precanned implementations for common scenarios like + * uploading from a file. The service documentation for the request content is as follows ' + *

+ * Object data. + *

+ * ' + * @param asyncResponseTransformer + * The response transformer for processing the streaming response in a non-blocking manner. See + * {@link AsyncResponseTransformer} for details on how this callback should be implemented and for links to + * precanned implementations for common scenarios like downloading to a file. The service documentation for + * the response content is as follows ' + *

+ * Object data. + *

+ * '. + * @return A future to the transformed result of the AsyncResponseTransformer.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • XmlException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample XmlAsyncClient.PutOperationWithChecksum + * @see AWS API Documentation + */ + @Override + public CompletableFuture putOperationWithChecksum( + PutOperationWithChecksumRequest putOperationWithChecksumRequest, AsyncRequestBody requestBody, + AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(putOperationWithChecksumRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, putOperationWithChecksumRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PutOperationWithChecksum"); + Pair, CompletableFuture> pair = AsyncResponseTransformerUtils + .wrapWithEndOfStreamFuture(asyncResponseTransformer); + asyncResponseTransformer = pair.left(); + CompletableFuture endOfStreamFuture = pair.right(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + PutOperationWithChecksumResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(true)); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("PutOperationWithChecksum") + .withMarshaller( + AsyncStreamingRequestMarshaller.builder() + .delegateMarshaller(new PutOperationWithChecksumRequestMarshaller(protocolFactory)) + .asyncRequestBody(requestBody).build()) + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute( + SdkInternalExecutionAttribute.HTTP_CHECKSUM, + HttpChecksum.builder().requestChecksumRequired(false) + .requestValidationMode(putOperationWithChecksumRequest.checksumModeAsString()) + .responseAlgorithms("CRC32C", "CRC32", "SHA1", "SHA256").isRequestStreaming(true) + .build()).withAsyncRequestBody(requestBody) + .withInput(putOperationWithChecksumRequest), asyncResponseTransformer); + CompletableFuture whenCompleteFuture = null; + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + if (e != null) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(e)); + } + endOfStreamFuture.whenComplete((r2, e2) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some operation with a streaming input + * + * @param streamingInputOperationRequest + * @param requestBody + * Functional interface that can be implemented to produce the request content in a non-blocking manner. The + * size of the content is expected to be known up front. See {@link AsyncRequestBody} for specific details on + * implementing this interface as well as links to precanned implementations for common scenarios like + * uploading from a file. The service documentation for the request content is as follows 'This be a stream' + * @return A Java Future containing the result of the StreamingInputOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • XmlException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample XmlAsyncClient.StreamingInputOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture streamingInputOperation( + StreamingInputOperationRequest streamingInputOperationRequest, AsyncRequestBody requestBody) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOperation"); + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(StreamingInputOperationResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + + CompletableFuture executeFuture = clientHandler + .execute(new ClientExecutionParams() + .withOperationName("StreamingInputOperation") + .withRequestConfiguration(clientConfiguration) + .withMarshaller( + AsyncStreamingRequestMarshaller.builder() + .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) + .asyncRequestBody(requestBody).build()).withCombinedResponseHandler(responseHandler) + .withMetricCollector(apiCallMetricCollector).withAsyncRequestBody(requestBody) + .withInput(streamingInputOperationRequest)); + CompletableFuture whenCompleteFuture = null; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + return whenCompleteFuture; + } catch (Throwable t) { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + /** + * Some operation with a streaming output + * + * @param streamingOutputOperationRequest + * @param asyncResponseTransformer + * The response transformer for processing the streaming response in a non-blocking manner. See + * {@link AsyncResponseTransformer} for details on how this callback should be implemented and for links to + * precanned implementations for common scenarios like downloading to a file. The service documentation for + * the response content is as follows 'This be a stream'. + * @return A future to the transformed result of the AsyncResponseTransformer.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following + * exceptions. + *
    + *
  • SdkException Base class for all exceptions that can be thrown by the SDK (both service and client). + * Can be used for catch all scenarios.
  • + *
  • SdkClientException If any client side error occurs such as an IO related failure, failure to get + * credentials, etc.
  • + *
  • XmlException Base class for all service exceptions. Unknown exceptions will be thrown as an instance + * of this type.
  • + *
+ * @sample XmlAsyncClient.StreamingOutputOperation + * @see AWS API Documentation + */ + @Override + public CompletableFuture streamingOutputOperation( + StreamingOutputOperationRequest streamingOutputOperationRequest, + AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingOutputOperation"); + Pair, CompletableFuture> pair = AsyncResponseTransformerUtils + .wrapWithEndOfStreamFuture(asyncResponseTransformer); + asyncResponseTransformer = pair.left(); + CompletableFuture endOfStreamFuture = pair.right(); + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + StreamingOutputOperationResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(true)); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + + CompletableFuture executeFuture = clientHandler.execute( + new ClientExecutionParams() + .withOperationName("StreamingOutputOperation") + .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(streamingOutputOperationRequest), asyncResponseTransformer); + CompletableFuture whenCompleteFuture = null; + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + whenCompleteFuture = executeFuture.whenComplete((r, e) -> { + if (e != null) { + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(e)); + } + endOfStreamFuture.whenComplete((r2, e2) -> { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + }); + }); + return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture); + } catch (Throwable t) { + AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; + runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring", + () -> finalAsyncResponseTransformer.exceptionOccurred(t)); + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + return CompletableFutureUtils.failedFuture(t); + } + } + + @Override + public final XmlServiceClientConfiguration serviceClientConfiguration() { + return this.serviceClientConfiguration; + } + + @Override + public final String serviceName() { + return SERVICE_NAME; + } + + private AwsXmlProtocolFactory init() { + return AwsXmlProtocolFactory + .builder() + .registerModeledException( + ExceptionMetadata.builder().errorCode("InvalidInput") + .exceptionBuilderSupplier(InvalidInputException::builder).httpStatusCode(400).build()) + .clientConfiguration(clientConfiguration).defaultServiceExceptionSupplier(XmlException::builder).build(); + } + + private static List resolveMetricPublishers(SdkClientConfiguration clientConfiguration, + RequestOverrideConfiguration requestOverrideConfiguration) { + List publishers = null; + if (requestOverrideConfiguration != null) { + publishers = requestOverrideConfiguration.metricPublishers(); + } + if (publishers == null || publishers.isEmpty()) { + publishers = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHERS); + } + if (publishers == null) { + publishers = Collections.emptyList(); + } + return publishers; + } + + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + XmlServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = XmlServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + @Override + public void close() { + clientHandler.close(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-xml-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-xml-client-class.java new file mode 100644 index 000000000000..fb581b307b69 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-xml-client-class.java @@ -0,0 +1,680 @@ +package software.amazon.awssdk.services.xml; + +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.CredentialType; +import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.Response; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.client.handler.ClientExecutionParams; +import software.amazon.awssdk.core.client.handler.SyncClientHandler; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksum; +import software.amazon.awssdk.core.interceptor.trait.HttpChecksumRequired; +import software.amazon.awssdk.core.internal.interceptor.trait.RequestCompression; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.core.runtime.transform.StreamingRequestMarshaller; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.NoOpMetricCollector; +import software.amazon.awssdk.protocols.core.ExceptionMetadata; +import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory; +import software.amazon.awssdk.protocols.xml.XmlOperationMetadata; +import software.amazon.awssdk.services.xml.internal.XmlServiceClientConfigurationBuilder; +import software.amazon.awssdk.services.xml.model.APostOperationRequest; +import software.amazon.awssdk.services.xml.model.APostOperationResponse; +import software.amazon.awssdk.services.xml.model.APostOperationWithOutputRequest; +import software.amazon.awssdk.services.xml.model.APostOperationWithOutputResponse; +import software.amazon.awssdk.services.xml.model.BearerAuthOperationRequest; +import software.amazon.awssdk.services.xml.model.BearerAuthOperationResponse; +import software.amazon.awssdk.services.xml.model.GetOperationWithChecksumRequest; +import software.amazon.awssdk.services.xml.model.GetOperationWithChecksumResponse; +import software.amazon.awssdk.services.xml.model.InvalidInputException; +import software.amazon.awssdk.services.xml.model.OperationWithChecksumRequiredRequest; +import software.amazon.awssdk.services.xml.model.OperationWithChecksumRequiredResponse; +import software.amazon.awssdk.services.xml.model.OperationWithNoneAuthTypeRequest; +import software.amazon.awssdk.services.xml.model.OperationWithNoneAuthTypeResponse; +import software.amazon.awssdk.services.xml.model.OperationWithRequestCompressionRequest; +import software.amazon.awssdk.services.xml.model.OperationWithRequestCompressionResponse; +import software.amazon.awssdk.services.xml.model.PutOperationWithChecksumRequest; +import software.amazon.awssdk.services.xml.model.PutOperationWithChecksumResponse; +import software.amazon.awssdk.services.xml.model.StreamingInputOperationRequest; +import software.amazon.awssdk.services.xml.model.StreamingInputOperationResponse; +import software.amazon.awssdk.services.xml.model.StreamingOutputOperationRequest; +import software.amazon.awssdk.services.xml.model.StreamingOutputOperationResponse; +import software.amazon.awssdk.services.xml.model.XmlException; +import software.amazon.awssdk.services.xml.transform.APostOperationRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.APostOperationWithOutputRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.BearerAuthOperationRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.GetOperationWithChecksumRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.OperationWithChecksumRequiredRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.OperationWithNoneAuthTypeRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.OperationWithRequestCompressionRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.PutOperationWithChecksumRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.StreamingInputOperationRequestMarshaller; +import software.amazon.awssdk.services.xml.transform.StreamingOutputOperationRequestMarshaller; +import software.amazon.awssdk.utils.Logger; + +/** + * Internal implementation of {@link XmlClient}. + * + * @see XmlClient#builder() + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +final class DefaultXmlClient implements XmlClient { + private static final Logger log = Logger.loggerFor(DefaultXmlClient.class); + + private final SyncClientHandler clientHandler; + + private final AwsXmlProtocolFactory protocolFactory; + + private final SdkClientConfiguration clientConfiguration; + + private final XmlServiceClientConfiguration serviceClientConfiguration; + + protected DefaultXmlClient(XmlServiceClientConfiguration serviceClientConfiguration, + SdkClientConfiguration clientConfiguration) { + this.clientHandler = new AwsSyncClientHandler(clientConfiguration); + this.clientConfiguration = clientConfiguration; + this.serviceClientConfiguration = serviceClientConfiguration; + this.protocolFactory = init(); + } + + /** + *

+ * Performs a post operation to the xml service and has no output + *

+ * + * @param aPostOperationRequest + * @return Result of the APostOperation operation returned by the service. + * @throws InvalidInputException + * The request was rejected because an invalid or out-of-range value was supplied for an input parameter. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws XmlException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample XmlClient.APostOperation + * @see AWS + * API Documentation + */ + @Override + public APostOperationResponse aPostOperation(APostOperationRequest aPostOperationRequest) throws InvalidInputException, + AwsServiceException, SdkClientException, XmlException { + + HttpResponseHandler> responseHandler = protocolFactory.createCombinedResponseHandler( + APostOperationResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperation"); + String hostPrefix = "foo-"; + String resolvedHostExpression = "foo-"; + + return clientHandler.execute(new ClientExecutionParams() + .withOperationName("APostOperation").withCombinedResponseHandler(responseHandler) + .withMetricCollector(apiCallMetricCollector).hostPrefixExpression(resolvedHostExpression) + .withRequestConfiguration(clientConfiguration).withInput(aPostOperationRequest) + .withMarshaller(new APostOperationRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + *

+ * Performs a post operation to the xml service and has modelled output + *

+ * + * @param aPostOperationWithOutputRequest + * @return Result of the APostOperationWithOutput operation returned by the service. + * @throws InvalidInputException + * The request was rejected because an invalid or out-of-range value was supplied for an input parameter. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws XmlException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample XmlClient.APostOperationWithOutput + * @see AWS API Documentation + */ + @Override + public APostOperationWithOutputResponse aPostOperationWithOutput( + APostOperationWithOutputRequest aPostOperationWithOutputRequest) throws InvalidInputException, AwsServiceException, + SdkClientException, XmlException { + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(APostOperationWithOutputResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperationWithOutput"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("APostOperationWithOutput").withCombinedResponseHandler(responseHandler) + .withMetricCollector(apiCallMetricCollector).withRequestConfiguration(clientConfiguration) + .withInput(aPostOperationWithOutputRequest) + .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the BearerAuthOperation operation. + * + * @param bearerAuthOperationRequest + * @return Result of the BearerAuthOperation operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws XmlException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample XmlClient.BearerAuthOperation + * @see AWS API Documentation + */ + @Override + public BearerAuthOperationResponse bearerAuthOperation(BearerAuthOperationRequest bearerAuthOperationRequest) + throws AwsServiceException, SdkClientException, XmlException { + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(BearerAuthOperationResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(bearerAuthOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, bearerAuthOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "BearerAuthOperation"); + + return clientHandler.execute(new ClientExecutionParams() + .withOperationName("BearerAuthOperation").withCombinedResponseHandler(responseHandler) + .withMetricCollector(apiCallMetricCollector).credentialType(CredentialType.TOKEN) + .withRequestConfiguration(clientConfiguration).withInput(bearerAuthOperationRequest) + .withMarshaller(new BearerAuthOperationRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the GetOperationWithChecksum operation. + * + * @param getOperationWithChecksumRequest + * @return Result of the GetOperationWithChecksum operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws XmlException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample XmlClient.GetOperationWithChecksum + * @see AWS API Documentation + */ + @Override + public GetOperationWithChecksumResponse getOperationWithChecksum( + GetOperationWithChecksumRequest getOperationWithChecksumRequest) throws AwsServiceException, SdkClientException, + XmlException { + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(GetOperationWithChecksumResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getOperationWithChecksumRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, getOperationWithChecksumRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetOperationWithChecksum"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("GetOperationWithChecksum") + .withCombinedResponseHandler(responseHandler) + .withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration) + .withInput(getOperationWithChecksumRequest) + .putExecutionAttribute( + SdkInternalExecutionAttribute.HTTP_CHECKSUM, + HttpChecksum.builder().requestChecksumRequired(true) + .requestAlgorithm(getOperationWithChecksumRequest.checksumAlgorithmAsString()) + .isRequestStreaming(false).build()) + .withMarshaller(new GetOperationWithChecksumRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the OperationWithChecksumRequired operation. + * + * @param operationWithChecksumRequiredRequest + * @return Result of the OperationWithChecksumRequired operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws XmlException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample XmlClient.OperationWithChecksumRequired + * @see AWS API Documentation + */ + @Override + public OperationWithChecksumRequiredResponse operationWithChecksumRequired( + OperationWithChecksumRequiredRequest operationWithChecksumRequiredRequest) throws AwsServiceException, + SdkClientException, XmlException { + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(OperationWithChecksumRequiredResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithChecksumRequired"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithChecksumRequired") + .withCombinedResponseHandler(responseHandler) + .withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration) + .withInput(operationWithChecksumRequiredRequest) + .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, + HttpChecksumRequired.create()) + .withMarshaller(new OperationWithChecksumRequiredRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the OperationWithNoneAuthType operation. + * + * @param operationWithNoneAuthTypeRequest + * @return Result of the OperationWithNoneAuthType operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws XmlException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample XmlClient.OperationWithNoneAuthType + * @see AWS API Documentation + */ + @Override + public OperationWithNoneAuthTypeResponse operationWithNoneAuthType( + OperationWithNoneAuthTypeRequest operationWithNoneAuthTypeRequest) throws AwsServiceException, SdkClientException, + XmlException { + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(OperationWithNoneAuthTypeResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithNoneAuthTypeRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithNoneAuthTypeRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithNoneAuthType"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithNoneAuthType").withCombinedResponseHandler(responseHandler) + .withMetricCollector(apiCallMetricCollector).withRequestConfiguration(clientConfiguration) + .withInput(operationWithNoneAuthTypeRequest) + .withMarshaller(new OperationWithNoneAuthTypeRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the OperationWithRequestCompression operation. + * + * @param operationWithRequestCompressionRequest + * @return Result of the OperationWithRequestCompression operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws XmlException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample XmlClient.OperationWithRequestCompression + * @see AWS API Documentation + */ + @Override + public OperationWithRequestCompressionResponse operationWithRequestCompression( + OperationWithRequestCompressionRequest operationWithRequestCompressionRequest) throws AwsServiceException, + SdkClientException, XmlException { + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(OperationWithRequestCompressionResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, + operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithRequestCompression"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("OperationWithRequestCompression") + .withCombinedResponseHandler(responseHandler) + .withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration) + .withInput(operationWithRequestCompressionRequest) + .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, + RequestCompression.builder().encodings("gzip").isStreaming(false).build()) + .withMarshaller(new OperationWithRequestCompressionRequestMarshaller(protocolFactory))); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Invokes the PutOperationWithChecksum operation. + * + * @param putOperationWithChecksumRequest + * @param requestBody + * The content to send to the service. A {@link RequestBody} can be created using one of several factory + * methods for various sources of data. For example, to create a request body from a file you can do the + * following. + * + *
+     * {@code RequestBody.fromFile(new File("myfile.txt"))}
+     * 
+ * + * See documentation in {@link RequestBody} for additional details and which sources of data are supported. + * The service documentation for the request content is as follows ' + *

+ * Object data. + *

+ * ' + * @param responseTransformer + * Functional interface for processing the streamed response content. The unmarshalled + * PutOperationWithChecksumResponse and an InputStream to the response content are provided as parameters to + * the callback. The callback may return a transformed type which will be the return value of this method. + * See {@link software.amazon.awssdk.core.sync.ResponseTransformer} for details on implementing this + * interface and for links to pre-canned implementations for common scenarios like downloading to a file. The + * service documentation for the response content is as follows ' + *

+ * Object data. + *

+ * '. + * @return The transformed result of the ResponseTransformer. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws XmlException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample XmlClient.PutOperationWithChecksum + * @see AWS API Documentation + */ + @Override + public ReturnT putOperationWithChecksum(PutOperationWithChecksumRequest putOperationWithChecksumRequest, + RequestBody requestBody, ResponseTransformer responseTransformer) + throws AwsServiceException, SdkClientException, XmlException { + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + PutOperationWithChecksumResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(true)); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(putOperationWithChecksumRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, putOperationWithChecksumRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PutOperationWithChecksum"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("PutOperationWithChecksum") + .withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) + .withInput(putOperationWithChecksumRequest) + .withMetricCollector(apiCallMetricCollector) + .putExecutionAttribute( + SdkInternalExecutionAttribute.HTTP_CHECKSUM, + HttpChecksum.builder().requestChecksumRequired(false) + .requestValidationMode(putOperationWithChecksumRequest.checksumModeAsString()) + .responseAlgorithms("CRC32C", "CRC32", "SHA1", "SHA256").isRequestStreaming(true) + .build()) + .withRequestBody(requestBody) + .withMarshaller( + StreamingRequestMarshaller.builder() + .delegateMarshaller(new PutOperationWithChecksumRequestMarshaller(protocolFactory)) + .requestBody(requestBody).build())); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Some operation with a streaming input + * + * @param streamingInputOperationRequest + * @param requestBody + * The content to send to the service. A {@link RequestBody} can be created using one of several factory + * methods for various sources of data. For example, to create a request body from a file you can do the + * following. + * + *
+     * {@code RequestBody.fromFile(new File("myfile.txt"))}
+     * 
+ * + * See documentation in {@link RequestBody} for additional details and which sources of data are supported. + * The service documentation for the request content is as follows 'This be a stream' + * @return Result of the StreamingInputOperation operation returned by the service. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws XmlException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample XmlClient.StreamingInputOperation + * @see AWS API Documentation + */ + @Override + public StreamingInputOperationResponse streamingInputOperation(StreamingInputOperationRequest streamingInputOperationRequest, + RequestBody requestBody) throws AwsServiceException, SdkClientException, XmlException { + + HttpResponseHandler> responseHandler = protocolFactory + .createCombinedResponseHandler(StreamingInputOperationResponse::builder, + new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOperation"); + + return clientHandler + .execute(new ClientExecutionParams() + .withOperationName("StreamingInputOperation") + .withCombinedResponseHandler(responseHandler) + .withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration) + .withInput(streamingInputOperationRequest) + .withRequestBody(requestBody) + .withMarshaller( + StreamingRequestMarshaller.builder() + .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) + .requestBody(requestBody).build())); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + /** + * Some operation with a streaming output + * + * @param streamingOutputOperationRequest + * @param responseTransformer + * Functional interface for processing the streamed response content. The unmarshalled + * StreamingOutputOperationResponse and an InputStream to the response content are provided as parameters to + * the callback. The callback may return a transformed type which will be the return value of this method. + * See {@link software.amazon.awssdk.core.sync.ResponseTransformer} for details on implementing this + * interface and for links to pre-canned implementations for common scenarios like downloading to a file. The + * service documentation for the response content is as follows 'This be a stream'. + * @return The transformed result of the ResponseTransformer. + * @throws SdkException + * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for + * catch all scenarios. + * @throws SdkClientException + * If any client side error occurs such as an IO related failure, failure to get credentials, etc. + * @throws XmlException + * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. + * @sample XmlClient.StreamingOutputOperation + * @see AWS API Documentation + */ + @Override + public ReturnT streamingOutputOperation(StreamingOutputOperationRequest streamingOutputOperationRequest, + ResponseTransformer responseTransformer) throws AwsServiceException, + SdkClientException, XmlException { + + HttpResponseHandler responseHandler = protocolFactory.createResponseHandler( + StreamingOutputOperationResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(true)); + + HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); + List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest + .overrideConfiguration().orElse(null)); + MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector + .create("ApiCall"); + try { + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Xml Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingOutputOperation"); + + return clientHandler.execute( + new ClientExecutionParams() + .withOperationName("StreamingOutputOperation").withResponseHandler(responseHandler) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(streamingOutputOperationRequest).withMetricCollector(apiCallMetricCollector) + .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)), responseTransformer); + } finally { + metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); + } + } + + @Override + public final String serviceName() { + return SERVICE_NAME; + } + + private static List resolveMetricPublishers(SdkClientConfiguration clientConfiguration, + RequestOverrideConfiguration requestOverrideConfiguration) { + List publishers = null; + if (requestOverrideConfiguration != null) { + publishers = requestOverrideConfiguration.metricPublishers(); + } + if (publishers == null || publishers.isEmpty()) { + publishers = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHERS); + } + if (publishers == null) { + publishers = Collections.emptyList(); + } + return publishers; + } + + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + XmlServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = XmlServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + + private AwsXmlProtocolFactory init() { + return AwsXmlProtocolFactory + .builder() + .registerModeledException( + ExceptionMetadata.builder().errorCode("InvalidInput") + .exceptionBuilderSupplier(InvalidInputException::builder).httpStatusCode(400).build()) + .clientConfiguration(clientConfiguration).defaultServiceExceptionSupplier(XmlException::builder).build(); + } + + @Override + public final XmlServiceClientConfiguration serviceClientConfiguration() { + return this.serviceClientConfiguration; + } + + @Override + public void close() { + clientHandler.close(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-json-async-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-json-async-client-class.java index ae6973fafab0..990c0975df28 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-json-async-client-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-json-async-client-class.java @@ -24,7 +24,9 @@ import software.amazon.awssdk.awscore.eventstream.EventStreamTaggedUnionPojoSupplier; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.SdkPojoBuilder; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SdkResponse; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; @@ -52,6 +54,7 @@ import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.JsonOperationMetadata; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; import software.amazon.awssdk.services.json.model.APostOperationRequest; import software.amazon.awssdk.services.json.model.APostOperationResponse; import software.amazon.awssdk.services.json.model.APostOperationWithOutputRequest; @@ -171,6 +174,7 @@ public JsonUtilities utilities() { */ @Override public CompletableFuture aPostOperation(APostOperationRequest aPostOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -196,8 +200,8 @@ public CompletableFuture aPostOperation(APostOperationRe .withOperationName("APostOperation") .withMarshaller(new APostOperationRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).hostPrefixExpression(resolvedHostExpression) - .withInput(aPostOperationRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .hostPrefixExpression(resolvedHostExpression).withInput(aPostOperationRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -236,6 +240,8 @@ public CompletableFuture aPostOperation(APostOperationRe @Override public CompletableFuture aPostOperationWithOutput( APostOperationWithOutputRequest aPostOperationWithOutputRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -257,7 +263,8 @@ public CompletableFuture aPostOperationWithOut .withOperationName("APostOperationWithOutput") .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(aPostOperationWithOutputRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(aPostOperationWithOutputRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -291,6 +298,8 @@ public CompletableFuture aPostOperationWithOut @Override public CompletableFuture eventStreamOperation(EventStreamOperationRequest eventStreamOperationRequest, Publisher requestStream, EventStreamOperationResponseHandler asyncResponseHandler) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, eventStreamOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -336,8 +345,9 @@ public CompletableFuture eventStreamOperation(EventStreamOperationRequest .withMarshaller(new EventStreamOperationRequestMarshaller(protocolFactory)) .withAsyncRequestBody(AsyncRequestBody.fromPublisher(adapted)).withFullDuplex(true) .withInitialRequestEvent(true).withResponseHandler(voidResponseHandler) - .withErrorResponseHandler(errorResponseHandler).withMetricCollector(apiCallMetricCollector) - .withInput(eventStreamOperationRequest), asyncResponseTransformer); + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector).withInput(eventStreamOperationRequest), + asyncResponseTransformer); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { if (e != null) { try { @@ -382,6 +392,8 @@ public CompletableFuture eventStreamOperation(EventStreamOperationRequest public CompletableFuture eventStreamOperationWithOnlyInput( EventStreamOperationWithOnlyInputRequest eventStreamOperationWithOnlyInputRequest, Publisher requestStream) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationWithOnlyInputRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, eventStreamOperationWithOnlyInputRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -412,7 +424,8 @@ public CompletableFuture eventStreamO .withMarshaller(new EventStreamOperationWithOnlyInputRequestMarshaller(protocolFactory)) .withAsyncRequestBody(AsyncRequestBody.fromPublisher(adapted)).withInitialRequestEvent(true) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(eventStreamOperationWithOnlyInputRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(eventStreamOperationWithOnlyInputRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -448,6 +461,8 @@ public CompletableFuture eventStreamO public CompletableFuture eventStreamOperationWithOnlyOutput( EventStreamOperationWithOnlyOutputRequest eventStreamOperationWithOnlyOutputRequest, EventStreamOperationWithOnlyOutputResponseHandler asyncResponseHandler) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationWithOnlyOutputRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, eventStreamOperationWithOnlyOutputRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -486,8 +501,8 @@ public CompletableFuture eventStreamOperationWithOnlyOutput( .withOperationName("EventStreamOperationWithOnlyOutput") .withMarshaller(new EventStreamOperationWithOnlyOutputRequestMarshaller(protocolFactory)) .withResponseHandler(voidResponseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(eventStreamOperationWithOnlyOutputRequest), - asyncResponseTransformer); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(eventStreamOperationWithOnlyOutputRequest), asyncResponseTransformer); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { if (e != null) { try { @@ -534,6 +549,8 @@ public CompletableFuture eventStreamOperationWithOnlyOutput( @Override public CompletableFuture getWithoutRequiredMembers( GetWithoutRequiredMembersRequest getWithoutRequiredMembersRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getWithoutRequiredMembersRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, getWithoutRequiredMembersRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -555,7 +572,8 @@ public CompletableFuture getWithoutRequiredMe .withOperationName("GetWithoutRequiredMembers") .withMarshaller(new GetWithoutRequiredMembersRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(getWithoutRequiredMembersRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(getWithoutRequiredMembersRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -590,6 +608,8 @@ public CompletableFuture getWithoutRequiredMe @Override public CompletableFuture operationWithChecksumRequired( OperationWithChecksumRequiredRequest operationWithChecksumRequiredRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -612,6 +632,7 @@ public CompletableFuture operationWithChe .withMarshaller(new OperationWithChecksumRequiredRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, HttpChecksumRequired.create()).withInput(operationWithChecksumRequiredRequest)); @@ -648,6 +669,8 @@ public CompletableFuture operationWithChe @Override public CompletableFuture operationWithNoneAuthType( OperationWithNoneAuthTypeRequest operationWithNoneAuthTypeRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithNoneAuthTypeRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithNoneAuthTypeRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -669,7 +692,7 @@ public CompletableFuture operationWithNoneAut .withOperationName("OperationWithNoneAuthType") .withMarshaller(new OperationWithNoneAuthTypeRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.IS_NONE_AUTH_TYPE_REQUEST, false) .withInput(operationWithNoneAuthTypeRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { @@ -706,6 +729,8 @@ public CompletableFuture operationWithNoneAut @Override public CompletableFuture operationWithRequestCompression( OperationWithRequestCompressionRequest operationWithRequestCompressionRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -728,6 +753,7 @@ public CompletableFuture operationWithR .withMarshaller(new OperationWithRequestCompressionRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, RequestCompression.builder().encodings("gzip").isStreaming(false).build()) @@ -766,6 +792,8 @@ public CompletableFuture operationWithR @Override public CompletableFuture paginatedOperationWithResultKey( PaginatedOperationWithResultKeyRequest paginatedOperationWithResultKeyRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(paginatedOperationWithResultKeyRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, paginatedOperationWithResultKeyRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -787,7 +815,8 @@ public CompletableFuture paginatedOpera .withOperationName("PaginatedOperationWithResultKey") .withMarshaller(new PaginatedOperationWithResultKeyRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(paginatedOperationWithResultKeyRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(paginatedOperationWithResultKeyRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -822,6 +851,8 @@ public CompletableFuture paginatedOpera @Override public CompletableFuture paginatedOperationWithoutResultKey( PaginatedOperationWithoutResultKeyRequest paginatedOperationWithoutResultKeyRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(paginatedOperationWithoutResultKeyRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, paginatedOperationWithoutResultKeyRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -843,7 +874,8 @@ public CompletableFuture paginatedOp .withOperationName("PaginatedOperationWithoutResultKey") .withMarshaller(new PaginatedOperationWithoutResultKeyRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(paginatedOperationWithoutResultKeyRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(paginatedOperationWithoutResultKeyRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -882,6 +914,8 @@ public CompletableFuture paginatedOp @Override public CompletableFuture streamingInputOperation( StreamingInputOperationRequest streamingInputOperationRequest, AsyncRequestBody requestBody) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -908,8 +942,9 @@ public CompletableFuture streamingInputOperatio AsyncStreamingRequestMarshaller.builder() .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) .asyncRequestBody(requestBody).build()).withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withMetricCollector(apiCallMetricCollector) - .withAsyncRequestBody(requestBody).withInput(streamingInputOperationRequest)); + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector).withAsyncRequestBody(requestBody) + .withInput(streamingInputOperationRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -954,6 +989,8 @@ public CompletableFuture streamingInputOperatio public CompletableFuture streamingInputOutputOperation( StreamingInputOutputOperationRequest streamingInputOutputOperationRequest, AsyncRequestBody requestBody, AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOutputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOutputOperationRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -986,8 +1023,9 @@ public CompletableFuture streamingInputOutputOperation( new StreamingInputOutputOperationRequestMarshaller(protocolFactory)) .asyncRequestBody(requestBody).transferEncoding(true).build()) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withAsyncRequestBody(requestBody) - .withInput(streamingInputOutputOperationRequest), asyncResponseTransformer); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withAsyncRequestBody(requestBody).withInput(streamingInputOutputOperationRequest), + asyncResponseTransformer); AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { if (e != null) { @@ -1037,6 +1075,8 @@ public CompletableFuture streamingInputOutputOperation( public CompletableFuture streamingOutputOperation( StreamingOutputOperationRequest streamingOutputOperationRequest, AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -1062,8 +1102,8 @@ public CompletableFuture streamingOutputOperation( .withOperationName("StreamingOutputOperation") .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(streamingOutputOperationRequest), - asyncResponseTransformer); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(streamingOutputOperationRequest), asyncResponseTransformer); AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { if (e != null) { @@ -1139,6 +1179,20 @@ private static boolean isSignerOverridden(SdkClientConfiguration clientConfigura return Boolean.TRUE.equals(clientConfiguration.option(SdkClientOption.SIGNER_OVERRIDDEN)); } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + private HttpResponseHandler createErrorResponseHandler(BaseAwsJsonProtocolFactory protocolFactory, JsonOperationMetadata operationMetadata) { return protocolFactory.createErrorResponseHandler(operationMetadata); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-query-compatible-json-async-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-query-compatible-json-async-client-class.java index 38fab697c390..014cfd0deab6 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-query-compatible-json-async-client-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-query-compatible-json-async-client-class.java @@ -12,6 +12,8 @@ import software.amazon.awssdk.awscore.client.handler.AwsAsyncClientHandler; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.AsyncClientHandler; @@ -26,6 +28,7 @@ import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.JsonOperationMetadata; +import software.amazon.awssdk.services.querytojsoncompatible.internal.QueryToJsonCompatibleServiceClientConfigurationBuilder; import software.amazon.awssdk.services.querytojsoncompatible.model.APostOperationRequest; import software.amazon.awssdk.services.querytojsoncompatible.model.APostOperationResponse; import software.amazon.awssdk.services.querytojsoncompatible.model.InvalidInputException; @@ -85,6 +88,7 @@ protected DefaultQueryToJsonCompatibleAsyncClient(QueryToJsonCompatibleServiceCl */ @Override public CompletableFuture aPostOperation(APostOperationRequest aPostOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -110,8 +114,8 @@ public CompletableFuture aPostOperation(APostOperationRe .withOperationName("APostOperation") .withMarshaller(new APostOperationRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).hostPrefixExpression(resolvedHostExpression) - .withInput(aPostOperationRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .hostPrefixExpression(resolvedHostExpression).withInput(aPostOperationRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -160,6 +164,20 @@ private static List resolveMetricPublishers(SdkClientConfigurat return publishers; } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + QueryToJsonCompatibleServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = QueryToJsonCompatibleServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + private HttpResponseHandler createErrorResponseHandler(BaseAwsJsonProtocolFactory protocolFactory, JsonOperationMetadata operationMetadata) { return protocolFactory.createErrorResponseHandler(operationMetadata); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-query-compatible-json-sync-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-query-compatible-json-sync-client-class.java index 42c98a423c77..ab894b63f6b0 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-query-compatible-json-sync-client-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-query-compatible-json-sync-client-class.java @@ -7,6 +7,8 @@ import software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.ClientExecutionParams; @@ -22,6 +24,7 @@ import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.JsonOperationMetadata; +import software.amazon.awssdk.services.querytojsoncompatible.internal.QueryToJsonCompatibleServiceClientConfigurationBuilder; import software.amazon.awssdk.services.querytojsoncompatible.model.APostOperationRequest; import software.amazon.awssdk.services.querytojsoncompatible.model.APostOperationResponse; import software.amazon.awssdk.services.querytojsoncompatible.model.InvalidInputException; @@ -87,6 +90,7 @@ public APostOperationResponse aPostOperation(APostOperationRequest aPostOperatio HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -102,7 +106,8 @@ public APostOperationResponse aPostOperation(APostOperationRequest aPostOperatio return clientHandler.execute(new ClientExecutionParams() .withOperationName("APostOperation").withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler).hostPrefixExpression(resolvedHostExpression) - .withInput(aPostOperationRequest).withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration).withInput(aPostOperationRequest) + .withMetricCollector(apiCallMetricCollector) .withMarshaller(new APostOperationRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -134,6 +139,20 @@ private HttpResponseHandler createErrorResponseHandler(Base return protocolFactory.createErrorResponseHandler(operationMetadata); } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + QueryToJsonCompatibleServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = QueryToJsonCompatibleServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + private > T init(T builder) { return builder .clientConfiguration(clientConfiguration) diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-customservicemetadata-async.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-customservicemetadata-async.java index d3d543b062fd..d1f818467382 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-customservicemetadata-async.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-customservicemetadata-async.java @@ -12,6 +12,8 @@ import software.amazon.awssdk.awscore.client.handler.AwsAsyncClientHandler; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.AsyncClientHandler; @@ -25,6 +27,7 @@ import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.JsonOperationMetadata; +import software.amazon.awssdk.services.protocolrestjsonwithcustomcontenttype.internal.ProtocolRestJsonWithCustomContentTypeServiceClientConfigurationBuilder; import software.amazon.awssdk.services.protocolrestjsonwithcustomcontenttype.model.OneOperationRequest; import software.amazon.awssdk.services.protocolrestjsonwithcustomcontenttype.model.OneOperationResponse; import software.amazon.awssdk.services.protocolrestjsonwithcustomcontenttype.model.ProtocolRestJsonWithCustomContentTypeException; @@ -79,6 +82,7 @@ protected DefaultProtocolRestJsonWithCustomContentTypeAsyncClient( */ @Override public CompletableFuture oneOperation(OneOperationRequest oneOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(oneOperationRequest, this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, oneOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -99,7 +103,8 @@ public CompletableFuture oneOperation(OneOperationRequest .execute(new ClientExecutionParams() .withOperationName("OneOperation").withMarshaller(new OneOperationRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(oneOperationRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(oneOperationRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -142,6 +147,20 @@ private static List resolveMetricPublishers(SdkClientConfigurat return publishers; } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + ProtocolRestJsonWithCustomContentTypeServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = ProtocolRestJsonWithCustomContentTypeServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + private HttpResponseHandler createErrorResponseHandler(BaseAwsJsonProtocolFactory protocolFactory, JsonOperationMetadata operationMetadata) { return protocolFactory.createErrorResponseHandler(operationMetadata); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-customservicemetadata-sync.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-customservicemetadata-sync.java index 7adf676c8d8f..e4ae29440602 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-customservicemetadata-sync.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-customservicemetadata-sync.java @@ -7,6 +7,8 @@ import software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.ClientExecutionParams; @@ -21,6 +23,7 @@ import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.JsonOperationMetadata; +import software.amazon.awssdk.services.protocolrestjsonwithcustomcontenttype.internal.ProtocolRestJsonWithCustomContentTypeServiceClientConfigurationBuilder; import software.amazon.awssdk.services.protocolrestjsonwithcustomcontenttype.model.OneOperationRequest; import software.amazon.awssdk.services.protocolrestjsonwithcustomcontenttype.model.OneOperationResponse; import software.amazon.awssdk.services.protocolrestjsonwithcustomcontenttype.model.ProtocolRestJsonWithCustomContentTypeException; @@ -81,6 +84,7 @@ public OneOperationResponse oneOperation(OneOperationRequest oneOperationRequest HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(oneOperationRequest, this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, oneOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -91,8 +95,8 @@ public OneOperationResponse oneOperation(OneOperationRequest oneOperationRequest return clientHandler.execute(new ClientExecutionParams() .withOperationName("OneOperation").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(oneOperationRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(oneOperationRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new OneOperationRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -124,6 +128,20 @@ private HttpResponseHandler createErrorResponseHandler(Base return protocolFactory.createErrorResponseHandler(operationMetadata); } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + ProtocolRestJsonWithCustomContentTypeServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = ProtocolRestJsonWithCustomContentTypeServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + private > T init(T builder) { return builder.clientConfiguration(clientConfiguration) .defaultServiceExceptionSupplier(ProtocolRestJsonWithCustomContentTypeException::builder) diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-endpoint-discovery-async.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-endpoint-discovery-async.java index 358ff77b746f..8aec80ff2293 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-endpoint-discovery-async.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-endpoint-discovery-async.java @@ -15,6 +15,8 @@ import software.amazon.awssdk.awscore.client.handler.AwsAsyncClientHandler; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.AsyncClientHandler; @@ -23,6 +25,7 @@ import software.amazon.awssdk.core.endpointdiscovery.EndpointDiscoveryRequest; import software.amazon.awssdk.core.http.HttpResponseHandler; import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.metrics.NoOpMetricCollector; @@ -30,6 +33,7 @@ import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.JsonOperationMetadata; +import software.amazon.awssdk.services.endpointdiscoverytest.internal.EndpointDiscoveryTestServiceClientConfigurationBuilder; import software.amazon.awssdk.services.endpointdiscoverytest.model.DescribeEndpointsRequest; import software.amazon.awssdk.services.endpointdiscoverytest.model.DescribeEndpointsResponse; import software.amazon.awssdk.services.endpointdiscoverytest.model.EndpointDiscoveryTestException; @@ -96,6 +100,8 @@ protected DefaultEndpointDiscoveryTestAsyncClient(EndpointDiscoveryTestServiceCl */ @Override public CompletableFuture describeEndpoints(DescribeEndpointsRequest describeEndpointsRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(describeEndpointsRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, describeEndpointsRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -117,7 +123,8 @@ public CompletableFuture describeEndpoints(DescribeEn .withOperationName("DescribeEndpoints") .withMarshaller(new DescribeEndpointsRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(describeEndpointsRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(describeEndpointsRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -150,6 +157,8 @@ public CompletableFuture describeEndpoints(DescribeEn @Override public CompletableFuture testDiscoveryIdentifiersRequired( TestDiscoveryIdentifiersRequiredRequest testDiscoveryIdentifiersRequiredRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(testDiscoveryIdentifiersRequiredRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, testDiscoveryIdentifiersRequiredRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -175,26 +184,29 @@ public CompletableFuture testDiscovery throw new IllegalStateException( "This operation requires endpoint discovery, but endpoint discovery was disabled on the client."); } - URI cachedEndpoint = null; + CompletableFuture endpointFuture = CompletableFuture.completedFuture(null); if (endpointDiscoveryEnabled) { - String key = testDiscoveryIdentifiersRequiredRequest.overrideConfiguration() - .flatMap(AwsRequestOverrideConfiguration::credentialsProvider) - .orElseGet(() -> clientConfiguration.option(AwsClientOption.CREDENTIALS_PROVIDER)).resolveCredentials() - .accessKeyId(); - EndpointDiscoveryRequest endpointDiscoveryRequest = EndpointDiscoveryRequest.builder().required(true) - .defaultEndpoint(clientConfiguration.option(SdkClientOption.ENDPOINT)) - .overrideConfiguration(testDiscoveryIdentifiersRequiredRequest.overrideConfiguration().orElse(null)) - .build(); - cachedEndpoint = endpointDiscoveryCache.get(key, endpointDiscoveryRequest); + CompletableFuture identityFuture = testDiscoveryIdentifiersRequiredRequest + .overrideConfiguration().flatMap(AwsRequestOverrideConfiguration::credentialsIdentityProvider) + .orElseGet(() -> clientConfiguration.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER)) + .resolveIdentity(); + endpointFuture = identityFuture.thenApply(credentials -> { + EndpointDiscoveryRequest endpointDiscoveryRequest = EndpointDiscoveryRequest.builder().required(true) + .defaultEndpoint(clientConfiguration.option(SdkClientOption.ENDPOINT)) + .overrideConfiguration(testDiscoveryIdentifiersRequiredRequest.overrideConfiguration().orElse(null)) + .build(); + return endpointDiscoveryCache.get(credentials.accessKeyId(), endpointDiscoveryRequest); + }); } - CompletableFuture executeFuture = clientHandler - .execute(new ClientExecutionParams() - .withOperationName("TestDiscoveryIdentifiersRequired") - .withMarshaller(new TestDiscoveryIdentifiersRequiredRequestMarshaller(protocolFactory)) - .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).discoveredEndpoint(cachedEndpoint) - .withInput(testDiscoveryIdentifiersRequiredRequest)); + CompletableFuture executeFuture = endpointFuture + .thenCompose(cachedEndpoint -> clientHandler + .execute(new ClientExecutionParams() + .withOperationName("TestDiscoveryIdentifiersRequired") + .withMarshaller(new TestDiscoveryIdentifiersRequiredRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .discoveredEndpoint(cachedEndpoint).withInput(testDiscoveryIdentifiersRequiredRequest))); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -226,6 +238,8 @@ public CompletableFuture testDiscovery @Override public CompletableFuture testDiscoveryOptional( TestDiscoveryOptionalRequest testDiscoveryOptionalRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(testDiscoveryOptionalRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, testDiscoveryOptionalRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -243,25 +257,28 @@ public CompletableFuture testDiscoveryOptional( operationMetadata); boolean endpointDiscoveryEnabled = clientConfiguration.option(SdkClientOption.ENDPOINT_DISCOVERY_ENABLED); boolean endpointOverridden = clientConfiguration.option(SdkClientOption.ENDPOINT_OVERRIDDEN) == Boolean.TRUE; - URI cachedEndpoint = null; + CompletableFuture endpointFuture = CompletableFuture.completedFuture(null); if (endpointDiscoveryEnabled) { - String key = testDiscoveryOptionalRequest.overrideConfiguration() - .flatMap(AwsRequestOverrideConfiguration::credentialsProvider) - .orElseGet(() -> clientConfiguration.option(AwsClientOption.CREDENTIALS_PROVIDER)).resolveCredentials() - .accessKeyId(); - EndpointDiscoveryRequest endpointDiscoveryRequest = EndpointDiscoveryRequest.builder().required(false) - .defaultEndpoint(clientConfiguration.option(SdkClientOption.ENDPOINT)) - .overrideConfiguration(testDiscoveryOptionalRequest.overrideConfiguration().orElse(null)).build(); - cachedEndpoint = endpointDiscoveryCache.get(key, endpointDiscoveryRequest); + CompletableFuture identityFuture = testDiscoveryOptionalRequest + .overrideConfiguration().flatMap(AwsRequestOverrideConfiguration::credentialsIdentityProvider) + .orElseGet(() -> clientConfiguration.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER)) + .resolveIdentity(); + endpointFuture = identityFuture.thenApply(credentials -> { + EndpointDiscoveryRequest endpointDiscoveryRequest = EndpointDiscoveryRequest.builder().required(false) + .defaultEndpoint(clientConfiguration.option(SdkClientOption.ENDPOINT)) + .overrideConfiguration(testDiscoveryOptionalRequest.overrideConfiguration().orElse(null)).build(); + return endpointDiscoveryCache.get(credentials.accessKeyId(), endpointDiscoveryRequest); + }); } - CompletableFuture executeFuture = clientHandler - .execute(new ClientExecutionParams() - .withOperationName("TestDiscoveryOptional") - .withMarshaller(new TestDiscoveryOptionalRequestMarshaller(protocolFactory)) - .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).discoveredEndpoint(cachedEndpoint) - .withInput(testDiscoveryOptionalRequest)); + CompletableFuture executeFuture = endpointFuture + .thenCompose(cachedEndpoint -> clientHandler + .execute(new ClientExecutionParams() + .withOperationName("TestDiscoveryOptional") + .withMarshaller(new TestDiscoveryOptionalRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .discoveredEndpoint(cachedEndpoint).withInput(testDiscoveryOptionalRequest))); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -293,6 +310,8 @@ public CompletableFuture testDiscoveryOptional( @Override public CompletableFuture testDiscoveryRequired( TestDiscoveryRequiredRequest testDiscoveryRequiredRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(testDiscoveryRequiredRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, testDiscoveryRequiredRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -318,25 +337,28 @@ public CompletableFuture testDiscoveryRequired( throw new IllegalStateException( "This operation requires endpoint discovery, but endpoint discovery was disabled on the client."); } - URI cachedEndpoint = null; + CompletableFuture endpointFuture = CompletableFuture.completedFuture(null); if (endpointDiscoveryEnabled) { - String key = testDiscoveryRequiredRequest.overrideConfiguration() - .flatMap(AwsRequestOverrideConfiguration::credentialsProvider) - .orElseGet(() -> clientConfiguration.option(AwsClientOption.CREDENTIALS_PROVIDER)).resolveCredentials() - .accessKeyId(); - EndpointDiscoveryRequest endpointDiscoveryRequest = EndpointDiscoveryRequest.builder().required(true) - .defaultEndpoint(clientConfiguration.option(SdkClientOption.ENDPOINT)) - .overrideConfiguration(testDiscoveryRequiredRequest.overrideConfiguration().orElse(null)).build(); - cachedEndpoint = endpointDiscoveryCache.get(key, endpointDiscoveryRequest); + CompletableFuture identityFuture = testDiscoveryRequiredRequest + .overrideConfiguration().flatMap(AwsRequestOverrideConfiguration::credentialsIdentityProvider) + .orElseGet(() -> clientConfiguration.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER)) + .resolveIdentity(); + endpointFuture = identityFuture.thenApply(credentials -> { + EndpointDiscoveryRequest endpointDiscoveryRequest = EndpointDiscoveryRequest.builder().required(true) + .defaultEndpoint(clientConfiguration.option(SdkClientOption.ENDPOINT)) + .overrideConfiguration(testDiscoveryRequiredRequest.overrideConfiguration().orElse(null)).build(); + return endpointDiscoveryCache.get(credentials.accessKeyId(), endpointDiscoveryRequest); + }); } - CompletableFuture executeFuture = clientHandler - .execute(new ClientExecutionParams() - .withOperationName("TestDiscoveryRequired") - .withMarshaller(new TestDiscoveryRequiredRequestMarshaller(protocolFactory)) - .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).discoveredEndpoint(cachedEndpoint) - .withInput(testDiscoveryRequiredRequest)); + CompletableFuture executeFuture = endpointFuture + .thenCompose(cachedEndpoint -> clientHandler + .execute(new ClientExecutionParams() + .withOperationName("TestDiscoveryRequired") + .withMarshaller(new TestDiscoveryRequiredRequestMarshaller(protocolFactory)) + .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .discoveredEndpoint(cachedEndpoint).withInput(testDiscoveryRequiredRequest))); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -379,6 +401,20 @@ private static List resolveMetricPublishers(SdkClientConfigurat return publishers; } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + EndpointDiscoveryTestServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = EndpointDiscoveryTestServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + private HttpResponseHandler createErrorResponseHandler(BaseAwsJsonProtocolFactory protocolFactory, JsonOperationMetadata operationMetadata) { return protocolFactory.createErrorResponseHandler(operationMetadata); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-endpoint-discovery-sync.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-endpoint-discovery-sync.java index 0732225d551b..2b851d9d1514 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-endpoint-discovery-sync.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-endpoint-discovery-sync.java @@ -3,6 +3,7 @@ import java.net.URI; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; @@ -10,6 +11,8 @@ import software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.ClientExecutionParams; @@ -19,6 +22,7 @@ import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.http.HttpResponseHandler; import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.metrics.NoOpMetricCollector; @@ -26,6 +30,7 @@ import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.JsonOperationMetadata; +import software.amazon.awssdk.services.endpointdiscoverytest.internal.EndpointDiscoveryTestServiceClientConfigurationBuilder; import software.amazon.awssdk.services.endpointdiscoverytest.model.DescribeEndpointsRequest; import software.amazon.awssdk.services.endpointdiscoverytest.model.DescribeEndpointsResponse; import software.amazon.awssdk.services.endpointdiscoverytest.model.EndpointDiscoveryTestException; @@ -39,6 +44,7 @@ import software.amazon.awssdk.services.endpointdiscoverytest.transform.TestDiscoveryIdentifiersRequiredRequestMarshaller; import software.amazon.awssdk.services.endpointdiscoverytest.transform.TestDiscoveryOptionalRequestMarshaller; import software.amazon.awssdk.services.endpointdiscoverytest.transform.TestDiscoveryRequiredRequestMarshaller; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Logger; /** @@ -98,6 +104,8 @@ public DescribeEndpointsResponse describeEndpoints(DescribeEndpointsRequest desc HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(describeEndpointsRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, describeEndpointsRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -108,8 +116,8 @@ public DescribeEndpointsResponse describeEndpoints(DescribeEndpointsRequest desc return clientHandler.execute(new ClientExecutionParams() .withOperationName("DescribeEndpoints").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(describeEndpointsRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(describeEndpointsRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new DescribeEndpointsRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -154,15 +162,17 @@ public TestDiscoveryIdentifiersRequiredResponse testDiscoveryIdentifiersRequired } URI cachedEndpoint = null; if (endpointDiscoveryEnabled) { - String key = testDiscoveryIdentifiersRequiredRequest.overrideConfiguration() - .flatMap(AwsRequestOverrideConfiguration::credentialsProvider) - .orElseGet(() -> clientConfiguration.option(AwsClientOption.CREDENTIALS_PROVIDER)).resolveCredentials() - .accessKeyId(); + CompletableFuture identityFuture = testDiscoveryIdentifiersRequiredRequest + .overrideConfiguration().flatMap(AwsRequestOverrideConfiguration::credentialsIdentityProvider) + .orElseGet(() -> clientConfiguration.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER)).resolveIdentity(); + String key = CompletableFutureUtils.joinLikeSync(identityFuture).accessKeyId(); EndpointDiscoveryRequest endpointDiscoveryRequest = EndpointDiscoveryRequest.builder().required(true) .defaultEndpoint(clientConfiguration.option(SdkClientOption.ENDPOINT)) .overrideConfiguration(testDiscoveryIdentifiersRequiredRequest.overrideConfiguration().orElse(null)).build(); cachedEndpoint = endpointDiscoveryCache.get(key, endpointDiscoveryRequest); } + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(testDiscoveryIdentifiersRequiredRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, testDiscoveryIdentifiersRequiredRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -175,7 +185,8 @@ public TestDiscoveryIdentifiersRequiredResponse testDiscoveryIdentifiersRequired .execute(new ClientExecutionParams() .withOperationName("TestDiscoveryIdentifiersRequired").withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler).discoveredEndpoint(cachedEndpoint) - .withInput(testDiscoveryIdentifiersRequiredRequest).withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration).withInput(testDiscoveryIdentifiersRequiredRequest) + .withMetricCollector(apiCallMetricCollector) .withMarshaller(new TestDiscoveryIdentifiersRequiredRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -211,15 +222,17 @@ public TestDiscoveryOptionalResponse testDiscoveryOptional(TestDiscoveryOptional boolean endpointOverridden = clientConfiguration.option(SdkClientOption.ENDPOINT_OVERRIDDEN) == Boolean.TRUE; URI cachedEndpoint = null; if (endpointDiscoveryEnabled) { - String key = testDiscoveryOptionalRequest.overrideConfiguration() - .flatMap(AwsRequestOverrideConfiguration::credentialsProvider) - .orElseGet(() -> clientConfiguration.option(AwsClientOption.CREDENTIALS_PROVIDER)).resolveCredentials() - .accessKeyId(); + CompletableFuture identityFuture = testDiscoveryOptionalRequest + .overrideConfiguration().flatMap(AwsRequestOverrideConfiguration::credentialsIdentityProvider) + .orElseGet(() -> clientConfiguration.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER)).resolveIdentity(); + String key = CompletableFutureUtils.joinLikeSync(identityFuture).accessKeyId(); EndpointDiscoveryRequest endpointDiscoveryRequest = EndpointDiscoveryRequest.builder().required(false) .defaultEndpoint(clientConfiguration.option(SdkClientOption.ENDPOINT)) .overrideConfiguration(testDiscoveryOptionalRequest.overrideConfiguration().orElse(null)).build(); cachedEndpoint = endpointDiscoveryCache.get(key, endpointDiscoveryRequest); } + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(testDiscoveryOptionalRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, testDiscoveryOptionalRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -231,7 +244,8 @@ public TestDiscoveryOptionalResponse testDiscoveryOptional(TestDiscoveryOptional return clientHandler.execute(new ClientExecutionParams() .withOperationName("TestDiscoveryOptional").withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler).discoveredEndpoint(cachedEndpoint) - .withInput(testDiscoveryOptionalRequest).withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration).withInput(testDiscoveryOptionalRequest) + .withMetricCollector(apiCallMetricCollector) .withMarshaller(new TestDiscoveryOptionalRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -275,15 +289,17 @@ public TestDiscoveryRequiredResponse testDiscoveryRequired(TestDiscoveryRequired } URI cachedEndpoint = null; if (endpointDiscoveryEnabled) { - String key = testDiscoveryRequiredRequest.overrideConfiguration() - .flatMap(AwsRequestOverrideConfiguration::credentialsProvider) - .orElseGet(() -> clientConfiguration.option(AwsClientOption.CREDENTIALS_PROVIDER)).resolveCredentials() - .accessKeyId(); + CompletableFuture identityFuture = testDiscoveryRequiredRequest + .overrideConfiguration().flatMap(AwsRequestOverrideConfiguration::credentialsIdentityProvider) + .orElseGet(() -> clientConfiguration.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER)).resolveIdentity(); + String key = CompletableFutureUtils.joinLikeSync(identityFuture).accessKeyId(); EndpointDiscoveryRequest endpointDiscoveryRequest = EndpointDiscoveryRequest.builder().required(true) .defaultEndpoint(clientConfiguration.option(SdkClientOption.ENDPOINT)) .overrideConfiguration(testDiscoveryRequiredRequest.overrideConfiguration().orElse(null)).build(); cachedEndpoint = endpointDiscoveryCache.get(key, endpointDiscoveryRequest); } + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(testDiscoveryRequiredRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, testDiscoveryRequiredRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -295,7 +311,8 @@ public TestDiscoveryRequiredResponse testDiscoveryRequired(TestDiscoveryRequired return clientHandler.execute(new ClientExecutionParams() .withOperationName("TestDiscoveryRequired").withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler).discoveredEndpoint(cachedEndpoint) - .withInput(testDiscoveryRequiredRequest).withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration).withInput(testDiscoveryRequiredRequest) + .withMetricCollector(apiCallMetricCollector) .withMarshaller(new TestDiscoveryRequiredRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -327,6 +344,20 @@ private HttpResponseHandler createErrorResponseHandler(Base return protocolFactory.createErrorResponseHandler(operationMetadata); } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + EndpointDiscoveryTestServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = EndpointDiscoveryTestServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + private > T init(T builder) { return builder.clientConfiguration(clientConfiguration) .defaultServiceExceptionSupplier(EndpointDiscoveryTestException::builder).protocol(AwsJsonProtocol.AWS_JSON) diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-class.java index f3c88ecbb220..032233125073 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-class.java @@ -27,7 +27,9 @@ import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.CredentialType; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.SdkPojoBuilder; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SdkResponse; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; @@ -56,6 +58,7 @@ import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.JsonOperationMetadata; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; import software.amazon.awssdk.services.json.model.APostOperationRequest; import software.amazon.awssdk.services.json.model.APostOperationResponse; import software.amazon.awssdk.services.json.model.APostOperationWithOutputRequest; @@ -180,6 +183,7 @@ public JsonUtilities utilities() { */ @Override public CompletableFuture aPostOperation(APostOperationRequest aPostOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -205,8 +209,8 @@ public CompletableFuture aPostOperation(APostOperationRe .withOperationName("APostOperation") .withMarshaller(new APostOperationRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).hostPrefixExpression(resolvedHostExpression) - .withInput(aPostOperationRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .hostPrefixExpression(resolvedHostExpression).withInput(aPostOperationRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -244,6 +248,8 @@ public CompletableFuture aPostOperation(APostOperationRe @Override public CompletableFuture aPostOperationWithOutput( APostOperationWithOutputRequest aPostOperationWithOutputRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -265,7 +271,8 @@ public CompletableFuture aPostOperationWithOut .withOperationName("APostOperationWithOutput") .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(aPostOperationWithOutputRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(aPostOperationWithOutputRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -299,6 +306,8 @@ public CompletableFuture aPostOperationWithOut @Override public CompletableFuture bearerAuthOperation( BearerAuthOperationRequest bearerAuthOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(bearerAuthOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, bearerAuthOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -321,8 +330,8 @@ public CompletableFuture bearerAuthOperation( .withOperationName("BearerAuthOperation") .withMarshaller(new BearerAuthOperationRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).credentialType(CredentialType.TOKEN) - .withInput(bearerAuthOperationRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .credentialType(CredentialType.TOKEN).withInput(bearerAuthOperationRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -356,6 +365,8 @@ public CompletableFuture bearerAuthOperation( @Override public CompletableFuture eventStreamOperation(EventStreamOperationRequest eventStreamOperationRequest, Publisher requestStream, EventStreamOperationResponseHandler asyncResponseHandler) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, eventStreamOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -405,8 +416,8 @@ public CompletableFuture eventStreamOperation(EventStreamOperationRequest .withMarshaller(new EventStreamOperationRequestMarshaller(protocolFactory)) .withAsyncRequestBody(AsyncRequestBody.fromPublisher(adapted)).withFullDuplex(true) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(eventStreamOperationRequest), - restAsyncResponseTransformer); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(eventStreamOperationRequest), restAsyncResponseTransformer); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { if (e != null) { try { @@ -451,6 +462,8 @@ public CompletableFuture eventStreamOperation(EventStreamOperationRequest public CompletableFuture eventStreamOperationWithOnlyInput( EventStreamOperationWithOnlyInputRequest eventStreamOperationWithOnlyInputRequest, Publisher requestStream) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationWithOnlyInputRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, eventStreamOperationWithOnlyInputRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -480,8 +493,8 @@ public CompletableFuture eventStreamO .withOperationName("EventStreamOperationWithOnlyInput") .withMarshaller(new EventStreamOperationWithOnlyInputRequestMarshaller(protocolFactory)) .withAsyncRequestBody(AsyncRequestBody.fromPublisher(adapted)).withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withMetricCollector(apiCallMetricCollector) - .withInput(eventStreamOperationWithOnlyInputRequest)); + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector).withInput(eventStreamOperationWithOnlyInputRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -517,6 +530,8 @@ public CompletableFuture eventStreamO public CompletableFuture eventStreamOperationWithOnlyOutput( EventStreamOperationWithOnlyOutputRequest eventStreamOperationWithOnlyOutputRequest, EventStreamOperationWithOnlyOutputResponseHandler asyncResponseHandler) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationWithOnlyOutputRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, eventStreamOperationWithOnlyOutputRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -560,7 +575,7 @@ public CompletableFuture eventStreamOperationWithOnlyOutput( .withOperationName("EventStreamOperationWithOnlyOutput") .withMarshaller(new EventStreamOperationWithOnlyOutputRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) .withInput(eventStreamOperationWithOnlyOutputRequest), restAsyncResponseTransformer); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { if (e != null) { @@ -604,6 +619,8 @@ public CompletableFuture eventStreamOperationWithOnlyOutput( @Override public CompletableFuture getOperationWithChecksum( GetOperationWithChecksumRequest getOperationWithChecksumRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getOperationWithChecksumRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, getOperationWithChecksumRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -626,6 +643,7 @@ public CompletableFuture getOperationWithCheck .withMarshaller(new GetOperationWithChecksumRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute( SdkInternalExecutionAttribute.HTTP_CHECKSUM, @@ -669,6 +687,8 @@ public CompletableFuture getOperationWithCheck @Override public CompletableFuture getWithoutRequiredMembers( GetWithoutRequiredMembersRequest getWithoutRequiredMembersRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getWithoutRequiredMembersRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, getWithoutRequiredMembersRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -690,7 +710,8 @@ public CompletableFuture getWithoutRequiredMe .withOperationName("GetWithoutRequiredMembers") .withMarshaller(new GetWithoutRequiredMembersRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(getWithoutRequiredMembersRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(getWithoutRequiredMembersRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -725,6 +746,8 @@ public CompletableFuture getWithoutRequiredMe @Override public CompletableFuture operationWithChecksumRequired( OperationWithChecksumRequiredRequest operationWithChecksumRequiredRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -747,6 +770,7 @@ public CompletableFuture operationWithChe .withMarshaller(new OperationWithChecksumRequiredRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, HttpChecksumRequired.create()).withInput(operationWithChecksumRequiredRequest)); @@ -784,6 +808,8 @@ public CompletableFuture operationWithChe @Override public CompletableFuture operationWithRequestCompression( OperationWithRequestCompressionRequest operationWithRequestCompressionRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -806,6 +832,7 @@ public CompletableFuture operationWithR .withMarshaller(new OperationWithRequestCompressionRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, RequestCompression.builder().encodings("gzip").isStreaming(false).build()) @@ -844,6 +871,8 @@ public CompletableFuture operationWithR @Override public CompletableFuture paginatedOperationWithResultKey( PaginatedOperationWithResultKeyRequest paginatedOperationWithResultKeyRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(paginatedOperationWithResultKeyRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, paginatedOperationWithResultKeyRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -865,7 +894,8 @@ public CompletableFuture paginatedOpera .withOperationName("PaginatedOperationWithResultKey") .withMarshaller(new PaginatedOperationWithResultKeyRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(paginatedOperationWithResultKeyRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(paginatedOperationWithResultKeyRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -900,6 +930,8 @@ public CompletableFuture paginatedOpera @Override public CompletableFuture paginatedOperationWithoutResultKey( PaginatedOperationWithoutResultKeyRequest paginatedOperationWithoutResultKeyRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(paginatedOperationWithoutResultKeyRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, paginatedOperationWithoutResultKeyRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -921,7 +953,8 @@ public CompletableFuture paginatedOp .withOperationName("PaginatedOperationWithoutResultKey") .withMarshaller(new PaginatedOperationWithoutResultKeyRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(paginatedOperationWithoutResultKeyRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(paginatedOperationWithoutResultKeyRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -974,6 +1007,8 @@ public CompletableFuture paginatedOp public CompletableFuture putOperationWithChecksum( PutOperationWithChecksumRequest putOperationWithChecksumRequest, AsyncRequestBody requestBody, AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(putOperationWithChecksumRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, putOperationWithChecksumRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -1006,6 +1041,7 @@ public CompletableFuture putOperationWithChecksum( .asyncRequestBody(requestBody).build()) .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withMetricCollector(apiCallMetricCollector) .withAsyncRequestBody(requestBody) .putExecutionAttribute( @@ -1062,6 +1098,8 @@ public CompletableFuture putOperationWithChecksum( @Override public CompletableFuture streamingInputOperation( StreamingInputOperationRequest streamingInputOperationRequest, AsyncRequestBody requestBody) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -1088,8 +1126,9 @@ public CompletableFuture streamingInputOperatio AsyncStreamingRequestMarshaller.builder() .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) .asyncRequestBody(requestBody).build()).withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withMetricCollector(apiCallMetricCollector) - .withAsyncRequestBody(requestBody).withInput(streamingInputOperationRequest)); + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector).withAsyncRequestBody(requestBody) + .withInput(streamingInputOperationRequest)); CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); }); @@ -1134,6 +1173,8 @@ public CompletableFuture streamingInputOperatio public CompletableFuture streamingInputOutputOperation( StreamingInputOutputOperationRequest streamingInputOutputOperationRequest, AsyncRequestBody requestBody, AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOutputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOutputOperationRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -1166,8 +1207,9 @@ public CompletableFuture streamingInputOutputOperation( new StreamingInputOutputOperationRequestMarshaller(protocolFactory)) .asyncRequestBody(requestBody).transferEncoding(true).build()) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withAsyncRequestBody(requestBody) - .withInput(streamingInputOutputOperationRequest), asyncResponseTransformer); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withAsyncRequestBody(requestBody).withInput(streamingInputOutputOperationRequest), + asyncResponseTransformer); AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { if (e != null) { @@ -1217,6 +1259,8 @@ public CompletableFuture streamingInputOutputOperation( public CompletableFuture streamingOutputOperation( StreamingOutputOperationRequest streamingOutputOperationRequest, AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -1242,8 +1286,8 @@ public CompletableFuture streamingOutputOperation( .withOperationName("StreamingOutputOperation") .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(streamingOutputOperationRequest), - asyncResponseTransformer); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(streamingOutputOperationRequest), asyncResponseTransformer); AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> { if (e != null) { @@ -1316,6 +1360,20 @@ private static boolean isSignerOverridden(SdkClientConfiguration clientConfigura return Boolean.TRUE.equals(clientConfiguration.option(SdkClientOption.SIGNER_OVERRIDDEN)); } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + private HttpResponseHandler createErrorResponseHandler(BaseAwsJsonProtocolFactory protocolFactory, JsonOperationMetadata operationMetadata) { return protocolFactory.createErrorResponseHandler(operationMetadata); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-client-class.java index 06f7808b8d9d..d2e3ac3a652c 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-client-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-client-class.java @@ -12,6 +12,8 @@ import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.CredentialType; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.ClientExecutionParams; @@ -35,6 +37,7 @@ import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory; import software.amazon.awssdk.protocols.json.JsonOperationMetadata; +import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; import software.amazon.awssdk.services.json.model.APostOperationRequest; import software.amazon.awssdk.services.json.model.APostOperationResponse; import software.amazon.awssdk.services.json.model.APostOperationWithOutputRequest; @@ -137,6 +140,7 @@ public APostOperationResponse aPostOperation(APostOperationRequest aPostOperatio HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -152,7 +156,8 @@ public APostOperationResponse aPostOperation(APostOperationRequest aPostOperatio return clientHandler.execute(new ClientExecutionParams() .withOperationName("APostOperation").withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler).hostPrefixExpression(resolvedHostExpression) - .withInput(aPostOperationRequest).withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration).withInput(aPostOperationRequest) + .withMetricCollector(apiCallMetricCollector) .withMarshaller(new APostOperationRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -191,6 +196,8 @@ public APostOperationWithOutputResponse aPostOperationWithOutput( HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -202,8 +209,8 @@ public APostOperationWithOutputResponse aPostOperationWithOutput( return clientHandler .execute(new ClientExecutionParams() .withOperationName("APostOperationWithOutput").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(aPostOperationWithOutputRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(aPostOperationWithOutputRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -238,6 +245,8 @@ public BearerAuthOperationResponse bearerAuthOperation(BearerAuthOperationReques HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(bearerAuthOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, bearerAuthOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -249,7 +258,8 @@ public BearerAuthOperationResponse bearerAuthOperation(BearerAuthOperationReques return clientHandler.execute(new ClientExecutionParams() .withOperationName("BearerAuthOperation").withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler).credentialType(CredentialType.TOKEN) - .withInput(bearerAuthOperationRequest).withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration).withInput(bearerAuthOperationRequest) + .withMetricCollector(apiCallMetricCollector) .withMarshaller(new BearerAuthOperationRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -284,6 +294,8 @@ public GetOperationWithChecksumResponse getOperationWithChecksum( HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getOperationWithChecksumRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, getOperationWithChecksumRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -297,6 +309,7 @@ public GetOperationWithChecksumResponse getOperationWithChecksum( .withOperationName("GetOperationWithChecksum") .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withInput(getOperationWithChecksumRequest) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute( @@ -342,6 +355,8 @@ public GetWithoutRequiredMembersResponse getWithoutRequiredMembers( HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getWithoutRequiredMembersRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, getWithoutRequiredMembersRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -353,8 +368,8 @@ public GetWithoutRequiredMembersResponse getWithoutRequiredMembers( return clientHandler .execute(new ClientExecutionParams() .withOperationName("GetWithoutRequiredMembers").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(getWithoutRequiredMembersRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(getWithoutRequiredMembersRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new GetWithoutRequiredMembersRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -389,6 +404,8 @@ public OperationWithChecksumRequiredResponse operationWithChecksumRequired( HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -402,6 +419,7 @@ public OperationWithChecksumRequiredResponse operationWithChecksumRequired( .withOperationName("OperationWithChecksumRequired") .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withInput(operationWithChecksumRequiredRequest) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, @@ -440,6 +458,8 @@ public OperationWithRequestCompressionResponse operationWithRequestCompression( HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -453,6 +473,7 @@ public OperationWithRequestCompressionResponse operationWithRequestCompression( .withOperationName("OperationWithRequestCompression") .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withInput(operationWithRequestCompressionRequest) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, @@ -491,6 +512,8 @@ public PaginatedOperationWithResultKeyResponse paginatedOperationWithResultKey( HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(paginatedOperationWithResultKeyRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, paginatedOperationWithResultKeyRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -502,8 +525,8 @@ public PaginatedOperationWithResultKeyResponse paginatedOperationWithResultKey( return clientHandler .execute(new ClientExecutionParams() .withOperationName("PaginatedOperationWithResultKey").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(paginatedOperationWithResultKeyRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(paginatedOperationWithResultKeyRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new PaginatedOperationWithResultKeyRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -538,6 +561,8 @@ public PaginatedOperationWithoutResultKeyResponse paginatedOperationWithoutResul HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(paginatedOperationWithoutResultKeyRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, paginatedOperationWithoutResultKeyRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -549,8 +574,8 @@ public PaginatedOperationWithoutResultKeyResponse paginatedOperationWithoutResul return clientHandler .execute(new ClientExecutionParams() .withOperationName("PaginatedOperationWithoutResultKey").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(paginatedOperationWithoutResultKeyRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(paginatedOperationWithoutResultKeyRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new PaginatedOperationWithoutResultKeyRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -611,6 +636,8 @@ public ReturnT putOperationWithChecksum(PutOperationWithChecksumReques HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(putOperationWithChecksumRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, putOperationWithChecksumRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -624,6 +651,7 @@ public ReturnT putOperationWithChecksum(PutOperationWithChecksumReques .withOperationName("PutOperationWithChecksum") .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withInput(putOperationWithChecksumRequest) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute( @@ -680,6 +708,8 @@ public StreamingInputOperationResponse streamingInputOperation(StreamingInputOpe HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -693,6 +723,7 @@ public StreamingInputOperationResponse streamingInputOperation(StreamingInputOpe .withOperationName("StreamingInputOperation") .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withInput(streamingInputOperationRequest) .withMetricCollector(apiCallMetricCollector) .withRequestBody(requestBody) @@ -754,6 +785,8 @@ public ReturnT streamingInputOutputOperation( HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOutputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOutputOperationRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -767,6 +800,7 @@ public ReturnT streamingInputOutputOperation( .withOperationName("StreamingInputOutputOperation") .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withInput(streamingInputOutputOperationRequest) .withMetricCollector(apiCallMetricCollector) .withRequestBody(requestBody) @@ -816,6 +850,8 @@ public ReturnT streamingOutputOperation(StreamingOutputOperationReques HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -827,8 +863,8 @@ public ReturnT streamingOutputOperation(StreamingOutputOperationReques return clientHandler.execute( new ClientExecutionParams() .withOperationName("StreamingOutputOperation").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(streamingOutputOperationRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(streamingOutputOperationRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)), responseTransformer); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -879,6 +915,20 @@ private HttpResponseHandler createErrorResponseHandler(Base return protocolFactory.createErrorResponseHandler(operationMetadata); } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + JsonServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = JsonServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + private > T init(T builder) { return builder .clientConfiguration(clientConfiguration) diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-async-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-async-client-class.java index b0ca9683c0c5..7d9e3b881cca 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-async-client-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-async-client-class.java @@ -18,6 +18,8 @@ import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.CredentialType; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.core.async.AsyncResponseTransformerUtils; @@ -38,6 +40,7 @@ import software.amazon.awssdk.metrics.NoOpMetricCollector; import software.amazon.awssdk.protocols.core.ExceptionMetadata; import software.amazon.awssdk.protocols.query.AwsQueryProtocolFactory; +import software.amazon.awssdk.services.query.internal.QueryServiceClientConfigurationBuilder; import software.amazon.awssdk.services.query.model.APostOperationRequest; import software.amazon.awssdk.services.query.model.APostOperationResponse; import software.amazon.awssdk.services.query.model.APostOperationWithOutputRequest; @@ -135,6 +138,7 @@ protected DefaultQueryAsyncClient(QueryServiceClientConfiguration serviceClientC */ @Override public CompletableFuture aPostOperation(APostOperationRequest aPostOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -155,8 +159,8 @@ public CompletableFuture aPostOperation(APostOperationRe .withOperationName("APostOperation") .withMarshaller(new APostOperationRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).hostPrefixExpression(resolvedHostExpression) - .withInput(aPostOperationRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .hostPrefixExpression(resolvedHostExpression).withInput(aPostOperationRequest)); CompletableFuture whenCompleteFuture = null; whenCompleteFuture = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -194,6 +198,8 @@ public CompletableFuture aPostOperation(APostOperationRe @Override public CompletableFuture aPostOperationWithOutput( APostOperationWithOutputRequest aPostOperationWithOutputRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -212,7 +218,8 @@ public CompletableFuture aPostOperationWithOut .withOperationName("APostOperationWithOutput") .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(aPostOperationWithOutputRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(aPostOperationWithOutputRequest)); CompletableFuture whenCompleteFuture = null; whenCompleteFuture = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -246,6 +253,8 @@ public CompletableFuture aPostOperationWithOut @Override public CompletableFuture bearerAuthOperation( BearerAuthOperationRequest bearerAuthOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(bearerAuthOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, bearerAuthOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -265,8 +274,8 @@ public CompletableFuture bearerAuthOperation( .withOperationName("BearerAuthOperation") .withMarshaller(new BearerAuthOperationRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .credentialType(CredentialType.TOKEN).withMetricCollector(apiCallMetricCollector) - .withInput(bearerAuthOperationRequest)); + .credentialType(CredentialType.TOKEN).withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector).withInput(bearerAuthOperationRequest)); CompletableFuture whenCompleteFuture = null; whenCompleteFuture = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -300,6 +309,8 @@ public CompletableFuture bearerAuthOperation( @Override public CompletableFuture getOperationWithChecksum( GetOperationWithChecksumRequest getOperationWithChecksumRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getOperationWithChecksumRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, getOperationWithChecksumRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -319,6 +330,7 @@ public CompletableFuture getOperationWithCheck .withMarshaller(new GetOperationWithChecksumRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute( SdkInternalExecutionAttribute.HTTP_CHECKSUM, @@ -359,6 +371,8 @@ public CompletableFuture getOperationWithCheck @Override public CompletableFuture operationWithChecksumRequired( OperationWithChecksumRequiredRequest operationWithChecksumRequiredRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -378,6 +392,7 @@ public CompletableFuture operationWithChe .withMarshaller(new OperationWithChecksumRequiredRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, HttpChecksumRequired.create()).withInput(operationWithChecksumRequiredRequest)); @@ -414,6 +429,8 @@ public CompletableFuture operationWithChe @Override public CompletableFuture operationWithContextParam( OperationWithContextParamRequest operationWithContextParamRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithContextParamRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithContextParamRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -432,7 +449,8 @@ public CompletableFuture operationWithContext .withOperationName("OperationWithContextParam") .withMarshaller(new OperationWithContextParamRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(operationWithContextParamRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(operationWithContextParamRequest)); CompletableFuture whenCompleteFuture = null; whenCompleteFuture = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -466,6 +484,8 @@ public CompletableFuture operationWithContext @Override public CompletableFuture operationWithNoneAuthType( OperationWithNoneAuthTypeRequest operationWithNoneAuthTypeRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithNoneAuthTypeRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithNoneAuthTypeRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -484,7 +504,7 @@ public CompletableFuture operationWithNoneAut .withOperationName("OperationWithNoneAuthType") .withMarshaller(new OperationWithNoneAuthTypeRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.IS_NONE_AUTH_TYPE_REQUEST, false) .withInput(operationWithNoneAuthTypeRequest)); CompletableFuture whenCompleteFuture = null; @@ -521,6 +541,8 @@ public CompletableFuture operationWithNoneAut @Override public CompletableFuture operationWithRequestCompression( OperationWithRequestCompressionRequest operationWithRequestCompressionRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -540,6 +562,7 @@ public CompletableFuture operationWithR .withMarshaller(new OperationWithRequestCompressionRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, RequestCompression.builder().encodings("gzip").isStreaming(false).build()) @@ -578,6 +601,8 @@ public CompletableFuture operationWithR @Override public CompletableFuture operationWithStaticContextParams( OperationWithStaticContextParamsRequest operationWithStaticContextParamsRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithStaticContextParamsRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithStaticContextParamsRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -596,7 +621,8 @@ public CompletableFuture operationWith .withOperationName("OperationWithStaticContextParams") .withMarshaller(new OperationWithStaticContextParamsRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(operationWithStaticContextParamsRequest)); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(operationWithStaticContextParamsRequest)); CompletableFuture whenCompleteFuture = null; whenCompleteFuture = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -649,6 +675,8 @@ public CompletableFuture operationWith public CompletableFuture putOperationWithChecksum( PutOperationWithChecksumRequest putOperationWithChecksumRequest, AsyncRequestBody requestBody, AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(putOperationWithChecksumRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, putOperationWithChecksumRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -678,6 +706,7 @@ public CompletableFuture putOperationWithChecksum( .asyncRequestBody(requestBody).build()) .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute( SdkInternalExecutionAttribute.HTTP_CHECKSUM, @@ -734,6 +763,8 @@ public CompletableFuture putOperationWithChecksum( @Override public CompletableFuture streamingInputOperation( StreamingInputOperationRequest streamingInputOperationRequest, AsyncRequestBody requestBody) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -757,8 +788,9 @@ public CompletableFuture streamingInputOperatio AsyncStreamingRequestMarshaller.builder() .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) .asyncRequestBody(requestBody).build()).withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withMetricCollector(apiCallMetricCollector) - .withAsyncRequestBody(requestBody).withInput(streamingInputOperationRequest)); + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withMetricCollector(apiCallMetricCollector).withAsyncRequestBody(requestBody) + .withInput(streamingInputOperationRequest)); CompletableFuture whenCompleteFuture = null; whenCompleteFuture = executeFuture.whenComplete((r, e) -> { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -798,6 +830,8 @@ public CompletableFuture streamingInputOperatio public CompletableFuture streamingOutputOperation( StreamingOutputOperationRequest streamingOutputOperationRequest, AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -820,8 +854,8 @@ public CompletableFuture streamingOutputOperation( .withOperationName("StreamingOutputOperation") .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(streamingOutputOperationRequest), - asyncResponseTransformer); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(streamingOutputOperationRequest), asyncResponseTransformer); CompletableFuture whenCompleteFuture = null; AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; whenCompleteFuture = executeFuture.whenComplete((r, e) -> { @@ -897,6 +931,20 @@ private static boolean isSignerOverridden(SdkClientConfiguration clientConfigura return Boolean.TRUE.equals(clientConfiguration.option(SdkClientOption.SIGNER_OVERRIDDEN)); } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + QueryServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = QueryServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + @Override public void close() { clientHandler.close(); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-client-class.java index 0ca5d7837899..a1497e0f4d73 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-client-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-client-class.java @@ -11,6 +11,8 @@ import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.CredentialType; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.ClientExecutionParams; @@ -31,6 +33,7 @@ import software.amazon.awssdk.metrics.NoOpMetricCollector; import software.amazon.awssdk.protocols.core.ExceptionMetadata; import software.amazon.awssdk.protocols.query.AwsQueryProtocolFactory; +import software.amazon.awssdk.services.query.internal.QueryServiceClientConfigurationBuilder; import software.amazon.awssdk.services.query.model.APostOperationRequest; import software.amazon.awssdk.services.query.model.APostOperationResponse; import software.amazon.awssdk.services.query.model.APostOperationWithOutputRequest; @@ -127,6 +130,7 @@ public APostOperationResponse aPostOperation(APostOperationRequest aPostOperatio .createResponseHandler(APostOperationResponse::builder); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -140,7 +144,8 @@ public APostOperationResponse aPostOperation(APostOperationRequest aPostOperatio return clientHandler.execute(new ClientExecutionParams() .withOperationName("APostOperation").withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler).hostPrefixExpression(resolvedHostExpression) - .withInput(aPostOperationRequest).withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration).withInput(aPostOperationRequest) + .withMetricCollector(apiCallMetricCollector) .withMarshaller(new APostOperationRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -176,6 +181,8 @@ public APostOperationWithOutputResponse aPostOperationWithOutput( .createResponseHandler(APostOperationWithOutputResponse::builder); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -187,8 +194,8 @@ public APostOperationWithOutputResponse aPostOperationWithOutput( return clientHandler .execute(new ClientExecutionParams() .withOperationName("APostOperationWithOutput").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(aPostOperationWithOutputRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(aPostOperationWithOutputRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -220,6 +227,8 @@ public BearerAuthOperationResponse bearerAuthOperation(BearerAuthOperationReques .createResponseHandler(BearerAuthOperationResponse::builder); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(bearerAuthOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, bearerAuthOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -231,7 +240,8 @@ public BearerAuthOperationResponse bearerAuthOperation(BearerAuthOperationReques return clientHandler.execute(new ClientExecutionParams() .withOperationName("BearerAuthOperation").withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler).credentialType(CredentialType.TOKEN) - .withInput(bearerAuthOperationRequest).withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration).withInput(bearerAuthOperationRequest) + .withMetricCollector(apiCallMetricCollector) .withMarshaller(new BearerAuthOperationRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -263,6 +273,8 @@ public GetOperationWithChecksumResponse getOperationWithChecksum( .createResponseHandler(GetOperationWithChecksumResponse::builder); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getOperationWithChecksumRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, getOperationWithChecksumRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -276,6 +288,7 @@ public GetOperationWithChecksumResponse getOperationWithChecksum( .withOperationName("GetOperationWithChecksum") .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withInput(getOperationWithChecksumRequest) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute( @@ -314,6 +327,8 @@ public OperationWithChecksumRequiredResponse operationWithChecksumRequired( .createResponseHandler(OperationWithChecksumRequiredResponse::builder); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -327,6 +342,7 @@ public OperationWithChecksumRequiredResponse operationWithChecksumRequired( .withOperationName("OperationWithChecksumRequired") .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withInput(operationWithChecksumRequiredRequest) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, @@ -362,6 +378,8 @@ public OperationWithContextParamResponse operationWithContextParam( .createResponseHandler(OperationWithContextParamResponse::builder); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithContextParamRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithContextParamRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -373,8 +391,8 @@ public OperationWithContextParamResponse operationWithContextParam( return clientHandler .execute(new ClientExecutionParams() .withOperationName("OperationWithContextParam").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(operationWithContextParamRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(operationWithContextParamRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new OperationWithContextParamRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -406,6 +424,8 @@ public OperationWithNoneAuthTypeResponse operationWithNoneAuthType( .createResponseHandler(OperationWithNoneAuthTypeResponse::builder); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithNoneAuthTypeRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithNoneAuthTypeRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -417,8 +437,8 @@ public OperationWithNoneAuthTypeResponse operationWithNoneAuthType( return clientHandler .execute(new ClientExecutionParams() .withOperationName("OperationWithNoneAuthType").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(operationWithNoneAuthTypeRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(operationWithNoneAuthTypeRequest).withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.IS_NONE_AUTH_TYPE_REQUEST, false) .withMarshaller(new OperationWithNoneAuthTypeRequestMarshaller(protocolFactory))); } finally { @@ -451,6 +471,8 @@ public OperationWithRequestCompressionResponse operationWithRequestCompression( .createResponseHandler(OperationWithRequestCompressionResponse::builder); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -464,6 +486,7 @@ public OperationWithRequestCompressionResponse operationWithRequestCompression( .withOperationName("OperationWithRequestCompression") .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withInput(operationWithRequestCompressionRequest) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, @@ -499,6 +522,8 @@ public OperationWithStaticContextParamsResponse operationWithStaticContextParams .createResponseHandler(OperationWithStaticContextParamsResponse::builder); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithStaticContextParamsRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithStaticContextParamsRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -510,8 +535,8 @@ public OperationWithStaticContextParamsResponse operationWithStaticContextParams return clientHandler .execute(new ClientExecutionParams() .withOperationName("OperationWithStaticContextParams").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(operationWithStaticContextParamsRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(operationWithStaticContextParamsRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new OperationWithStaticContextParamsRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -569,6 +594,8 @@ public ReturnT putOperationWithChecksum(PutOperationWithChecksumReques .createResponseHandler(PutOperationWithChecksumResponse::builder); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(putOperationWithChecksumRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, putOperationWithChecksumRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -582,6 +609,7 @@ public ReturnT putOperationWithChecksum(PutOperationWithChecksumReques .withOperationName("PutOperationWithChecksum") .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withInput(putOperationWithChecksumRequest) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute( @@ -635,6 +663,8 @@ public StreamingInputOperationResponse streamingInputOperation(StreamingInputOpe .createResponseHandler(StreamingInputOperationResponse::builder); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -648,6 +678,7 @@ public StreamingInputOperationResponse streamingInputOperation(StreamingInputOpe .withOperationName("StreamingInputOperation") .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withInput(streamingInputOperationRequest) .withMetricCollector(apiCallMetricCollector) .withRequestBody(requestBody) @@ -692,6 +723,8 @@ public ReturnT streamingOutputOperation(StreamingOutputOperationReques .createResponseHandler(StreamingOutputOperationResponse::builder); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -703,8 +736,8 @@ public ReturnT streamingOutputOperation(StreamingOutputOperationReques return clientHandler.execute( new ClientExecutionParams() .withOperationName("StreamingOutputOperation").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(streamingOutputOperationRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(streamingOutputOperationRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)), responseTransformer); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -755,6 +788,20 @@ private static List resolveMetricPublishers(SdkClientConfigurat return publishers; } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + QueryServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = QueryServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + private AwsQueryProtocolFactory init() { return AwsQueryProtocolFactory .builder() diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-xml-async-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-xml-async-client-class.java index 959bfd8618bf..a904abf79cc7 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-xml-async-client-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-xml-async-client-class.java @@ -22,7 +22,9 @@ import software.amazon.awssdk.core.CredentialType; import software.amazon.awssdk.core.RequestOverrideConfiguration; import software.amazon.awssdk.core.Response; +import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.SdkPojoBuilder; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.core.async.AsyncResponseTransformerUtils; @@ -45,6 +47,7 @@ import software.amazon.awssdk.protocols.core.ExceptionMetadata; import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory; import software.amazon.awssdk.protocols.xml.XmlOperationMetadata; +import software.amazon.awssdk.services.xml.internal.XmlServiceClientConfigurationBuilder; import software.amazon.awssdk.services.xml.model.APostOperationRequest; import software.amazon.awssdk.services.xml.model.APostOperationResponse; import software.amazon.awssdk.services.xml.model.APostOperationWithOutputRequest; @@ -140,6 +143,7 @@ protected DefaultXmlAsyncClient(XmlServiceClientConfiguration serviceClientConfi */ @Override public CompletableFuture aPostOperation(APostOperationRequest aPostOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -156,7 +160,7 @@ public CompletableFuture aPostOperation(APostOperationRe CompletableFuture executeFuture = clientHandler .execute(new ClientExecutionParams() - .withOperationName("APostOperation") + .withOperationName("APostOperation").withRequestConfiguration(clientConfiguration) .withMarshaller(new APostOperationRequestMarshaller(protocolFactory)) .withCombinedResponseHandler(responseHandler).hostPrefixExpression(resolvedHostExpression) .withMetricCollector(apiCallMetricCollector).withInput(aPostOperationRequest)); @@ -198,6 +202,8 @@ public CompletableFuture aPostOperation(APostOperationRe @Override public CompletableFuture aPostOperationWithOutput( APostOperationWithOutputRequest aPostOperationWithOutputRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -212,7 +218,7 @@ public CompletableFuture aPostOperationWithOut CompletableFuture executeFuture = clientHandler .execute(new ClientExecutionParams() - .withOperationName("APostOperationWithOutput") + .withOperationName("APostOperationWithOutput").withRequestConfiguration(clientConfiguration) .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory)) .withCombinedResponseHandler(responseHandler).withMetricCollector(apiCallMetricCollector) .withInput(aPostOperationWithOutputRequest)); @@ -250,6 +256,8 @@ public CompletableFuture aPostOperationWithOut @Override public CompletableFuture bearerAuthOperation( BearerAuthOperationRequest bearerAuthOperationRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(bearerAuthOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, bearerAuthOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -265,7 +273,7 @@ public CompletableFuture bearerAuthOperation( CompletableFuture executeFuture = clientHandler .execute(new ClientExecutionParams() - .withOperationName("BearerAuthOperation") + .withOperationName("BearerAuthOperation").withRequestConfiguration(clientConfiguration) .withMarshaller(new BearerAuthOperationRequestMarshaller(protocolFactory)) .withCombinedResponseHandler(responseHandler).credentialType(CredentialType.TOKEN) .withMetricCollector(apiCallMetricCollector).withInput(bearerAuthOperationRequest)); @@ -303,6 +311,8 @@ public CompletableFuture bearerAuthOperation( @Override public CompletableFuture eventStreamOperation(EventStreamOperationRequest eventStreamOperationRequest, EventStreamOperationResponseHandler asyncResponseHandler) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(eventStreamOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, eventStreamOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -334,7 +344,7 @@ public CompletableFuture eventStreamOperation(EventStreamOperationRequest CompletableFuture executeFuture = clientHandler.execute( new ClientExecutionParams() - .withOperationName("EventStreamOperation") + .withOperationName("EventStreamOperation").withRequestConfiguration(clientConfiguration) .withMarshaller(new EventStreamOperationRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) .withMetricCollector(apiCallMetricCollector).withInput(eventStreamOperationRequest), @@ -380,6 +390,8 @@ public CompletableFuture eventStreamOperation(EventStreamOperationRequest @Override public CompletableFuture getOperationWithChecksum( GetOperationWithChecksumRequest getOperationWithChecksumRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getOperationWithChecksumRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, getOperationWithChecksumRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -395,6 +407,7 @@ public CompletableFuture getOperationWithCheck CompletableFuture executeFuture = clientHandler .execute(new ClientExecutionParams() .withOperationName("GetOperationWithChecksum") + .withRequestConfiguration(clientConfiguration) .withMarshaller(new GetOperationWithChecksumRequestMarshaller(protocolFactory)) .withCombinedResponseHandler(responseHandler) .withMetricCollector(apiCallMetricCollector) @@ -438,6 +451,8 @@ public CompletableFuture getOperationWithCheck @Override public CompletableFuture operationWithChecksumRequired( OperationWithChecksumRequiredRequest operationWithChecksumRequiredRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -453,6 +468,7 @@ public CompletableFuture operationWithChe CompletableFuture executeFuture = clientHandler .execute(new ClientExecutionParams() .withOperationName("OperationWithChecksumRequired") + .withRequestConfiguration(clientConfiguration) .withMarshaller(new OperationWithChecksumRequiredRequestMarshaller(protocolFactory)) .withCombinedResponseHandler(responseHandler) .withMetricCollector(apiCallMetricCollector) @@ -492,6 +508,8 @@ public CompletableFuture operationWithChe @Override public CompletableFuture operationWithNoneAuthType( OperationWithNoneAuthTypeRequest operationWithNoneAuthTypeRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithNoneAuthTypeRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithNoneAuthTypeRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -506,7 +524,7 @@ public CompletableFuture operationWithNoneAut CompletableFuture executeFuture = clientHandler .execute(new ClientExecutionParams() - .withOperationName("OperationWithNoneAuthType") + .withOperationName("OperationWithNoneAuthType").withRequestConfiguration(clientConfiguration) .withMarshaller(new OperationWithNoneAuthTypeRequestMarshaller(protocolFactory)) .withCombinedResponseHandler(responseHandler).withMetricCollector(apiCallMetricCollector) .putExecutionAttribute(SdkInternalExecutionAttribute.IS_NONE_AUTH_TYPE_REQUEST, false) @@ -546,6 +564,8 @@ public CompletableFuture operationWithNoneAut @Override public CompletableFuture operationWithRequestCompression( OperationWithRequestCompressionRequest operationWithRequestCompressionRequest) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -561,6 +581,7 @@ public CompletableFuture operationWithR CompletableFuture executeFuture = clientHandler .execute(new ClientExecutionParams() .withOperationName("OperationWithRequestCompression") + .withRequestConfiguration(clientConfiguration) .withMarshaller(new OperationWithRequestCompressionRequestMarshaller(protocolFactory)) .withCombinedResponseHandler(responseHandler) .withMetricCollector(apiCallMetricCollector) @@ -620,6 +641,8 @@ public CompletableFuture operationWithR public CompletableFuture putOperationWithChecksum( PutOperationWithChecksumRequest putOperationWithChecksumRequest, AsyncRequestBody requestBody, AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(putOperationWithChecksumRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, putOperationWithChecksumRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -649,6 +672,7 @@ public CompletableFuture putOperationWithChecksum( .asyncRequestBody(requestBody).build()) .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute( SdkInternalExecutionAttribute.HTTP_CHECKSUM, @@ -705,6 +729,8 @@ public CompletableFuture putOperationWithChecksum( @Override public CompletableFuture streamingInputOperation( StreamingInputOperationRequest streamingInputOperationRequest, AsyncRequestBody requestBody) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -723,6 +749,7 @@ public CompletableFuture streamingInputOperatio CompletableFuture executeFuture = clientHandler .execute(new ClientExecutionParams() .withOperationName("StreamingInputOperation") + .withRequestConfiguration(clientConfiguration) .withMarshaller( AsyncStreamingRequestMarshaller.builder() .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) @@ -769,6 +796,8 @@ public CompletableFuture streamingInputOperatio public CompletableFuture streamingOutputOperation( StreamingOutputOperationRequest streamingOutputOperationRequest, AsyncResponseTransformer asyncResponseTransformer) { + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -791,8 +820,8 @@ public CompletableFuture streamingOutputOperation( .withOperationName("StreamingOutputOperation") .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)) .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(streamingOutputOperationRequest), - asyncResponseTransformer); + .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector) + .withInput(streamingOutputOperationRequest), asyncResponseTransformer); CompletableFuture whenCompleteFuture = null; AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer; whenCompleteFuture = executeFuture.whenComplete((r, e) -> { @@ -863,6 +892,20 @@ private static boolean isSignerOverridden(SdkClientConfiguration clientConfigura return Boolean.TRUE.equals(clientConfiguration.option(SdkClientOption.SIGNER_OVERRIDDEN)); } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + XmlServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = XmlServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + @Override public void close() { clientHandler.close(); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-xml-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-xml-client-class.java index d52550654b17..2d485610e4a6 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-xml-client-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-xml-client-class.java @@ -12,6 +12,8 @@ import software.amazon.awssdk.core.CredentialType; import software.amazon.awssdk.core.RequestOverrideConfiguration; import software.amazon.awssdk.core.Response; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.ClientExecutionParams; @@ -33,6 +35,7 @@ import software.amazon.awssdk.protocols.core.ExceptionMetadata; import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory; import software.amazon.awssdk.protocols.xml.XmlOperationMetadata; +import software.amazon.awssdk.services.xml.internal.XmlServiceClientConfigurationBuilder; import software.amazon.awssdk.services.xml.model.APostOperationRequest; import software.amazon.awssdk.services.xml.model.APostOperationResponse; import software.amazon.awssdk.services.xml.model.APostOperationWithOutputRequest; @@ -120,6 +123,7 @@ public APostOperationResponse aPostOperation(APostOperationRequest aPostOperatio HttpResponseHandler> responseHandler = protocolFactory.createCombinedResponseHandler( APostOperationResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -133,7 +137,8 @@ public APostOperationResponse aPostOperation(APostOperationRequest aPostOperatio return clientHandler.execute(new ClientExecutionParams() .withOperationName("APostOperation").withCombinedResponseHandler(responseHandler) .withMetricCollector(apiCallMetricCollector).hostPrefixExpression(resolvedHostExpression) - .withInput(aPostOperationRequest).withMarshaller(new APostOperationRequestMarshaller(protocolFactory))); + .withRequestConfiguration(clientConfiguration).withInput(aPostOperationRequest) + .withMarshaller(new APostOperationRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); } @@ -167,6 +172,8 @@ public APostOperationWithOutputResponse aPostOperationWithOutput( HttpResponseHandler> responseHandler = protocolFactory .createCombinedResponseHandler(APostOperationWithOutputResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationWithOutputRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationWithOutputRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -178,7 +185,8 @@ public APostOperationWithOutputResponse aPostOperationWithOutput( return clientHandler .execute(new ClientExecutionParams() .withOperationName("APostOperationWithOutput").withCombinedResponseHandler(responseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(aPostOperationWithOutputRequest) + .withMetricCollector(apiCallMetricCollector).withRequestConfiguration(clientConfiguration) + .withInput(aPostOperationWithOutputRequest) .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -209,6 +217,8 @@ public BearerAuthOperationResponse bearerAuthOperation(BearerAuthOperationReques HttpResponseHandler> responseHandler = protocolFactory .createCombinedResponseHandler(BearerAuthOperationResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(bearerAuthOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, bearerAuthOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -220,7 +230,7 @@ public BearerAuthOperationResponse bearerAuthOperation(BearerAuthOperationReques return clientHandler.execute(new ClientExecutionParams() .withOperationName("BearerAuthOperation").withCombinedResponseHandler(responseHandler) .withMetricCollector(apiCallMetricCollector).credentialType(CredentialType.TOKEN) - .withInput(bearerAuthOperationRequest) + .withRequestConfiguration(clientConfiguration).withInput(bearerAuthOperationRequest) .withMarshaller(new BearerAuthOperationRequestMarshaller(protocolFactory))); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -251,6 +261,8 @@ public GetOperationWithChecksumResponse getOperationWithChecksum( HttpResponseHandler> responseHandler = protocolFactory .createCombinedResponseHandler(GetOperationWithChecksumResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(getOperationWithChecksumRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, getOperationWithChecksumRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -264,6 +276,7 @@ public GetOperationWithChecksumResponse getOperationWithChecksum( .withOperationName("GetOperationWithChecksum") .withCombinedResponseHandler(responseHandler) .withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration) .withInput(getOperationWithChecksumRequest) .putExecutionAttribute( SdkInternalExecutionAttribute.HTTP_CHECKSUM, @@ -300,6 +313,8 @@ public OperationWithChecksumRequiredResponse operationWithChecksumRequired( HttpResponseHandler> responseHandler = protocolFactory .createCombinedResponseHandler(OperationWithChecksumRequiredResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithChecksumRequiredRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithChecksumRequiredRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -313,6 +328,7 @@ public OperationWithChecksumRequiredResponse operationWithChecksumRequired( .withOperationName("OperationWithChecksumRequired") .withCombinedResponseHandler(responseHandler) .withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration) .withInput(operationWithChecksumRequiredRequest) .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM_REQUIRED, HttpChecksumRequired.create()) @@ -346,6 +362,8 @@ public OperationWithNoneAuthTypeResponse operationWithNoneAuthType( HttpResponseHandler> responseHandler = protocolFactory .createCombinedResponseHandler(OperationWithNoneAuthTypeResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithNoneAuthTypeRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithNoneAuthTypeRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -357,7 +375,8 @@ public OperationWithNoneAuthTypeResponse operationWithNoneAuthType( return clientHandler .execute(new ClientExecutionParams() .withOperationName("OperationWithNoneAuthType").withCombinedResponseHandler(responseHandler) - .withMetricCollector(apiCallMetricCollector).withInput(operationWithNoneAuthTypeRequest) + .withMetricCollector(apiCallMetricCollector).withRequestConfiguration(clientConfiguration) + .withInput(operationWithNoneAuthTypeRequest) .putExecutionAttribute(SdkInternalExecutionAttribute.IS_NONE_AUTH_TYPE_REQUEST, false) .withMarshaller(new OperationWithNoneAuthTypeRequestMarshaller(protocolFactory))); } finally { @@ -389,6 +408,8 @@ public OperationWithRequestCompressionResponse operationWithRequestCompression( HttpResponseHandler> responseHandler = protocolFactory .createCombinedResponseHandler(OperationWithRequestCompressionResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithRequestCompressionRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithRequestCompressionRequest.overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -402,6 +423,7 @@ public OperationWithRequestCompressionResponse operationWithRequestCompression( .withOperationName("OperationWithRequestCompression") .withCombinedResponseHandler(responseHandler) .withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration) .withInput(operationWithRequestCompressionRequest) .putExecutionAttribute(SdkInternalExecutionAttribute.REQUEST_COMPRESSION, RequestCompression.builder().encodings("gzip").isStreaming(false).build()) @@ -462,6 +484,8 @@ public ReturnT putOperationWithChecksum(PutOperationWithChecksumReques PutOperationWithChecksumResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(true)); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(putOperationWithChecksumRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, putOperationWithChecksumRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -475,6 +499,7 @@ public ReturnT putOperationWithChecksum(PutOperationWithChecksumReques .withOperationName("PutOperationWithChecksum") .withResponseHandler(responseHandler) .withErrorResponseHandler(errorResponseHandler) + .withRequestConfiguration(clientConfiguration) .withInput(putOperationWithChecksumRequest) .withMetricCollector(apiCallMetricCollector) .putExecutionAttribute( @@ -527,6 +552,8 @@ public StreamingInputOperationResponse streamingInputOperation(StreamingInputOpe HttpResponseHandler> responseHandler = protocolFactory .createCombinedResponseHandler(StreamingInputOperationResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(false)); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingInputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingInputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -540,6 +567,7 @@ public StreamingInputOperationResponse streamingInputOperation(StreamingInputOpe .withOperationName("StreamingInputOperation") .withCombinedResponseHandler(responseHandler) .withMetricCollector(apiCallMetricCollector) + .withRequestConfiguration(clientConfiguration) .withInput(streamingInputOperationRequest) .withRequestBody(requestBody) .withMarshaller( @@ -583,6 +611,8 @@ public ReturnT streamingOutputOperation(StreamingOutputOperationReques StreamingOutputOperationResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(true)); HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); + SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(streamingOutputOperationRequest, + this.clientConfiguration); List metricPublishers = resolveMetricPublishers(clientConfiguration, streamingOutputOperationRequest .overrideConfiguration().orElse(null)); MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector @@ -594,8 +624,8 @@ public ReturnT streamingOutputOperation(StreamingOutputOperationReques return clientHandler.execute( new ClientExecutionParams() .withOperationName("StreamingOutputOperation").withResponseHandler(responseHandler) - .withErrorResponseHandler(errorResponseHandler).withInput(streamingOutputOperationRequest) - .withMetricCollector(apiCallMetricCollector) + .withErrorResponseHandler(errorResponseHandler).withRequestConfiguration(clientConfiguration) + .withInput(streamingOutputOperationRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)), responseTransformer); } finally { metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect())); @@ -633,6 +663,20 @@ private static List resolveMetricPublishers(SdkClientConfigurat return publishers; } + private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) { + List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList()); + if (plugins.isEmpty()) { + return clientConfiguration; + } + XmlServiceClientConfigurationBuilder.BuilderInternal serviceConfigBuilder = XmlServiceClientConfigurationBuilder + .builder(clientConfiguration.toBuilder()); + serviceConfigBuilder.overrideConfiguration(serviceClientConfiguration.overrideConfiguration()); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + return serviceConfigBuilder.buildSdkClientConfiguration(); + } + private AwsXmlProtocolFactory init() { return AwsXmlProtocolFactory .builder() diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/eventstreamoperationrequest.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/eventstreamoperationrequest.java index f54ea2e67215..ea0b39f4f87e 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/eventstreamoperationrequest.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/eventstreamoperationrequest.java @@ -121,4 +121,3 @@ public List> sdkFields() { } } } - diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/eventstreamoperationwithonlyinputrequest.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/eventstreamoperationwithonlyinputrequest.java index 28924eb1ac60..8138680b03fb 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/eventstreamoperationwithonlyinputrequest.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/eventstreamoperationwithonlyinputrequest.java @@ -121,4 +121,3 @@ public List> sdkFields() { } } } - diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithnoinputoroutputrequest.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithnoinputoroutputrequest.java index 400dfebd0c32..55b4018a03d1 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithnoinputoroutputrequest.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithnoinputoroutputrequest.java @@ -119,4 +119,3 @@ public List> sdkFields() { } } } - diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/serviceclientconfiguration-builder.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/serviceclientconfiguration-builder.java new file mode 100644 index 000000000000..a5e6064a8462 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/serviceclientconfiguration-builder.java @@ -0,0 +1,225 @@ +package software.amazon.awssdk.services.jsonprotocoltests.internal; + +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.config.AwsClientOption; +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.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.jsonprotocoltests.JsonProtocolTestsServiceClientConfiguration; +import software.amazon.awssdk.services.jsonprotocoltests.auth.scheme.JsonProtocolTestsAuthSchemeProvider; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public class JsonProtocolTestsServiceClientConfigurationBuilder { + public static JsonProtocolTestsServiceClientConfiguration.Builder builder() { + return new BuilderImpl(); + } + + public static BuilderInternal builder(SdkClientConfiguration.Builder builder) { + return new BuilderImpl(builder); + } + + public interface BuilderInternal extends JsonProtocolTestsServiceClientConfiguration.Builder { + SdkClientConfiguration buildSdkClientConfiguration(); + } + + public static class BuilderImpl implements BuilderInternal { + private final SdkClientConfiguration.Builder internalBuilder; + + private ClientOverrideConfiguration overrideConfiguration; + + private URI endpointOverride; + + private IdentityProvider credentialsProvider; + + private Map> authSchemes; + + private BuilderImpl() { + this.internalBuilder = SdkClientConfiguration.builder(); + } + + private BuilderImpl(SdkClientConfiguration.Builder internalBuilder) { + this.internalBuilder = internalBuilder; + if (Boolean.TRUE.equals(internalBuilder.option(SdkClientOption.ENDPOINT_OVERRIDDEN))) { + this.endpointOverride = internalBuilder.option(SdkClientOption.ENDPOINT); + } + this.credentialsProvider = internalBuilder.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER); + Map> authSchemes = internalBuilder.option(SdkClientOption.AUTH_SCHEMES); + if (authSchemes != null) { + authSchemes = new HashMap<>(authSchemes); + } + this.authSchemes = authSchemes; + } + + /** + * Sets the value for client override configuration + */ + @Override + public JsonProtocolTestsServiceClientConfiguration.Builder overrideConfiguration( + ClientOverrideConfiguration overrideConfiguration) { + this.overrideConfiguration = overrideConfiguration; + return this; + } + + /** + * Gets the value for client override configuration + */ + @Override + public ClientOverrideConfiguration overrideConfiguration() { + return overrideConfiguration; + } + + /** + * Sets the value for endpoint override + */ + @Override + public JsonProtocolTestsServiceClientConfiguration.Builder endpointOverride(URI endpointOverride) { + this.endpointOverride = endpointOverride; + return this; + } + + /** + * Gets the value for endpoint override + */ + @Override + public URI endpointOverride() { + return endpointOverride; + } + + /** + * Sets the value for endpoint provider + */ + @Override + public JsonProtocolTestsServiceClientConfiguration.Builder endpointProvider(EndpointProvider endpointProvider) { + internalBuilder.option(SdkClientOption.ENDPOINT_PROVIDER, endpointProvider); + return this; + } + + /** + * Gets the value for endpoint provider + */ + @Override + public EndpointProvider endpointProvider() { + return internalBuilder.option(SdkClientOption.ENDPOINT_PROVIDER); + } + + /** + * Sets the value for AWS region + */ + @Override + public JsonProtocolTestsServiceClientConfiguration.Builder region(Region region) { + internalBuilder.option(AwsClientOption.AWS_REGION, region); + return this; + } + + /** + * Gets the value for AWS region + */ + @Override + public Region region() { + return internalBuilder.option(AwsClientOption.AWS_REGION); + } + + /** + * Sets the value for credentials provider + */ + @Override + public JsonProtocolTestsServiceClientConfiguration.Builder credentialsProvider( + IdentityProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * Gets the value for credentials provider + */ + @Override + public IdentityProvider credentialsProvider() { + return credentialsProvider; + } + + @Override + public JsonProtocolTestsServiceClientConfiguration.Builder putAuthScheme(AuthScheme authScheme) { + if (authSchemes == null) { + authSchemes = new HashMap<>(); + } + authSchemes.put(authScheme.schemeId(), authScheme); + return this; + } + + @Override + public Map> authSchemes() { + if (authSchemes == null) { + return Collections.emptyMap(); + } + return Collections.unmodifiableMap(new HashMap<>(authSchemes)); + } + + /** + * Sets the value for auth scheme provider + */ + @Override + public JsonProtocolTestsServiceClientConfiguration.Builder authSchemeProvider( + JsonProtocolTestsAuthSchemeProvider authSchemeProvider) { + internalBuilder.option(SdkClientOption.AUTH_SCHEME_PROVIDER, authSchemeProvider); + return this; + } + + /** + * Gets the value for auth scheme provider + */ + @Override + public JsonProtocolTestsAuthSchemeProvider authSchemeProvider() { + AuthSchemeProvider result = internalBuilder.option(SdkClientOption.AUTH_SCHEME_PROVIDER); + if (result == null) { + return null; + } + return Validate.isInstanceOf(JsonProtocolTestsAuthSchemeProvider.class, result, "Expected an instance of " + + JsonProtocolTestsAuthSchemeProvider.class.getSimpleName()); + } + + @Override + public JsonProtocolTestsServiceClientConfiguration build() { + return new JsonProtocolTestsServiceClientConfiguration(this); + } + + @Override + public SdkClientConfiguration buildSdkClientConfiguration() { + if (overrideConfiguration != null) { + SdkClientConfigurationUtil.copyOverridesToConfiguration(overrideConfiguration, internalBuilder); + } + if (endpointOverride != null) { + internalBuilder.option(SdkClientOption.ENDPOINT, endpointOverride); + internalBuilder.option(SdkClientOption.ENDPOINT_OVERRIDDEN, true); + } + if (credentialsProvider != null + && !credentialsProvider.equals(internalBuilder.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER))) { + internalBuilder.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER, credentialsProvider); + IdentityProviders identityProviders = internalBuilder.option(SdkClientOption.IDENTITY_PROVIDERS); + if (identityProviders == null) { + identityProviders = IdentityProviders.builder().putIdentityProvider(credentialsProvider).build(); + } else { + identityProviders = identityProviders.toBuilder().putIdentityProvider(credentialsProvider).build(); + } + internalBuilder.option(SdkClientOption.IDENTITY_PROVIDERS, identityProviders); + } + if (authSchemes != null && !authSchemes.equals(internalBuilder.option(SdkClientOption.AUTH_SCHEMES))) { + internalBuilder.option(SdkClientOption.AUTH_SCHEMES, authSchemes()); + } + return internalBuilder.build(); + } + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/serviceclientconfiguration.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/serviceclientconfiguration.java index 572604798a8b..4c38f0d3a796 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/serviceclientconfiguration.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/serviceclientconfiguration.java @@ -1,12 +1,18 @@ package software.amazon.awssdk.services.jsonprotocoltests; import java.net.URI; +import java.util.Map; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.awscore.AwsServiceClientConfiguration; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.jsonprotocoltests.auth.scheme.JsonProtocolTestsAuthSchemeProvider; +import software.amazon.awssdk.services.jsonprotocoltests.internal.JsonProtocolTestsServiceClientConfigurationBuilder; /** * Class to expose the service client settings to the user. Implementation of {@link AwsServiceClientConfiguration} @@ -14,82 +20,108 @@ @Generated("software.amazon.awssdk:codegen") @SdkPublicApi public final class JsonProtocolTestsServiceClientConfiguration extends AwsServiceClientConfiguration { - private JsonProtocolTestsServiceClientConfiguration(Builder builder) { + private final JsonProtocolTestsAuthSchemeProvider authSchemeProvider; + + public JsonProtocolTestsServiceClientConfiguration(Builder builder) { super(builder); + this.authSchemeProvider = builder.authSchemeProvider(); + } + + /** + * Gets the value for auth scheme provider + */ + public JsonProtocolTestsAuthSchemeProvider authSchemeProvider() { + return authSchemeProvider; } public static Builder builder() { - return new BuilderImpl(); + return JsonProtocolTestsServiceClientConfigurationBuilder.builder(); } /** * A builder for creating a {@link JsonProtocolTestsServiceClientConfiguration} */ public interface Builder extends AwsServiceClientConfiguration.Builder { + /** + * Sets the value for client override configuration + */ @Override - JsonProtocolTestsServiceClientConfiguration build(); + Builder overrideConfiguration(ClientOverrideConfiguration overrideConfiguration); /** - * Configure the region + * Gets the value for client override configuration */ @Override - Builder region(Region region); + ClientOverrideConfiguration overrideConfiguration(); /** - * Configure the endpointOverride + * Sets the value for endpoint override */ @Override Builder endpointOverride(URI endpointOverride); /** - * Configure the client override configuration + * Gets the value for endpoint override */ @Override - Builder overrideConfiguration(ClientOverrideConfiguration clientOverrideConfiguration); + URI endpointOverride(); /** - * Configure the endpointProvider + * Sets the value for endpoint provider */ @Override Builder endpointProvider(EndpointProvider endpointProvider); - } + /** + * Gets the value for endpoint provider + */ + @Override + EndpointProvider endpointProvider(); - private static final class BuilderImpl extends AwsServiceClientConfiguration.BuilderImpl implements Builder { - private BuilderImpl() { - } + /** + * Sets the value for AWS region + */ + @Override + Builder region(Region region); - private BuilderImpl(JsonProtocolTestsServiceClientConfiguration serviceClientConfiguration) { - super(serviceClientConfiguration); - } + /** + * Gets the value for AWS region + */ + @Override + Region region(); + /** + * Sets the value for credentials provider + */ @Override - public Builder region(Region region) { - this.region = region; - return this; - } + Builder credentialsProvider(IdentityProvider credentialsProvider); + /** + * Gets the value for credentials provider + */ @Override - public Builder overrideConfiguration(ClientOverrideConfiguration clientOverrideConfiguration) { - this.overrideConfiguration = clientOverrideConfiguration; - return this; - } + IdentityProvider credentialsProvider(); @Override - public Builder endpointOverride(URI endpointOverride) { - this.endpointOverride = endpointOverride; - return this; - } + Builder putAuthScheme(AuthScheme authScheme); + /** + * Gets the value for auth schemes + */ @Override - public Builder endpointProvider(EndpointProvider endpointProvider) { - this.endpointProvider = endpointProvider; - return this; - } + Map> authSchemes(); + + /** + * Sets the value for auth scheme provider + */ + Builder authSchemeProvider(JsonProtocolTestsAuthSchemeProvider authSchemeProvider); + + /** + * Gets the value for auth scheme provider + */ + JsonProtocolTestsAuthSchemeProvider authSchemeProvider(); @Override - public JsonProtocolTestsServiceClientConfiguration build() { - return new JsonProtocolTestsServiceClientConfiguration(this); - } + JsonProtocolTestsServiceClientConfiguration build(); } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/getrandompersonrequest.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/getrandompersonrequest.java index c0b560494d1b..fe6a522a879d 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/getrandompersonrequest.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/getrandompersonrequest.java @@ -118,4 +118,3 @@ public List> sdkFields() { } } } - diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streambirthsrequest.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streambirthsrequest.java index 2fd73f6cae52..ce82614a114b 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streambirthsrequest.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streambirthsrequest.java @@ -118,4 +118,3 @@ public List> sdkFields() { } } } - diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streambirthsresponse.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streambirthsresponse.java index 2a31f3778465..ff15117bac35 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streambirthsresponse.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streambirthsresponse.java @@ -101,4 +101,3 @@ public List> sdkFields() { } } } - diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streamdeathsrequest.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streamdeathsrequest.java index 8b57134e50d2..fd46bd164640 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streamdeathsrequest.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streamdeathsrequest.java @@ -118,4 +118,3 @@ public List> sdkFields() { } } } - diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streamdeathsresponse.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streamdeathsresponse.java index 05f1b52262b4..f524a65fac8b 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streamdeathsresponse.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/sharedstream/streamdeathsresponse.java @@ -101,4 +101,3 @@ public List> sdkFields() { } } } - diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/streaminginputoperationrequest.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/streaminginputoperationrequest.java index 1a204e847b7e..f34af78fc192 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/streaminginputoperationrequest.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/streaminginputoperationrequest.java @@ -121,4 +121,3 @@ public List> sdkFields() { } } } - diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/streamingoutputoperationrequest.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/streamingoutputoperationrequest.java index b8645f580795..4ad67e9f6e1e 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/streamingoutputoperationrequest.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/streamingoutputoperationrequest.java @@ -119,4 +119,3 @@ public List> sdkFields() { } } } - diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-auth-scheme-interceptor.java deleted file mode 100644 index 9ce2351d6db6..000000000000 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-auth-scheme-interceptor.java +++ /dev/null @@ -1,53 +0,0 @@ -package software.amazon.awssdk.services.query.endpoints.internal; - -import java.util.List; -import java.util.function.Supplier; -import software.amazon.awssdk.annotations.Generated; -import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.signer.Aws4Signer; -import software.amazon.awssdk.auth.signer.SignerLoader; -import software.amazon.awssdk.awscore.AwsRequest; -import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; -import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; -import software.amazon.awssdk.awscore.util.SignerOverrideUtils; -import software.amazon.awssdk.core.SdkRequest; -import software.amazon.awssdk.core.exception.SdkClientException; -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.SdkInternalExecutionAttribute; -import software.amazon.awssdk.core.signer.Signer; -import software.amazon.awssdk.endpoints.Endpoint; - -@Generated("software.amazon.awssdk:codegen") -@SdkInternalApi -public final class QueryEndpointAuthSchemeInterceptor implements ExecutionInterceptor { - @Override - public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { - Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT); - AwsRequest request = (AwsRequest) context.request(); - if (resolvedEndpoint.headers() != null) { - request = AwsEndpointProviderUtils.addHeaders(request, resolvedEndpoint.headers()); - } - List authSchemes = resolvedEndpoint.attribute(AwsEndpointAttribute.AUTH_SCHEMES); - if (authSchemes == null) { - return request; - } - EndpointAuthScheme chosenAuthScheme = AuthSchemeUtils.chooseAuthScheme(authSchemes); - Supplier signerProvider = signerProvider(chosenAuthScheme); - AuthSchemeUtils.setSigningParams(executionAttributes, chosenAuthScheme); - return SignerOverrideUtils.overrideSignerIfNotOverridden(request, executionAttributes, signerProvider); - } - - private Supplier signerProvider(EndpointAuthScheme authScheme) { - switch (authScheme.name()) { - case "sigv4": - return Aws4Signer::create; - case "sigv4a": - return SignerLoader::getSigV4aSigner; - default: - break; - } - throw SdkClientException.create("Don't know how to create signer for auth scheme: " + authScheme.name()); - } -} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-provider-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-provider-class.java index c5f87bc1efea..b657640f35d2 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-provider-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-provider-class.java @@ -304,7 +304,8 @@ private static EndpointRuleset ruleSet() { .required(false).build()) .addParameter( Parameter.builder().name("defaultTrueParam").type(ParameterType.fromValue("boolean")) - .required(false).defaultValue(Value.fromBool(true)).build()) + .required(false).documentation("A param that defauls to true") + .defaultValue(Value.fromBool(true)).build()) .addParameter( Parameter.builder().name("defaultStringParam").type(ParameterType.fromValue("string")) .required(false).defaultValue(Value.fromStr("hello endpoints")).build()) diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-preSra.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-preSra.java new file mode 100644 index 000000000000..305e7d3ded10 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-preSra.java @@ -0,0 +1,201 @@ +package software.amazon.awssdk.services.query.endpoints.internal; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletionException; +import java.util.function.Supplier; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.signer.Aws4Signer; +import software.amazon.awssdk.auth.signer.SignerLoader; +import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; +import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; +import software.amazon.awssdk.awscore.util.SignerOverrideUtils; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SelectedAuthScheme; +import software.amazon.awssdk.core.exception.SdkClientException; +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.signer.Signer; +import software.amazon.awssdk.endpoints.Endpoint; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.services.query.endpoints.QueryClientContextParams; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; +import software.amazon.awssdk.services.query.model.OperationWithContextParamRequest; +import software.amazon.awssdk.utils.AttributeMap; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class QueryResolveEndpointInterceptor implements ExecutionInterceptor { + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + SdkRequest result = context.request(); + if (AwsEndpointProviderUtils.endpointIsDiscovered(executionAttributes)) { + return result; + } + QueryEndpointProvider provider = (QueryEndpointProvider) executionAttributes + .getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER); + try { + Endpoint endpoint = provider.resolveEndpoint(ruleParams(result, executionAttributes)).join(); + if (!AwsEndpointProviderUtils.disableHostPrefixInjection(executionAttributes)) { + Optional hostPrefix = hostPrefix(executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME), + result); + if (hostPrefix.isPresent()) { + endpoint = AwsEndpointProviderUtils.addHostPrefix(endpoint, hostPrefix.get()); + } + } + List endpointAuthSchemes = endpoint.attribute(AwsEndpointAttribute.AUTH_SCHEMES); + SelectedAuthScheme selectedAuthScheme = executionAttributes + .getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + if (endpointAuthSchemes != null && selectedAuthScheme != null) { + selectedAuthScheme = authSchemeWithEndpointSignerProperties(endpointAuthSchemes, selectedAuthScheme); + executionAttributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, selectedAuthScheme); + } + if (endpointAuthSchemes != null) { + EndpointAuthScheme chosenAuthScheme = AuthSchemeUtils.chooseAuthScheme(endpointAuthSchemes); + Supplier signerProvider = signerProvider(chosenAuthScheme); + result = SignerOverrideUtils.overrideSignerIfNotOverridden(result, executionAttributes, signerProvider); + } + executionAttributes.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, endpoint); + return result; + } catch (CompletionException e) { + Throwable cause = e.getCause(); + if (cause instanceof SdkClientException) { + throw (SdkClientException) cause; + } else { + throw SdkClientException.create("Endpoint resolution failed", cause); + } + } + } + + @Override + public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { + Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT); + if (resolvedEndpoint.headers().isEmpty()) { + return context.httpRequest(); + } + SdkHttpRequest.Builder httpRequestBuilder = context.httpRequest().toBuilder(); + resolvedEndpoint.headers().forEach((name, values) -> { + values.forEach(v -> httpRequestBuilder.appendHeader(name, v)); + }); + return httpRequestBuilder.build(); + } + + public static QueryEndpointParams ruleParams(SdkRequest request, ExecutionAttributes executionAttributes) { + QueryEndpointParams.Builder builder = QueryEndpointParams.builder(); + builder.region(AwsEndpointProviderUtils.regionBuiltIn(executionAttributes)); + builder.useDualStackEndpoint(AwsEndpointProviderUtils.dualStackEnabledBuiltIn(executionAttributes)); + builder.useFipsEndpoint(AwsEndpointProviderUtils.fipsEnabledBuiltIn(executionAttributes)); + setClientContextParams(builder, executionAttributes); + setContextParams(builder, executionAttributes.getAttribute(AwsExecutionAttribute.OPERATION_NAME), request); + setStaticContextParams(builder, executionAttributes.getAttribute(AwsExecutionAttribute.OPERATION_NAME)); + return builder.build(); + } + + private static void setContextParams(QueryEndpointParams.Builder params, String operationName, SdkRequest request) { + switch (operationName) { + case "OperationWithContextParam": + setContextParams(params, (OperationWithContextParamRequest) request); + break; + default: + break; + } + } + + private static void setContextParams(QueryEndpointParams.Builder params, OperationWithContextParamRequest request) { + params.operationContextParam(request.stringMember()); + } + + private static void setStaticContextParams(QueryEndpointParams.Builder params, String operationName) { + switch (operationName) { + case "OperationWithStaticContextParams": + operationWithStaticContextParamsStaticContextParams(params); + break; + default: + break; + } + } + + private static void operationWithStaticContextParamsStaticContextParams(QueryEndpointParams.Builder params) { + params.staticStringParam("hello"); + } + + private SelectedAuthScheme authSchemeWithEndpointSignerProperties( + List endpointAuthSchemes, SelectedAuthScheme selectedAuthScheme) { + for (EndpointAuthScheme endpointAuthScheme : endpointAuthSchemes) { + AuthSchemeOption.Builder option = selectedAuthScheme.authSchemeOption().toBuilder(); + if (endpointAuthScheme instanceof SigV4AuthScheme) { + SigV4AuthScheme v4AuthScheme = (SigV4AuthScheme) endpointAuthScheme; + if (v4AuthScheme.isDisableDoubleEncodingSet()) { + option.putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, !v4AuthScheme.disableDoubleEncoding()); + } + if (v4AuthScheme.signingRegion() != null) { + option.putSignerProperty(AwsV4HttpSigner.REGION_NAME, v4AuthScheme.signingRegion()); + } + if (v4AuthScheme.signingName() != null) { + option.putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, v4AuthScheme.signingName()); + } + return new SelectedAuthScheme<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), option.build()); + } + if (endpointAuthScheme instanceof SigV4aAuthScheme) { + SigV4aAuthScheme v4aAuthScheme = (SigV4aAuthScheme) endpointAuthScheme; + if (v4aAuthScheme.isDisableDoubleEncodingSet()) { + option.putSignerProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, !v4aAuthScheme.disableDoubleEncoding()); + } + if (v4aAuthScheme.signingRegionSet() != null) { + RegionSet regionSet = RegionSet.create(v4aAuthScheme.signingRegionSet()); + option.putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet); + } + if (v4aAuthScheme.signingName() != null) { + option.putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, v4aAuthScheme.signingName()); + } + return new SelectedAuthScheme<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), option.build()); + } + throw new IllegalArgumentException("Endpoint auth scheme '" + endpointAuthScheme.name() + + "' cannot be mapped to the SDK auth scheme. Was it declared in the service's model?"); + } + return selectedAuthScheme; + } + + private static void setClientContextParams(QueryEndpointParams.Builder params, ExecutionAttributes executionAttributes) { + AttributeMap clientContextParams = executionAttributes.getAttribute(SdkInternalExecutionAttribute.CLIENT_CONTEXT_PARAMS); + Optional.ofNullable(clientContextParams.get(QueryClientContextParams.BOOLEAN_CONTEXT_PARAM)).ifPresent( + params::booleanContextParam); + Optional.ofNullable(clientContextParams.get(QueryClientContextParams.STRING_CONTEXT_PARAM)).ifPresent( + params::stringContextParam); + } + + private static Optional hostPrefix(String operationName, SdkRequest request) { + switch (operationName) { + case "APostOperation": { + return Optional.of("foo-"); + } + default: + return Optional.empty(); + } + } + + private Supplier signerProvider(EndpointAuthScheme authScheme) { + switch (authScheme.name()) { + case "sigv4": + return Aws4Signer::create; + case "sigv4a": + return SignerLoader::getSigV4aSigner; + default: + break; + } + throw SdkClientException.create("Don't know how to create signer for auth scheme: " + authScheme.name()); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java index c0e2335014dd..4d2a407cf416 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java @@ -1,11 +1,17 @@ package software.amazon.awssdk.services.query.endpoints.internal; +import java.util.List; import java.util.Optional; import java.util.concurrent.CompletionException; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; +import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; @@ -13,6 +19,12 @@ import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.endpoints.Endpoint; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.identity.spi.Identity; import software.amazon.awssdk.services.query.endpoints.QueryClientContextParams; import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; @@ -24,22 +36,30 @@ public final class QueryResolveEndpointInterceptor implements ExecutionInterceptor { @Override public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + SdkRequest result = context.request(); if (AwsEndpointProviderUtils.endpointIsDiscovered(executionAttributes)) { - return context.request(); + return result; } QueryEndpointProvider provider = (QueryEndpointProvider) executionAttributes .getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER); try { - Endpoint result = provider.resolveEndpoint(ruleParams(context, executionAttributes)).join(); + Endpoint endpoint = provider.resolveEndpoint(ruleParams(result, executionAttributes)).join(); if (!AwsEndpointProviderUtils.disableHostPrefixInjection(executionAttributes)) { Optional hostPrefix = hostPrefix(executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME), - context.request()); + result); if (hostPrefix.isPresent()) { - result = AwsEndpointProviderUtils.addHostPrefix(result, hostPrefix.get()); + endpoint = AwsEndpointProviderUtils.addHostPrefix(endpoint, hostPrefix.get()); } } - executionAttributes.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, result); - return context.request(); + List endpointAuthSchemes = endpoint.attribute(AwsEndpointAttribute.AUTH_SCHEMES); + SelectedAuthScheme selectedAuthScheme = executionAttributes + .getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + if (endpointAuthSchemes != null && selectedAuthScheme != null) { + selectedAuthScheme = authSchemeWithEndpointSignerProperties(endpointAuthSchemes, selectedAuthScheme); + executionAttributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, selectedAuthScheme); + } + executionAttributes.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, endpoint); + return result; } catch (CompletionException e) { Throwable cause = e.getCause(); if (cause instanceof SdkClientException) { @@ -50,13 +70,26 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut } } - private static QueryEndpointParams ruleParams(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + @Override + public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { + Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT); + if (resolvedEndpoint.headers().isEmpty()) { + return context.httpRequest(); + } + SdkHttpRequest.Builder httpRequestBuilder = context.httpRequest().toBuilder(); + resolvedEndpoint.headers().forEach((name, values) -> { + values.forEach(v -> httpRequestBuilder.appendHeader(name, v)); + }); + return httpRequestBuilder.build(); + } + + public static QueryEndpointParams ruleParams(SdkRequest request, ExecutionAttributes executionAttributes) { QueryEndpointParams.Builder builder = QueryEndpointParams.builder(); builder.region(AwsEndpointProviderUtils.regionBuiltIn(executionAttributes)); builder.useDualStackEndpoint(AwsEndpointProviderUtils.dualStackEnabledBuiltIn(executionAttributes)); builder.useFipsEndpoint(AwsEndpointProviderUtils.fipsEnabledBuiltIn(executionAttributes)); setClientContextParams(builder, executionAttributes); - setContextParams(builder, executionAttributes.getAttribute(AwsExecutionAttribute.OPERATION_NAME), context.request()); + setContextParams(builder, executionAttributes.getAttribute(AwsExecutionAttribute.OPERATION_NAME), request); setStaticContextParams(builder, executionAttributes.getAttribute(AwsExecutionAttribute.OPERATION_NAME)); return builder.build(); } @@ -89,6 +122,46 @@ private static void operationWithStaticContextParamsStaticContextParams(QueryEnd params.staticStringParam("hello"); } + private SelectedAuthScheme authSchemeWithEndpointSignerProperties( + List endpointAuthSchemes, SelectedAuthScheme selectedAuthScheme) { + for (EndpointAuthScheme endpointAuthScheme : endpointAuthSchemes) { + if (!endpointAuthScheme.schemeId().equals(selectedAuthScheme.authSchemeOption().schemeId())) { + continue; + } + AuthSchemeOption.Builder option = selectedAuthScheme.authSchemeOption().toBuilder(); + if (endpointAuthScheme instanceof SigV4AuthScheme) { + SigV4AuthScheme v4AuthScheme = (SigV4AuthScheme) endpointAuthScheme; + if (v4AuthScheme.isDisableDoubleEncodingSet()) { + option.putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, !v4AuthScheme.disableDoubleEncoding()); + } + if (v4AuthScheme.signingRegion() != null) { + option.putSignerProperty(AwsV4HttpSigner.REGION_NAME, v4AuthScheme.signingRegion()); + } + if (v4AuthScheme.signingName() != null) { + option.putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, v4AuthScheme.signingName()); + } + return new SelectedAuthScheme<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), option.build()); + } + if (endpointAuthScheme instanceof SigV4aAuthScheme) { + SigV4aAuthScheme v4aAuthScheme = (SigV4aAuthScheme) endpointAuthScheme; + if (v4aAuthScheme.isDisableDoubleEncodingSet()) { + option.putSignerProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, !v4aAuthScheme.disableDoubleEncoding()); + } + if (v4aAuthScheme.signingRegionSet() != null) { + RegionSet regionSet = RegionSet.create(v4aAuthScheme.signingRegionSet()); + option.putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet); + } + if (v4aAuthScheme.signingName() != null) { + option.putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, v4aAuthScheme.signingName()); + } + return new SelectedAuthScheme<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), option.build()); + } + throw new IllegalArgumentException("Endpoint auth scheme '" + endpointAuthScheme.name() + + "' cannot be mapped to the SDK auth scheme. Was it declared in the service's model?"); + } + return selectedAuthScheme; + } + private static void setClientContextParams(QueryEndpointParams.Builder params, ExecutionAttributes executionAttributes) { AttributeMap clientContextParams = executionAttributes.getAttribute(SdkInternalExecutionAttribute.CLIENT_CONTEXT_PARAMS); Optional.ofNullable(clientContextParams.get(QueryClientContextParams.BOOLEAN_CONTEXT_PARAM)).ifPresent( diff --git a/core/annotations/pom.xml b/core/annotations/pom.xml index f62aff22f62e..e1c9f448d9bb 100644 --- a/core/annotations/pom.xml +++ b/core/annotations/pom.xml @@ -20,7 +20,7 @@ core software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/core/arns/pom.xml b/core/arns/pom.xml index be4935d1d15a..fe72c78804df 100644 --- a/core/arns/pom.xml +++ b/core/arns/pom.xml @@ -20,7 +20,7 @@ core software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/core/auth-crt/pom.xml b/core/auth-crt/pom.xml index 72b78f2c3be8..3f7ae12f9864 100644 --- a/core/auth-crt/pom.xml +++ b/core/auth-crt/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk core - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT auth-crt @@ -68,6 +68,13 @@ ${awscrt.version}
+ + + software.amazon.awssdk + http-auth-aws-crt + ${awsjavasdk.version} + + org.junit.jupiter junit-jupiter @@ -162,6 +169,18 @@ + + + org.apache.maven.plugins + maven-dependency-plugin + + + + software.amazon.awssdk:http-auth-aws-crt + + + diff --git a/core/auth/pom.xml b/core/auth/pom.xml index 74c4587eaafb..71140b68434b 100644 --- a/core/auth/pom.xml +++ b/core/auth/pom.xml @@ -22,15 +22,16 @@ software.amazon.awssdk core - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT auth AWS Java SDK :: Auth - The AWS SDK for Java - Auth module holds the classes that are used for authentication with AWS services + The AWS SDK for Java - Auth module holds the classes that are used for authentication with services https://aws.amazon.com/sdkforjava + software.amazon.awssdk @@ -47,6 +48,11 @@ sdk-core ${awsjavasdk.version} + + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + software.amazon.awssdk regions @@ -67,6 +73,21 @@ json-utils ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-spi + ${awsjavasdk.version} + software.amazon.eventstream eventstream @@ -163,6 +184,12 @@ ${jimfs.version} test + + software.amazon.awssdk + checksums + ${awsjavasdk.version} + test + diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsBasicCredentials.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsBasicCredentials.java index cf19e36fcab6..1c8b32cd928e 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsBasicCredentials.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsBasicCredentials.java @@ -25,12 +25,12 @@ import software.amazon.awssdk.utils.Validate; /** - * Provides access to the AWS credentials used for accessing AWS services: AWS access key ID and secret access key. These - * credentials are used to securely sign requests to AWS services. + * Provides access to the AWS credentials used for accessing services: AWS access key ID and secret access key. These + * credentials are used to securely sign requests to services (e.g., AWS services) that use them for authentication. * *

For more details on AWS access keys, see: - * - * http://docs.amazonwebservices.com/AWSSecurityCredentials/1.0/AboutAWSCredentials.html#AccessKeys

+ * + * https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys

* * @see AwsCredentialsProvider */ diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentials.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentials.java index b907d77a892a..2d29eef2808f 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentials.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentials.java @@ -16,27 +16,18 @@ package software.amazon.awssdk.auth.credentials; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; /** - * Provides access to the AWS credentials used for accessing AWS services: AWS access key ID and secret access key. These - * credentials are used to securely sign requests to AWS services. + * Provides access to the AWS credentials used for accessing services: AWS access key ID and secret access key. These + * credentials are used to securely sign requests to services (e.g., AWS services) that use them for authentication. * *

For more details on AWS access keys, see: - * - * http://docs.amazonwebservices.com/AWSSecurityCredentials/1.0/AboutAWSCredentials.html#AccessKeys

+ * + * https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys

* * @see AwsCredentialsProvider */ @SdkPublicApi -public interface AwsCredentials { - - /** - * Retrieve the AWS access key, used to identify the user interacting with AWS. - */ - String accessKeyId(); - - /** - * Retrieve the AWS secret access key, used to authenticate the user interacting with AWS. - */ - String secretAccessKey(); +public interface AwsCredentials extends AwsCredentialsIdentity { } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProvider.java index 3c797cf9905c..e3dec9e5440d 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProvider.java @@ -15,7 +15,11 @@ package software.amazon.awssdk.auth.credentials; +import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.ResolveIdentityRequest; /** * Interface for loading {@link AwsCredentials} that are used for authentication. @@ -27,7 +31,7 @@ */ @FunctionalInterface @SdkPublicApi -public interface AwsCredentialsProvider { +public interface AwsCredentialsProvider extends IdentityProvider { /** * Returns {@link AwsCredentials} that can be used to authorize an AWS request. Each implementation of AWSCredentialsProvider * can choose its own strategy for loading credentials. For example, an implementation might load credentials from an existing @@ -39,4 +43,14 @@ public interface AwsCredentialsProvider { * @return AwsCredentials which the caller can use to authorize an AWS request. */ AwsCredentials resolveCredentials(); + + @Override + default Class identityType() { + return AwsCredentialsIdentity.class; + } + + @Override + default CompletableFuture resolveIdentity(ResolveIdentityRequest request) { + return CompletableFuture.completedFuture(resolveCredentials()); + } } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProviderChain.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProviderChain.java index 3dd4a4ae4011..439fcc987406 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProviderChain.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProviderChain.java @@ -22,6 +22,9 @@ import java.util.List; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.IoUtils; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.SdkAutoCloseable; @@ -52,11 +55,11 @@ public final class AwsCredentialsProviderChain ToCopyableBuilder { private static final Logger log = Logger.loggerFor(AwsCredentialsProviderChain.class); - private final List credentialsProviders; + private final List> credentialsProviders; private final boolean reuseLastProviderEnabled; - private volatile AwsCredentialsProvider lastUsedProvider; + private volatile IdentityProvider lastUsedProvider; /** * @see #builder() @@ -84,21 +87,31 @@ public static AwsCredentialsProviderChain of(AwsCredentialsProvider... awsCreden return builder().credentialsProviders(awsCredentialsProviders).build(); } + /** + * Create an AWS credentials provider chain with default configuration that checks the given credential providers. + * @param awsCredentialsProviders The credentials providers that should be checked for credentials, in the order they should + * be checked. + * @return A credential provider chain that checks the provided credential providers in order. + */ + public static AwsCredentialsProviderChain of(IdentityProvider... awsCredentialsProviders) { + return builder().credentialsProviders(awsCredentialsProviders).build(); + } + @Override public AwsCredentials resolveCredentials() { if (reuseLastProviderEnabled && lastUsedProvider != null) { - return lastUsedProvider.resolveCredentials(); + return CredentialUtils.toCredentials(CompletableFutureUtils.joinLikeSync(lastUsedProvider.resolveIdentity())); } List exceptionMessages = null; - for (AwsCredentialsProvider provider : credentialsProviders) { + for (IdentityProvider provider : credentialsProviders) { try { - AwsCredentials credentials = provider.resolveCredentials(); + AwsCredentialsIdentity credentials = CompletableFutureUtils.joinLikeSync(provider.resolveIdentity()); log.debug(() -> "Loading credentials from " + provider); lastUsedProvider = provider; - return credentials; + return CredentialUtils.toCredentials(credentials); } catch (RuntimeException e) { // Ignore any exceptions and move onto the next provider String message = provider + ": " + e.getMessage(); @@ -156,19 +169,43 @@ public interface Builder extends CopyableBuilder> credentialsProviders); + + /** + * Configure the credentials providers that should be checked for credentials, in the order they should be checked. + */ + default Builder credentialsProviders(AwsCredentialsProvider... credentialsProviders) { + return credentialsProviders((IdentityProvider[]) credentialsProviders); + } + + /** + * Configure the credentials providers that should be checked for credentials, in the order they should be checked. + */ + default Builder credentialsProviders(IdentityProvider... credentialsProviders) { + throw new UnsupportedOperationException(); + } + + /** + * Add a credential provider to the chain, after the credential providers that have already been configured. + */ + default Builder addCredentialsProvider(AwsCredentialsProvider credentialsProvider) { + return addCredentialsProvider((IdentityProvider) credentialsProvider); + } /** * Add a credential provider to the chain, after the credential providers that have already been configured. */ - Builder addCredentialsProvider(AwsCredentialsProvider credentialsProviders); + default Builder addCredentialsProvider(IdentityProvider credentialsProvider) { + throw new UnsupportedOperationException(); + } AwsCredentialsProviderChain build(); } private static final class BuilderImpl implements Builder { private Boolean reuseLastProviderEnabled = true; - private List credentialsProviders = new ArrayList<>(); + private List> credentialsProviders = new ArrayList<>(); private BuilderImpl() { } @@ -199,13 +236,25 @@ public void setCredentialsProviders(Collection } @Override - public Builder credentialsProviders(AwsCredentialsProvider... credentialsProviders) { - return credentialsProviders(Arrays.asList(credentialsProviders)); + public Builder credentialsIdentityProviders( + Collection> credentialsProviders) { + this.credentialsProviders = new ArrayList<>(credentialsProviders); + return this; + } + + public void setCredentialsIdentityProviders( + Collection> credentialsProviders) { + credentialsIdentityProviders(credentialsProviders); + } + + @Override + public Builder credentialsProviders(IdentityProvider... credentialsProviders) { + return credentialsIdentityProviders(Arrays.asList(credentialsProviders)); } @Override - public Builder addCredentialsProvider(AwsCredentialsProvider credentialsProviders) { - this.credentialsProviders.add(credentialsProviders); + public Builder addCredentialsProvider(IdentityProvider credentialsProvider) { + this.credentialsProviders.add(credentialsProvider); return this; } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsSessionCredentials.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsSessionCredentials.java index de90e393708f..a63c3e9e7b00 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsSessionCredentials.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsSessionCredentials.java @@ -20,6 +20,7 @@ import java.util.Optional; import software.amazon.awssdk.annotations.Immutable; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.Validate; @@ -30,7 +31,7 @@ */ @Immutable @SdkPublicApi -public final class AwsSessionCredentials implements AwsCredentials { +public final class AwsSessionCredentials implements AwsCredentials, AwsSessionCredentialsIdentity { private final String accessKeyId; private final String secretAccessKey; @@ -83,6 +84,7 @@ public String secretAccessKey() { /** * Retrieve the expiration time of these credentials, if it exists. */ + @Override public Optional expirationTime() { return Optional.ofNullable(expirationTime); } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/CredentialUtils.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/CredentialUtils.java index 6e4879b59c2a..ced5d9a5ccff 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/CredentialUtils.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/CredentialUtils.java @@ -16,6 +16,10 @@ package software.amazon.awssdk.auth.credentials; import software.amazon.awssdk.annotations.SdkProtectedApi; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.utils.CompletableFutureUtils; @SdkProtectedApi public final class CredentialUtils { @@ -28,6 +32,79 @@ private CredentialUtils() { * authenticate themselves. */ public static boolean isAnonymous(AwsCredentials credentials) { + return isAnonymous((AwsCredentialsIdentity) credentials); + } + + /** + * Determine whether the provided credentials are anonymous credentials, indicating that the customer is not attempting to + * authenticate themselves. + */ + public static boolean isAnonymous(AwsCredentialsIdentity credentials) { return credentials.secretAccessKey() == null && credentials.accessKeyId() == null; } + + /** + * Converts an {@link AwsCredentialsIdentity} to {@link AwsCredentials}. + * + *

Usage of the new AwsCredentialsIdentity type is preferred over AwsCredentials. But some places may need to still + * convert to the older AwsCredentials type to work with existing code.

+ * + *

The conversion is only aware of {@link AwsCredentialsIdentity} and {@link AwsSessionCredentialsIdentity} types. If the + * input is another sub-type that has other properties, they are not carried over. i.e., + *

    + *
  • AwsSessionCredentialsIdentity -> AwsSessionCredentials
  • + *
  • AwsCredentialsIdentity -> AwsBasicCredentials
  • + *
+ *

+ * + * @param awsCredentialsIdentity The {@link AwsCredentialsIdentity} to convert + * @return The corresponding {@link AwsCredentials} + */ + public static AwsCredentials toCredentials(AwsCredentialsIdentity awsCredentialsIdentity) { + if (awsCredentialsIdentity == null) { + return null; + } + if (awsCredentialsIdentity instanceof AwsCredentials) { + return (AwsCredentials) awsCredentialsIdentity; + } + + // identity-spi defines 2 known types - AwsCredentialsIdentity and a sub-type AwsSessionCredentialsIdentity + if (awsCredentialsIdentity instanceof AwsSessionCredentialsIdentity) { + AwsSessionCredentialsIdentity awsSessionCredentialsIdentity = (AwsSessionCredentialsIdentity) awsCredentialsIdentity; + return AwsSessionCredentials.create(awsSessionCredentialsIdentity.accessKeyId(), + awsSessionCredentialsIdentity.secretAccessKey(), + awsSessionCredentialsIdentity.sessionToken()); + } + if (isAnonymous(awsCredentialsIdentity)) { + return AwsBasicCredentials.ANONYMOUS_CREDENTIALS; + } + return AwsBasicCredentials.create(awsCredentialsIdentity.accessKeyId(), + awsCredentialsIdentity.secretAccessKey()); + } + + /** + * Converts an {@link IdentityProvider} to {@link AwsCredentialsProvider} based on + * {@link #toCredentials(AwsCredentialsIdentity)}. + * + *

Usage of the new IdentityProvider type is preferred over AwsCredentialsProvider. But some places may need to still + * convert to the older AwsCredentialsProvider type to work with existing code. + *

+ * + * @param identityProvider The {@link IdentityProvider} to convert + * @return The corresponding {@link AwsCredentialsProvider} + */ + public static AwsCredentialsProvider toCredentialsProvider( + IdentityProvider identityProvider) { + if (identityProvider == null) { + return null; + } + if (identityProvider instanceof AwsCredentialsProvider) { + return (AwsCredentialsProvider) identityProvider; + } + return () -> { + AwsCredentialsIdentity awsCredentialsIdentity = + CompletableFutureUtils.joinLikeSync(identityProvider.resolveIdentity()); + return toCredentials(awsCredentialsIdentity); + }; + } } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/AwsSignerExecutionAttribute.java b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/AwsSignerExecutionAttribute.java index a90eae258660..a4837f27ed7b 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/AwsSignerExecutionAttribute.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/AwsSignerExecutionAttribute.java @@ -15,69 +15,464 @@ package software.amazon.awssdk.auth.signer; +import static software.amazon.awssdk.utils.CompletableFutureUtils.joinLikeSync; + import java.time.Clock; +import java.time.Duration; import java.time.Instant; +import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.annotations.SdkProtectedApi; +import software.amazon.awssdk.annotations.SdkTestInternalApi; import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.CredentialUtils; import software.amazon.awssdk.auth.signer.params.Aws4SignerParams; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.interceptor.ExecutionAttribute; 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.signer.Signer; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.Identity; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.RegionScope; +import software.amazon.awssdk.utils.CompletableFutureUtils; /** * AWS-specific signing attributes attached to the execution. This information is available to {@link ExecutionInterceptor}s and * {@link Signer}s. + * + * @deprecated Signer execution attributes have been deprecated in favor of signer properties, set on the auth scheme's signer + * option. */ +@Deprecated @SdkProtectedApi public final class AwsSignerExecutionAttribute extends SdkExecutionAttribute { /** * The key under which the request credentials are set. + * + * @deprecated This is a protected class that is internal to the SDK, so you shouldn't be using it. If you are using it + * from execution interceptors, you should instead be overriding the credential provider via the {@code SdkRequest}'s + * {@code overrideConfiguration.credentialsProvider}. If you're using it to call the SDK's signers, you should migrate to a + * subtype of {@code HttpSigner}. */ - public static final ExecutionAttribute AWS_CREDENTIALS = new ExecutionAttribute<>("AwsCredentials"); + @Deprecated + public static final ExecutionAttribute AWS_CREDENTIALS = + ExecutionAttribute.derivedBuilder("AwsCredentials", + AwsCredentials.class, + SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .readMapping(AwsSignerExecutionAttribute::awsCredentialsReadMapping) + .writeMapping(AwsSignerExecutionAttribute::awsCredentialsWriteMapping) + .build(); /** * The AWS {@link Region} that is used for signing a request. This is not always same as the region configured on the client * for global services like IAM. + * + * @deprecated This is a protected class that is internal to the SDK, so you shouldn't be using it. If you are using it + * from execution interceptors, you should instead be overriding the signing region via the {@code AuthSchemeProvider} that + * is configured on the SDK client builder. If you're using it to call the SDK's signers, you should migrate to a + * subtype of {@code HttpSigner}. */ - public static final ExecutionAttribute SIGNING_REGION = new ExecutionAttribute<>("SigningRegion"); + @Deprecated + public static final ExecutionAttribute SIGNING_REGION = + ExecutionAttribute.derivedBuilder("SigningRegion", + Region.class, + SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .readMapping(AwsSignerExecutionAttribute::signingRegionReadMapping) + .writeMapping(AwsSignerExecutionAttribute::signingRegionWriteMapping) + .build(); /** * The AWS {@link Region} that is used for signing a request. This is not always same as the region configured on the client * for global services like IAM. + * + * @deprecated This is a protected class that is internal to the SDK, so you shouldn't be using it. If you are using it + * from execution interceptors, you should instead be overriding the signing region scope via the {@code AuthSchemeProvider} + * that is configured on the SDK client builder. If you're using it to call the SDK's signers, you should migrate to a + * subtype of {@code HttpSigner}. */ - public static final ExecutionAttribute SIGNING_REGION_SCOPE = new ExecutionAttribute<>("SigningRegionScope"); + @Deprecated + public static final ExecutionAttribute SIGNING_REGION_SCOPE = + ExecutionAttribute.derivedBuilder("SigningRegionScope", + RegionScope.class, + SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .readMapping(AwsSignerExecutionAttribute::signingRegionScopeReadMapping) + .writeMapping(AwsSignerExecutionAttribute::signingRegionScopeWriteMapping) + .build(); /** * The signing name of the service to be using in SigV4 signing + * + * @deprecated This is a protected class that is internal to the SDK, so you shouldn't be using it. If you are using it + * from execution interceptors, you should instead be overriding the signing region name via the {@code AuthSchemeProvider} + * that is configured on the SDK client builder. If you're using it to call the SDK's signers, you should migrate to a + * subtype of {@code HttpSigner}. */ - public static final ExecutionAttribute SERVICE_SIGNING_NAME = new ExecutionAttribute<>("ServiceSigningName"); + @Deprecated + public static final ExecutionAttribute SERVICE_SIGNING_NAME = + ExecutionAttribute.derivedBuilder("ServiceSigningName", + String.class, + SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .readMapping(AwsSignerExecutionAttribute::serviceSigningNameReadMapping) + .writeMapping(AwsSignerExecutionAttribute::serviceSigningNameWriteMapping) + .build(); /** * The key to specify whether to use double url encoding during signing. + * + * @deprecated This is a protected class that is internal to the SDK, so you shouldn't be using it. If you are using it + * from execution interceptors, you should instead be overriding the double-url-encode setting via the {@code + * AuthSchemeProvider} that is configured on the SDK client builder. If you're using it to call the SDK's signers, you + * should migrate to a subtype of {@code HttpSigner}. */ - public static final ExecutionAttribute SIGNER_DOUBLE_URL_ENCODE = new ExecutionAttribute<>("DoubleUrlEncode"); + @Deprecated + public static final ExecutionAttribute SIGNER_DOUBLE_URL_ENCODE = + ExecutionAttribute.derivedBuilder("DoubleUrlEncode", + Boolean.class, + SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .readMapping(AwsSignerExecutionAttribute::signerDoubleUrlEncodeReadMapping) + .writeMapping(AwsSignerExecutionAttribute::signerDoubleUrlEncodeWriteMapping) + .build(); /** * The key to specify whether to normalize the resource path during signing. + * + * @deprecated This is a protected class that is internal to the SDK, so you shouldn't be using it. If you are using it + * from execution interceptors, you should instead be overriding the normalize-path setting via the {@code + * AuthSchemeProvider} that is configured on the SDK client builder. If you're using it to call the SDK's signers, you + * should migrate to a subtype of {@code HttpSigner}. */ + @Deprecated public static final ExecutionAttribute SIGNER_NORMALIZE_PATH = - new ExecutionAttribute<>("NormalizePath"); + ExecutionAttribute.derivedBuilder("NormalizePath", + Boolean.class, + SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .readMapping(AwsSignerExecutionAttribute::signerNormalizePathReadMapping) + .writeMapping(AwsSignerExecutionAttribute::signerNormalizePathWriteMapping) + .build(); /** * An override clock to use during signing. * @see Aws4SignerParams.Builder#signingClockOverride(Clock) + * + * @deprecated This is a protected class that is internal to the SDK, so you shouldn't be using it. If you are using it + * from execution interceptors, you should instead be overriding the clock setting via the {@code + * AuthSchemeProvider} that is configured on the SDK client builder. If you're using it to call the SDK's signers, you + * should migrate to a subtype of {@code HttpSigner}. */ - public static final ExecutionAttribute SIGNING_CLOCK = new ExecutionAttribute<>("Clock"); + @Deprecated + public static final ExecutionAttribute SIGNING_CLOCK = + ExecutionAttribute.derivedBuilder("Clock", + Clock.class, + SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .readMapping(AwsSignerExecutionAttribute::signingClockReadMapping) + .writeMapping(AwsSignerExecutionAttribute::signingClockWriteMapping) + .build(); /** * The key to specify the expiration time when pre-signing aws requests. + * + * @deprecated This is a protected class that is internal to the SDK, so you shouldn't be using it. If you are using it + * from execution interceptors, you should instead be overriding the expiration via the {@code AuthSchemeProvider} that is + * configured on the SDK client builder. If you're using it to call the SDK's signers, you should migrate to a subtype of + * {@code HttpSigner}. */ - public static final ExecutionAttribute PRESIGNER_EXPIRATION = new ExecutionAttribute<>("PresignerExpiration"); + @Deprecated + public static final ExecutionAttribute PRESIGNER_EXPIRATION = + ExecutionAttribute.derivedBuilder("PresignerExpiration", + Instant.class, + SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .readMapping(AwsSignerExecutionAttribute::presignerExpirationReadMapping) + .writeMapping(AwsSignerExecutionAttribute::presignerExpirationWriteMapping) + .build(); + + private static Clock presignerExpirationClock = Clock.systemUTC(); private AwsSignerExecutionAttribute() { } + + private static AwsCredentials awsCredentialsReadMapping(SelectedAuthScheme authScheme) { + if (authScheme == null) { + return null; + } + Identity identity = joinLikeSync(authScheme.identity()); + if (!(identity instanceof AwsCredentialsIdentity)) { + return null; + } + return CredentialUtils.toCredentials((AwsCredentialsIdentity) identity); + } + + private static SelectedAuthScheme awsCredentialsWriteMapping(SelectedAuthScheme authScheme, + AwsCredentials awsCredentials) { + if (authScheme == null) { + // This is an unusual use-case. + // Let's assume they're setting the credentials so that they can call the signer directly. If that's true, then it + // doesn't really matter what we store other than the credentials. + return new SelectedAuthScheme<>(CompletableFuture.completedFuture(awsCredentials), + AwsV4HttpSigner.create(), + AuthSchemeOption.builder() + .schemeId(AwsV4AuthScheme.SCHEME_ID) + .build()); + } + + return new SelectedAuthScheme<>(CompletableFuture.completedFuture((T) awsCredentials), + authScheme.signer(), + authScheme.authSchemeOption()); + } + + private static Region signingRegionReadMapping(SelectedAuthScheme authScheme) { + if (authScheme == null) { + return null; + } + String regionName = authScheme.authSchemeOption().signerProperty(AwsV4HttpSigner.REGION_NAME); + if (regionName == null) { + return null; + } + return Region.of(regionName); + } + + private static SelectedAuthScheme signingRegionWriteMapping(SelectedAuthScheme authScheme, + Region region) { + String regionString = region == null ? null : region.id(); + + if (authScheme == null) { + // This is an unusual use-case. + // Let's assume they're setting the region so that they can call the signer directly. If that's true, then it + // doesn't really matter what we store other than the region. + return new SelectedAuthScheme<>(CompletableFuture.completedFuture(new UnsetIdentity()), + new UnsetHttpSigner(), + AuthSchemeOption.builder() + .schemeId("unset") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, + regionString) + .build()); + } + + return new SelectedAuthScheme<>(authScheme.identity(), + authScheme.signer(), + authScheme.authSchemeOption().copy(o -> o.putSignerProperty(AwsV4HttpSigner.REGION_NAME, + regionString))); + } + + private static RegionScope signingRegionScopeReadMapping(SelectedAuthScheme authScheme) { + if (authScheme == null) { + return null; + } + RegionSet regionSet = authScheme.authSchemeOption().signerProperty(AwsV4aHttpSigner.REGION_SET); + if (regionSet == null || regionSet.asString().isEmpty()) { + return null; + } + + return RegionScope.create(regionSet.asString()); + } + + private static SelectedAuthScheme signingRegionScopeWriteMapping(SelectedAuthScheme authScheme, + RegionScope regionScope) { + RegionSet regionSet = regionScope != null ? RegionSet.create(regionScope.id()) : null; + + if (authScheme == null) { + // This is an unusual use-case. + // Let's assume they're setting the region scope so that they can call the signer directly. If that's true, then it + // doesn't really matter what we store other than the region scope. + return new SelectedAuthScheme<>(CompletableFuture.completedFuture(new UnsetIdentity()), + new UnsetHttpSigner(), + AuthSchemeOption.builder() + .schemeId("unset") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet) + .build()); + } + + return new SelectedAuthScheme<>(authScheme.identity(), + authScheme.signer(), + authScheme.authSchemeOption().copy(o -> o.putSignerProperty(AwsV4aHttpSigner.REGION_SET, + regionSet))); + } + + private static String serviceSigningNameReadMapping(SelectedAuthScheme authScheme) { + if (authScheme == null) { + return null; + } + return authScheme.authSchemeOption().signerProperty(AwsV4FamilyHttpSigner.SERVICE_SIGNING_NAME); + } + + private static SelectedAuthScheme serviceSigningNameWriteMapping(SelectedAuthScheme authScheme, + String signingName) { + if (authScheme == null) { + // This is an unusual use-case. + // Let's assume they're setting the signing name so that they can call the signer directly. If that's true, then it + // doesn't really matter what we store other than the signing name. + return new SelectedAuthScheme<>(CompletableFuture.completedFuture(new UnsetIdentity()), + new UnsetHttpSigner(), + AuthSchemeOption.builder() + .schemeId("unset") + .putSignerProperty(AwsV4FamilyHttpSigner.SERVICE_SIGNING_NAME, + signingName) + .build()); + } + + return new SelectedAuthScheme<>(authScheme.identity(), + authScheme.signer(), + authScheme.authSchemeOption() + .copy(o -> o.putSignerProperty(AwsV4FamilyHttpSigner.SERVICE_SIGNING_NAME, + signingName))); + } + + private static Boolean signerDoubleUrlEncodeReadMapping(SelectedAuthScheme authScheme) { + if (authScheme == null) { + return null; + } + AuthSchemeOption authOption = authScheme.authSchemeOption(); + return authOption.signerProperty(AwsV4FamilyHttpSigner.DOUBLE_URL_ENCODE); + } + + private static SelectedAuthScheme signerDoubleUrlEncodeWriteMapping(SelectedAuthScheme authScheme, + Boolean doubleUrlEncode) { + if (authScheme == null) { + // This is an unusual use-case. + // Let's assume they're setting double-url-encode so that they can call the signer directly. If that's true, then it + // doesn't really matter what we store other than double-url-encode. + return new SelectedAuthScheme<>(CompletableFuture.completedFuture(new UnsetIdentity()), + new UnsetHttpSigner(), + AuthSchemeOption.builder() + .schemeId("unset") + .putSignerProperty(AwsV4FamilyHttpSigner.DOUBLE_URL_ENCODE, + doubleUrlEncode) + .build()); + } + + return new SelectedAuthScheme<>(authScheme.identity(), + authScheme.signer(), + authScheme.authSchemeOption() + .copy(o -> o.putSignerProperty(AwsV4FamilyHttpSigner.DOUBLE_URL_ENCODE, + doubleUrlEncode))); + } + + private static Boolean signerNormalizePathReadMapping(SelectedAuthScheme authScheme) { + if (authScheme == null) { + return null; + } + AuthSchemeOption authOption = authScheme.authSchemeOption(); + return authOption.signerProperty(AwsV4FamilyHttpSigner.NORMALIZE_PATH); + } + + private static SelectedAuthScheme signerNormalizePathWriteMapping(SelectedAuthScheme authScheme, + Boolean normalizePath) { + if (authScheme == null) { + // This is an unusual use-case. + // Let's assume they're setting normalize-path so that they can call the signer directly. If that's true, then it + // doesn't really matter what we store other than normalize-path. + return new SelectedAuthScheme<>(CompletableFuture.completedFuture(new UnsetIdentity()), + new UnsetHttpSigner(), + AuthSchemeOption.builder() + .schemeId("unset") + .putSignerProperty(AwsV4FamilyHttpSigner.NORMALIZE_PATH, + normalizePath) + .build()); + } + + return new SelectedAuthScheme<>(authScheme.identity(), + authScheme.signer(), + authScheme.authSchemeOption() + .copy(o -> o.putSignerProperty(AwsV4FamilyHttpSigner.NORMALIZE_PATH, + normalizePath))); + } + + private static Clock signingClockReadMapping(SelectedAuthScheme authScheme) { + if (authScheme == null) { + return null; + } + return authScheme.authSchemeOption().signerProperty(HttpSigner.SIGNING_CLOCK); + } + + private static SelectedAuthScheme signingClockWriteMapping(SelectedAuthScheme authScheme, + Clock clock) { + if (authScheme == null) { + // This is an unusual use-case. + // Let's assume they're setting signing clock so that they can call the signer directly. If that's true, then it + // doesn't really matter what we store other than the signing clock. + return new SelectedAuthScheme<>(CompletableFuture.completedFuture(new UnsetIdentity()), + new UnsetHttpSigner(), + AuthSchemeOption.builder() + .schemeId("unset") + .putSignerProperty(HttpSigner.SIGNING_CLOCK, clock) + .build()); + } + + return new SelectedAuthScheme<>(authScheme.identity(), + authScheme.signer(), + authScheme.authSchemeOption() + .copy(o -> o.putSignerProperty(HttpSigner.SIGNING_CLOCK, clock))); + } + + @SdkTestInternalApi + static void presignerExpirationClock(Clock clock) { + presignerExpirationClock = clock; + } + + private static Instant presignerExpirationReadMapping(SelectedAuthScheme authScheme) { + if (authScheme == null) { + return null; + } + Duration expirationDuration = authScheme.authSchemeOption().signerProperty(AwsV4FamilyHttpSigner.EXPIRATION_DURATION); + if (expirationDuration == null) { + return null; + } + + // This is kind of weird, since reading the value twice will give different values. That seems very unlikely to cause + // issues, though. + return presignerExpirationClock.instant().plus(expirationDuration); + } + + private static SelectedAuthScheme presignerExpirationWriteMapping(SelectedAuthScheme authScheme, + Instant expiration) { + Duration expirationDuration = expiration == null ? null + : Duration.between(presignerExpirationClock.instant(), expiration); + + if (authScheme == null) { + // This is an unusual use-case. + // Let's assume they're setting the expiration so that they can call the presigner directly. If that's true, then it + // doesn't really matter what we store other than the expiration. + return new SelectedAuthScheme<>(CompletableFuture.completedFuture(new UnsetIdentity()), + new UnsetHttpSigner(), + AuthSchemeOption.builder() + .schemeId("unset") + .putSignerProperty(AwsV4FamilyHttpSigner.EXPIRATION_DURATION, + expirationDuration) + .build()); + } + + return new SelectedAuthScheme<>(authScheme.identity(), + authScheme.signer(), + authScheme.authSchemeOption() + .copy(o -> o.putSignerProperty(AwsV4FamilyHttpSigner.EXPIRATION_DURATION, + expirationDuration))); + } + + private static class UnsetIdentity implements Identity { + } + + private static class UnsetHttpSigner implements HttpSigner { + @Override + public SignedRequest sign(SignRequest request) { + throw new IllegalStateException("A signer was not configured."); + } + + @Override + public CompletableFuture signAsync(AsyncSignRequest request) { + return CompletableFutureUtils.failedFuture(new IllegalStateException("A signer was not configured.")); + } + } } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/S3SignerExecutionAttribute.java b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/S3SignerExecutionAttribute.java index 69576d3d193f..e35c1f8d4f89 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/S3SignerExecutionAttribute.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/S3SignerExecutionAttribute.java @@ -15,26 +15,141 @@ package software.amazon.awssdk.auth.signer; +import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.annotations.SdkProtectedApi; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.interceptor.ExecutionAttribute; import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.utils.CompletableFutureUtils; /** * S3-specific signing attributes attached to the execution. + * + * @deprecated Signer execution attributes have been deprecated in favor of signer properties, set on the auth scheme's signer + * option. */ @SdkProtectedApi +@Deprecated public final class S3SignerExecutionAttribute extends SdkExecutionAttribute { - /** * The key to specify whether to enable chunked encoding or not + * + * @deprecated This is a protected class that is internal to the SDK, so you shouldn't be using it. If you are using it + * from execution interceptors, you should instead be overriding the chunk encoding setting via the {@code AuthSchemeProvider} + * that is configured on the SDK client builder. If you're using it to call the SDK's signers, you should migrate to a + * subtype of {@code HttpSigner}. */ - public static final ExecutionAttribute ENABLE_CHUNKED_ENCODING = new ExecutionAttribute<>("ChunkedEncoding"); + @Deprecated + public static final ExecutionAttribute ENABLE_CHUNKED_ENCODING = + ExecutionAttribute.derivedBuilder("ChunkedEncoding", + Boolean.class, + SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .readMapping(S3SignerExecutionAttribute::enableChunkedEncodingReadMapping) + .writeMapping(S3SignerExecutionAttribute::enableChunkedEncodingWriteMapping) + .build(); + /** * The key to specify whether to enable payload signing or not + * + * @deprecated This is a protected class that is internal to the SDK, so you shouldn't be using it. If you are using it + * from execution interceptors, you should instead be overriding the payload signing setting via the {@code + * AuthSchemeProvider} that is configured on the SDK client builder. If you're using it to call the SDK's signers, you + * should migrate to a subtype of {@code HttpSigner}. */ - public static final ExecutionAttribute ENABLE_PAYLOAD_SIGNING = new ExecutionAttribute<>("PayloadSigning"); + @Deprecated + public static final ExecutionAttribute ENABLE_PAYLOAD_SIGNING = + ExecutionAttribute.derivedBuilder("PayloadSigning", + Boolean.class, + SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .readMapping(S3SignerExecutionAttribute::enablePayloadSigningReadMapping) + .writeMapping(S3SignerExecutionAttribute::enablePayloadSigningWriteMapping) + .build(); private S3SignerExecutionAttribute() { } + + private static Boolean enableChunkedEncodingReadMapping(SelectedAuthScheme authScheme) { + if (authScheme == null) { + return null; + } + AuthSchemeOption authOption = authScheme.authSchemeOption(); + return authOption.signerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED); + } + + private static SelectedAuthScheme enableChunkedEncodingWriteMapping(SelectedAuthScheme authScheme, + Boolean enableChunkedEncoding) { + if (authScheme == null) { + // This is an unusual use-case. + // Let's assume they're setting chunked-encoding so that they can call the signer directly. If that's true, then it + // doesn't really matter what we store other than chunked-encoding. + return new SelectedAuthScheme<>(CompletableFuture.completedFuture(new UnsetIdentity()), + new UnsetHttpSigner(), + AuthSchemeOption.builder() + .schemeId("unset") + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, + enableChunkedEncoding) + .build()); + } + + return new SelectedAuthScheme<>(authScheme.identity(), + authScheme.signer(), + authScheme.authSchemeOption() + .copy(o -> o.putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, + enableChunkedEncoding))); + } + + private static Boolean enablePayloadSigningReadMapping(SelectedAuthScheme authScheme) { + if (authScheme == null) { + return null; + } + return authScheme.authSchemeOption().signerProperty(AwsV4FamilyHttpSigner.PAYLOAD_SIGNING_ENABLED); + } + + private static SelectedAuthScheme enablePayloadSigningWriteMapping(SelectedAuthScheme authScheme, + Boolean payloadSigningEnabled) { + if (authScheme == null) { + // This is an unusual use-case. + // Let's assume they're configuring payload signing so that they can call the signer directly. If that's true, then it + // doesn't really matter what we store other than the payload signing setting. + return new SelectedAuthScheme<>(CompletableFuture.completedFuture(new UnsetIdentity()), + new UnsetHttpSigner(), + AuthSchemeOption.builder() + .schemeId("unset") + .putSignerProperty(AwsV4FamilyHttpSigner.PAYLOAD_SIGNING_ENABLED, + payloadSigningEnabled) + .build()); + } + + return new SelectedAuthScheme<>(authScheme.identity(), + authScheme.signer(), + authScheme.authSchemeOption() + .copy(o -> o.putSignerProperty(AwsV4FamilyHttpSigner.PAYLOAD_SIGNING_ENABLED, + payloadSigningEnabled)) + ); + } + + private static class UnsetIdentity implements Identity { + } + + private static class UnsetHttpSigner implements HttpSigner { + @Override + public SignedRequest sign(SignRequest request) { + throw new IllegalStateException("A signer was not configured."); + } + + @Override + public CompletableFuture signAsync(AsyncSignRequest request) { + return CompletableFutureUtils.failedFuture(new IllegalStateException("A signer was not configured.")); + } + } } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/SdkToken.java b/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/SdkToken.java index 60676313fb88..ab4b02126e36 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/SdkToken.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/SdkToken.java @@ -15,34 +15,18 @@ package software.amazon.awssdk.auth.token.credentials; -import java.time.Instant; -import java.util.Optional; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.identity.spi.TokenIdentity; /** - * Provides token which is used to securely authorize requests to AWS services. - * A token is a string that the OAuth client uses to make requests to the resource server. + * Provides token which is used to securely authorize requests to services that use token based auth, e.g., OAuth. * - *

For more details on tokens, see: + *

For more details on OAuth tokens, see: * * https://oauth.net/2/access-tokens

* * @see SdkTokenProvider */ - @SdkPublicApi -public interface SdkToken { - - - /** - * Retrieves string field representing the literal token string. - * A token is a string that the OAuth client uses to make requests to the resource server. - */ - String token(); - - - /** - * Retrieves the time at which the token expires. - */ - Optional expirationTime(); +public interface SdkToken extends TokenIdentity { } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/SdkTokenProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/SdkTokenProvider.java index c4cad9f8b5a8..eed06129e48d 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/SdkTokenProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/SdkTokenProvider.java @@ -15,8 +15,11 @@ package software.amazon.awssdk.auth.token.credentials; +import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.annotations.SdkPublicApi; -import software.amazon.awssdk.auth.token.credentials.SdkToken; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.ResolveIdentityRequest; +import software.amazon.awssdk.identity.spi.TokenIdentity; /** * Interface for loading {@link SdkToken} that are used for authentication. @@ -24,7 +27,7 @@ */ @FunctionalInterface @SdkPublicApi -public interface SdkTokenProvider { +public interface SdkTokenProvider extends IdentityProvider { /** * Returns an {@link SdkToken} that can be used to authorize a request. Each implementation of SdkTokenProvider * can choose its own strategy for loading token. For example, an implementation might load token from an existing @@ -34,4 +37,14 @@ public interface SdkTokenProvider { * @return AwsToken which the caller can use to authorize an AWS request using token authorization for a request. */ SdkToken resolveToken(); + + @Override + default Class identityType() { + return TokenIdentity.class; + } + + @Override + default CompletableFuture resolveIdentity(ResolveIdentityRequest request) { + return CompletableFuture.completedFuture(resolveToken()); + } } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/SdkTokenProviderChain.java b/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/SdkTokenProviderChain.java index cc7272267014..66784dcca99b 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/SdkTokenProviderChain.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/SdkTokenProviderChain.java @@ -21,7 +21,11 @@ import java.util.Collections; import java.util.List; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.auth.token.credentials.internal.TokenUtils; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.IoUtils; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.SdkAutoCloseable; @@ -47,11 +51,11 @@ public final class SdkTokenProviderChain implements SdkTokenProvider, SdkAutoCloseable { private static final Logger log = Logger.loggerFor(SdkTokenProviderChain.class); - private final List sdkTokenProviders; + private final List> sdkTokenProviders; private final boolean reuseLastProviderEnabled; - private volatile SdkTokenProvider lastUsedProvider; + private volatile IdentityProvider lastUsedProvider; /** * @see #builder() @@ -70,7 +74,7 @@ public static Builder builder() { } /** - * Create an AWS token provider chain with default configuration that checks the given token providers. + * Create a token provider chain with default configuration that checks the given token providers. * @param sdkTokenProviders The token providers that should be checked for token, in the order they should * be checked. * @return A token provider chain that checks the provided token providers in order. @@ -79,21 +83,31 @@ public static SdkTokenProviderChain of(SdkTokenProvider... sdkTokenProviders) { return builder().tokenProviders(sdkTokenProviders).build(); } + /** + * Create a token provider chain with default configuration that checks the given token providers. + * @param sdkTokenProviders The token providers that should be checked for token, in the order they should + * be checked. + * @return A token provider chain that checks the provided token providers in order. + */ + public static SdkTokenProviderChain of(IdentityProvider... sdkTokenProviders) { + return builder().tokenProviders(sdkTokenProviders).build(); + } + @Override public SdkToken resolveToken() { if (reuseLastProviderEnabled && lastUsedProvider != null) { - return lastUsedProvider.resolveToken(); + return TokenUtils.toSdkToken(CompletableFutureUtils.joinLikeSync(lastUsedProvider.resolveIdentity())); } List exceptionMessages = null; - for (SdkTokenProvider provider : sdkTokenProviders) { + for (IdentityProvider provider : sdkTokenProviders) { try { - SdkToken token = provider.resolveToken(); + TokenIdentity token = CompletableFutureUtils.joinLikeSync(provider.resolveIdentity()); log.debug(() -> "Loading token from " + provider); lastUsedProvider = provider; - return token; + return TokenUtils.toSdkToken(token); } catch (RuntimeException e) { // Ignore any exceptions and move onto the next provider String message = provider + ": " + e.getMessage(); @@ -119,7 +133,7 @@ public void close() { @Override public String toString() { - return ToString.builder("AwsTokenProviderChain") + return ToString.builder("SdkTokenProviderChain") .add("tokenProviders", sdkTokenProviders) .build(); } @@ -146,19 +160,42 @@ public interface Builder { /** * Configure the token providers that should be checked for token, in the order they should be checked. */ - Builder tokenProviders(SdkTokenProvider... tokenProviders); + Builder tokenIdentityProviders(Collection> tokenProviders); + + /** + * Configure the token providers that should be checked for token, in the order they should be checked. + */ + default Builder tokenProviders(SdkTokenProvider... tokenProviders) { + return tokenProviders((IdentityProvider[]) tokenProviders); + } + + /** + * Configure the token providers that should be checked for token, in the order they should be checked. + */ + default Builder tokenProviders(IdentityProvider... tokenProviders) { + throw new UnsupportedOperationException(); + } + + /** + * Add a token provider to the chain, after the token providers that have already been configured. + */ + default Builder addTokenProvider(SdkTokenProvider tokenProvider) { + return addTokenProvider((IdentityProvider) tokenProvider); + } /** * Add a token provider to the chain, after the token providers that have already been configured. */ - Builder addTokenProvider(SdkTokenProvider tokenProviders); + default Builder addTokenProvider(IdentityProvider tokenProvider) { + throw new UnsupportedOperationException(); + } SdkTokenProviderChain build(); } private static final class BuilderImpl implements Builder { private Boolean reuseLastProviderEnabled = true; - private List tokenProviders = new ArrayList<>(); + private List> tokenProviders = new ArrayList<>(); private BuilderImpl() { } @@ -183,13 +220,23 @@ public void setTokenProviders(Collection tokenProvid tokenProviders(tokenProviders); } - public Builder tokenProviders(SdkTokenProvider... tokenProviders) { - return tokenProviders(Arrays.asList(tokenProviders)); + @Override + public Builder tokenIdentityProviders(Collection> tokenProviders) { + this.tokenProviders = new ArrayList<>(tokenProviders); + return this; + } + + public void setTokenIdentityProviders(Collection> tokenProviders) { + tokenIdentityProviders(tokenProviders); + } + + public Builder tokenProviders(IdentityProvider... tokenProvider) { + return tokenIdentityProviders(Arrays.asList(tokenProvider)); } @Override - public Builder addTokenProvider(SdkTokenProvider tokenProviders) { - this.tokenProviders.add(tokenProviders); + public Builder addTokenProvider(IdentityProvider tokenProvider) { + this.tokenProviders.add(tokenProvider); return this; } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/internal/TokenUtils.java b/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/internal/TokenUtils.java new file mode 100644 index 000000000000..0b170ff8f668 --- /dev/null +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/internal/TokenUtils.java @@ -0,0 +1,53 @@ +/* + * 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.auth.token.credentials.internal; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.token.credentials.SdkToken; +import software.amazon.awssdk.identity.spi.TokenIdentity; + +// TODO(sra-identity-and-auth): Delete this once more SRA work is done where signers are using the new Identity types and this +// conversion is not necessary. +@SdkInternalApi +public class TokenUtils { + + private TokenUtils() { + } + + /** + * Converts an {@link TokenIdentity} to {@link SdkToken}. + * + *

Usage of the new TokenIdentity type is preferred over SdkToken. But some places may need to still + * convert to the older SdkToken type to work with existing code.

+ * + *

The conversion is only aware of {@link TokenIdentity} interface. If the input is another sub-type that has other + * properties, they are not carried over. + *

+ * + * @param tokenIdentity The {@link TokenIdentity} to convert + * @return The corresponding {@link SdkToken} + */ + + public static SdkToken toSdkToken(TokenIdentity tokenIdentity) { + if (tokenIdentity == null) { + return null; + } + if (tokenIdentity instanceof SdkToken) { + return (SdkToken) tokenIdentity; + } + return () -> tokenIdentity.token(); + } +} diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/token/signer/SdkTokenExecutionAttribute.java b/core/auth/src/main/java/software/amazon/awssdk/auth/token/signer/SdkTokenExecutionAttribute.java index 0352a61f19b8..29ad99b331a0 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/token/signer/SdkTokenExecutionAttribute.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/token/signer/SdkTokenExecutionAttribute.java @@ -15,21 +15,73 @@ package software.amazon.awssdk.auth.token.signer; +import static software.amazon.awssdk.utils.CompletableFutureUtils.joinLikeSync; + +import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.auth.token.credentials.SdkToken; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.interceptor.ExecutionAttribute; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; +import software.amazon.awssdk.http.auth.signer.BearerHttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.identity.spi.Identity; /** * SdkToken authorizing attributes attached to the execution. + * + * @deprecated Signer execution attributes have been deprecated in favor of signer properties, set on the auth scheme's signer + * options. */ +@Deprecated @SdkProtectedApi public final class SdkTokenExecutionAttribute { /** * The token to sign requests using token authorization instead of AWS Credentials. + * + * @deprecated This is a protected class that is internal to the SDK, so you shouldn't be using it. */ - public static final ExecutionAttribute SDK_TOKEN = new ExecutionAttribute<>("SdkToken"); + @Deprecated + public static final ExecutionAttribute SDK_TOKEN = + ExecutionAttribute.derivedBuilder("SdkToken", + SdkToken.class, + SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .readMapping(SdkTokenExecutionAttribute::sdkTokenReadMapping) + .writeMapping(SdkTokenExecutionAttribute::sdkTokenWriteMapping) + .build(); private SdkTokenExecutionAttribute() { } + + + private static SdkToken sdkTokenReadMapping(SelectedAuthScheme authScheme) { + if (authScheme == null) { + return null; + } + Identity identity = joinLikeSync(authScheme.identity()); + if (!(identity instanceof SdkToken)) { + return null; + } + return (SdkToken) identity; + } + + private static SelectedAuthScheme sdkTokenWriteMapping(SelectedAuthScheme authScheme, + SdkToken token) { + if (authScheme == null) { + // This is an unusual use-case. + // Let's assume they're setting the token so that they can call the signer directly. If that's true, then it + // doesn't really matter what we store other than the credentials. + return new SelectedAuthScheme<>(CompletableFuture.completedFuture(token), + BearerHttpSigner.create(), + AuthSchemeOption.builder() + .schemeId(BearerAuthScheme.SCHEME_ID) + .build()); + } + + return new SelectedAuthScheme<>(CompletableFuture.completedFuture((T) token), + authScheme.signer(), + authScheme.authSchemeOption()); + } } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/token/signer/aws/BearerTokenSigner.java b/core/auth/src/main/java/software/amazon/awssdk/auth/token/signer/aws/BearerTokenSigner.java index 5c90a6dc8a02..f150ac057fe1 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/token/signer/aws/BearerTokenSigner.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/token/signer/aws/BearerTokenSigner.java @@ -79,4 +79,4 @@ private SdkHttpFullRequest doSign(SdkHttpFullRequest request, TokenSignerParams private String buildAuthorizationHeader(SdkToken token) { return String.format("%s %s", BEARER_LABEL, token.token()); } -} \ No newline at end of file +} diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProviderChainTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProviderChainTest.java index ea8b688e3161..8a59ad26c15c 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProviderChainTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/AwsCredentialsProviderChainTest.java @@ -15,27 +15,28 @@ package software.amazon.awssdk.auth.credentials; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; -import org.junit.Rule; +import java.util.Arrays; import org.junit.Test; -import org.junit.rules.ExpectedException; +import org.junit.jupiter.api.function.Executable; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.utils.StringInputStream; public class AwsCredentialsProviderChainTest { - @Rule - public ExpectedException thrown = ExpectedException.none(); - /** * Tests that, by default, the chain remembers which provider was able to * provide credentials, and only calls that provider for any additional * calls to getCredentials. */ @Test - public void testReusingLastProvider() throws Exception { + public void resolveCredentials_reuseEnabled_reusesLastProvider() throws Exception { MockCredentialsProvider provider1 = new MockCredentialsProvider("Failed!"); MockCredentialsProvider provider2 = new MockCredentialsProvider(); AwsCredentialsProviderChain chain = AwsCredentialsProviderChain.builder() @@ -64,7 +65,7 @@ public void testReusingLastProvider() throws Exception { * provider that can return credentials. */ @Test - public void testDisableReusingLastProvider() throws Exception { + public void resolveCredentials_reuseDisabled_alwaysGoesThroughChain() throws Exception { MockCredentialsProvider provider1 = new MockCredentialsProvider("Failed!"); MockCredentialsProvider provider2 = new MockCredentialsProvider(); AwsCredentialsProviderChain chain = AwsCredentialsProviderChain.builder() @@ -85,7 +86,7 @@ public void testDisableReusingLastProvider() throws Exception { } @Test - public void testMissingProfileUsesNextProvider() { + public void resolveCredentials_missingProfile_usesNextProvider() { ProfileCredentialsProvider provider = new ProfileCredentialsProvider.BuilderImpl() .defaultProfileFileLoader(() -> ProfileFile.builder() @@ -103,24 +104,73 @@ public void testMissingProfileUsesNextProvider() { } /** - * Tests that getCredentials throws an thrown if all providers in the + * Tests that resolveCredentials throws an thrown if all providers in the * chain fail to provide credentials. */ @Test - public void testGetCredentialsException() { + public void resolveCredentials_allProvidersFail_throwsExceptionWithMessageFromAllProviders() { MockCredentialsProvider provider1 = new MockCredentialsProvider("Failed!"); MockCredentialsProvider provider2 = new MockCredentialsProvider("Bad!"); AwsCredentialsProviderChain chain = AwsCredentialsProviderChain.builder() .credentialsProviders(provider1, provider2) .build(); - thrown.expect(SdkClientException.class); - thrown.expectMessage(provider1.exceptionMessage); - thrown.expectMessage(provider2.exceptionMessage); + SdkClientException e = assertThrows(SdkClientException.class, () -> chain.resolveCredentials()); + assertThat(e.getMessage()).contains(provider1.exceptionMessage); + assertThat(e.getMessage()).contains(provider2.exceptionMessage); + } - chain.resolveCredentials(); + @Test + public void resolveCredentials_emptyChain_throwsException() { + assertThrowsIllegalArgument(() -> AwsCredentialsProviderChain.of()); + + assertThrowsIllegalArgument(() -> AwsCredentialsProviderChain + .builder() + .credentialsProviders() + .build()); + + assertThrowsIllegalArgument(() -> AwsCredentialsProviderChain + .builder() + .credentialsProviders(Arrays.asList()) + .build()); } + private void assertThrowsIllegalArgument(Executable executable) { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, executable); + assertThat(e.getMessage()).contains("No credential providers were specified."); + } + + /** + * Tests that the chain is setup correctly with the overloaded methods that accept the AwsCredentialsProvider type. + */ + @Test + public void createMethods_withOldCredentialsType_work() { + AwsCredentialsProvider provider = StaticCredentialsProvider.create(AwsBasicCredentials.create( + "accessKey", "secretKey")); + assertChainResolvesCorrectly(AwsCredentialsProviderChain.of(provider)); + assertChainResolvesCorrectly(AwsCredentialsProviderChain.builder().credentialsProviders(provider).build()); + assertChainResolvesCorrectly(AwsCredentialsProviderChain.builder().credentialsProviders(Arrays.asList(provider)).build()); + assertChainResolvesCorrectly(AwsCredentialsProviderChain.builder().addCredentialsProvider(provider).build()); + } + + /** + * Tests that the chain is setup correctly with the overloaded methods that accept the IdentityProvider type. + */ + @Test + public void createMethods_withNewCredentialsType_work() { + IdentityProvider provider = StaticCredentialsProvider.create(AwsBasicCredentials.create( + "accessKey", "secretKey")); + assertChainResolvesCorrectly(AwsCredentialsProviderChain.of(provider)); + assertChainResolvesCorrectly(AwsCredentialsProviderChain.builder().credentialsProviders(provider).build()); + assertChainResolvesCorrectly(AwsCredentialsProviderChain.builder().credentialsIdentityProviders(Arrays.asList(provider)).build()); + assertChainResolvesCorrectly(AwsCredentialsProviderChain.builder().addCredentialsProvider(provider).build()); + } + + private static void assertChainResolvesCorrectly(AwsCredentialsProviderChain chain) { + AwsCredentials credentials = chain.resolveCredentials(); + assertThat(credentials.accessKeyId()).isEqualTo("accessKey"); + assertThat(credentials.secretAccessKey()).isEqualTo("secretKey"); + } private static final class MockCredentialsProvider implements AwsCredentialsProvider { private final StaticCredentialsProvider staticCredentialsProvider; diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/CredentialUtilsTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/CredentialUtilsTest.java new file mode 100644 index 000000000000..0eaa6508ee98 --- /dev/null +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/CredentialUtilsTest.java @@ -0,0 +1,126 @@ +/* + * 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.auth.credentials; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; + +public class CredentialUtilsTest { + + @Test + public void isAnonymous_AwsCredentials_true() { + assertThat(CredentialUtils.isAnonymous(AwsBasicCredentials.ANONYMOUS_CREDENTIALS)).isTrue(); + } + + @Test + public void isAnonymous_AwsCredentials_false() { + assertThat(CredentialUtils.isAnonymous(AwsBasicCredentials.create("akid", "skid"))).isFalse(); + } + + @Test + public void isAnonymous_AwsCredentialsIdentity_true() { + assertThat(CredentialUtils.isAnonymous((AwsCredentialsIdentity) AwsBasicCredentials.ANONYMOUS_CREDENTIALS)).isTrue(); + } + + @Test + public void isAnonymous_AwsCredentialsIdentity_false() { + assertThat(CredentialUtils.isAnonymous(AwsCredentialsIdentity.create("akid", "skid"))).isFalse(); + } + + @Test + public void toCredentials_null_returnsNull() { + assertThat(CredentialUtils.toCredentials(null)).isNull(); + } + + + @Test + public void toCredentials_AwsSessionCredentials_doesNotCreateNewObject() { + AwsSessionCredentialsIdentity input = AwsSessionCredentials.create("ak", "sk", "session"); + AwsCredentials output = CredentialUtils.toCredentials(input); + assertThat(output).isSameAs(input); + } + + @Test + public void toCredentials_AwsSessionCredentialsIdentity_returnsAwsSessionCredentials() { + AwsCredentials awsCredentials = CredentialUtils.toCredentials(AwsSessionCredentialsIdentity.create( + "akid", "skid", "session")); + + assertThat(awsCredentials).isInstanceOf(AwsSessionCredentials.class); + AwsSessionCredentials awsSessionCredentials = (AwsSessionCredentials) awsCredentials; + assertThat(awsSessionCredentials.accessKeyId()).isEqualTo("akid"); + assertThat(awsSessionCredentials.secretAccessKey()).isEqualTo("skid"); + assertThat(awsSessionCredentials.sessionToken()).isEqualTo("session"); + } + + @Test + public void toCredentials_AwsCredentials_returnsAsIs() { + AwsCredentialsIdentity input = AwsBasicCredentials.create("ak", "sk"); + AwsCredentials output = CredentialUtils.toCredentials(input); + assertThat(output).isSameAs(input); + } + + @Test + public void toCredentials_AwsCredentialsIdentity_returnsAwsCredentials() { + AwsCredentials awsCredentials = CredentialUtils.toCredentials(AwsCredentialsIdentity.create("akid", "skid")); + + assertThat(awsCredentials.accessKeyId()).isEqualTo("akid"); + assertThat(awsCredentials.secretAccessKey()).isEqualTo("skid"); + } + + @Test + public void toCredentials_Anonymous_returnsAnonymous() { + AwsCredentials awsCredentials = CredentialUtils.toCredentials(new AwsCredentialsIdentity() { + @Override + public String accessKeyId() { + return null; + } + + @Override + public String secretAccessKey() { + return null; + } + }); + + assertThat(awsCredentials.accessKeyId()).isNull(); + assertThat(awsCredentials.secretAccessKey()).isNull(); + } + + @Test + public void toCredentialsProvider_null_returnsNull() { + assertThat(CredentialUtils.toCredentialsProvider(null)).isNull(); + } + + @Test + public void toCredentialsProvider_AwsCredentialsProvider_returnsAsIs() { + IdentityProvider input = + StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")); + AwsCredentialsProvider output = CredentialUtils.toCredentialsProvider(input); + assertThat(output).isSameAs(input); + } + + @Test + public void toCredentialsProvider_IdentityProvider_converts() { + AwsCredentialsProvider credentialsProvider = CredentialUtils.toCredentialsProvider( + StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid"))); + AwsCredentials credentials = credentialsProvider.resolveCredentials(); + assertThat(credentials.accessKeyId()).isEqualTo("akid"); + assertThat(credentials.secretAccessKey()).isEqualTo("skid"); + } +} diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/signer/AwsSignerExecutionAttributeTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/signer/AwsSignerExecutionAttributeTest.java new file mode 100644 index 000000000000..f75735b23cf0 --- /dev/null +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/signer/AwsSignerExecutionAttributeTest.java @@ -0,0 +1,288 @@ +/* + * 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.auth.signer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.SHA256; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.core.SelectedAuthScheme; +import software.amazon.awssdk.core.checksums.Algorithm; +import software.amazon.awssdk.core.checksums.ChecksumSpecs; +import software.amazon.awssdk.core.interceptor.ExecutionAttribute; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.regions.RegionScope; + +class AwsSignerExecutionAttributeTest { + private static final SelectedAuthScheme EMPTY_SELECTED_AUTH_SCHEME = + new SelectedAuthScheme<>(CompletableFuture.completedFuture(Mockito.mock(Identity.class)), + (HttpSigner) Mockito.mock(HttpSigner.class), + AuthSchemeOption.builder().schemeId("mock").build()); + + private ExecutionAttributes attributes; + private Clock testClock; + + @BeforeEach + public void setup() { + this.attributes = new ExecutionAttributes(); + this.testClock = Clock.fixed(Instant.now(), ZoneOffset.UTC); + AwsSignerExecutionAttribute.presignerExpirationClock(testClock); + } + + @Test + public void awsCredentials_oldAndNewAttributeAreMirrored() { + AwsCredentials creds = Mockito.mock(AwsCredentials.class); + + // If selected auth scheme is null, writing non-null old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + attributes.putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, creds); + assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME).identity().join()).isSameAs(creds); + + // If selected auth scheme is null, writing null to old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + attributes.putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, null); + assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME).identity().join()).isNull(); + + // If selected auth scheme is non-null, writing non-null to old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, EMPTY_SELECTED_AUTH_SCHEME); + attributes.putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, creds); + assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME).identity().join()).isSameAs(creds); + + // If selected auth scheme is non-null, writing null to old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, EMPTY_SELECTED_AUTH_SCHEME); + attributes.putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, null); + assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME).identity().join()).isNull(); + + // Writing non-null new property can be read with old property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, + new SelectedAuthScheme<>(CompletableFuture.completedFuture(creds), + EMPTY_SELECTED_AUTH_SCHEME.signer(), + EMPTY_SELECTED_AUTH_SCHEME.authSchemeOption())); + assertThat(attributes.getAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS)).isSameAs(creds); + + // Writing null new property can be read with old property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, + new SelectedAuthScheme<>(CompletableFuture.completedFuture(null), + EMPTY_SELECTED_AUTH_SCHEME.signer(), + EMPTY_SELECTED_AUTH_SCHEME.authSchemeOption())); + assertThat(attributes.getAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS)).isNull(); + + // Null selected auth scheme can be read with old property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + assertThat(attributes.getAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS)).isNull(); + } + + @Test + public void signingRegion_oldAndNewAttributeAreMirrored() { + assertOldAndNewAttributesAreMirrored(AwsSignerExecutionAttribute.SIGNING_REGION, + AwsV4HttpSigner.REGION_NAME, + Region.US_EAST_1, + "us-east-1"); + } + + @Test + public void signingRegionScope_oldAndNewAttributeAreMirrored() { + assertOldAndNewAttributesAreMirrored(AwsSignerExecutionAttribute.SIGNING_REGION_SCOPE, + AwsV4aHttpSigner.REGION_SET, + RegionScope.create("foo"), + RegionSet.create("foo") + ); + } + + @Test + public void signingRegionScope_mappingToOldWithMoreThanOneRegionThrows() { + RegionSet regionSet = RegionSet.create("foo1,foo2"); + AuthSchemeOption newOption = + EMPTY_SELECTED_AUTH_SCHEME.authSchemeOption().copy(o -> o.putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet)); + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, + new SelectedAuthScheme<>(EMPTY_SELECTED_AUTH_SCHEME.identity(), + EMPTY_SELECTED_AUTH_SCHEME.signer(), + newOption)); + assertThrows(IllegalArgumentException.class, () -> attributes.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION_SCOPE)); + } + + @Test + public void signingName_oldAndNewAttributeAreMirrored() { + assertOldAndNewAttributesAreMirrored(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, + AwsV4FamilyHttpSigner.SERVICE_SIGNING_NAME, + "ServiceName"); + } + + @Test + public void doubleUrlEncode_oldAndNewAttributeAreMirrored() { + assertOldAndNewBooleanAttributesAreMirrored(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE, + AwsV4FamilyHttpSigner.DOUBLE_URL_ENCODE); + } + + @Test + public void signerNormalizePath_oldAndNewAttributeAreMirrored() { + assertOldAndNewBooleanAttributesAreMirrored(AwsSignerExecutionAttribute.SIGNER_NORMALIZE_PATH, + AwsV4FamilyHttpSigner.NORMALIZE_PATH); + } + + @Test + public void signingClock_oldAndNewAttributeAreMirrored() { + assertOldAndNewAttributesAreMirrored(AwsSignerExecutionAttribute.SIGNING_CLOCK, + HttpSigner.SIGNING_CLOCK, + Mockito.mock(Clock.class)); + } + + @Test + public void signingExpiration_oldAndNewAttributeAreMirrored() { + assertOldAndNewAttributesAreMirrored(AwsSignerExecutionAttribute.PRESIGNER_EXPIRATION, + AwsV4FamilyHttpSigner.EXPIRATION_DURATION, + testClock.instant().plusSeconds(10), + Duration.ofSeconds(10)); + } + + @Test + public void checksum_AttributeWriteReflectedInProperty() { + assertOldAttributeWrite_canBeReadFromNewAttributeCases(SdkExecutionAttribute.RESOLVED_CHECKSUM_SPECS, + AwsV4FamilyHttpSigner.CHECKSUM_ALGORITHM, + ChecksumSpecs.builder() + .isRequestChecksumRequired(true) + .headerName("beepboop") + .isRequestStreaming(true) + .isValidationEnabled(true) + .responseValidationAlgorithms( + Collections.singletonList(Algorithm.CRC32)) + .algorithm(Algorithm.SHA256) + .build(), + SHA256); + } + + @Test + public void checksum_PropertyWriteReflectedInAttribute() { + // We need to set up the attribute first, so that ChecksumSpecs information is not lost + ChecksumSpecs valueToWrite = ChecksumSpecs.builder() + .isRequestChecksumRequired(true) + .headerName("beepboop") + .isRequestStreaming(true) + .isValidationEnabled(true) + .responseValidationAlgorithms( + Collections.singletonList(Algorithm.CRC32)) + .algorithm(Algorithm.SHA256) + .build(); + attributes.putAttribute(SdkExecutionAttribute.RESOLVED_CHECKSUM_SPECS, valueToWrite); + + assertNewPropertyWrite_canBeReadFromNewAttributeCases(SdkExecutionAttribute.RESOLVED_CHECKSUM_SPECS, + AwsV4FamilyHttpSigner.CHECKSUM_ALGORITHM, + valueToWrite, + SHA256); + } + + private void assertOldAndNewBooleanAttributesAreMirrored(ExecutionAttribute attribute, + SignerProperty property) { + assertOldAndNewAttributesAreMirrored(attribute, property, true); + assertOldAndNewAttributesAreMirrored(attribute, property, false); + } + + private void assertOldAndNewAttributesAreMirrored(ExecutionAttribute attributeToWrite, + SignerProperty propertyToRead, + T valueToWrite) { + assertOldAndNewAttributesAreMirrored(attributeToWrite, propertyToRead, valueToWrite, valueToWrite); + } + + private void assertOldAndNewAttributesAreMirrored(ExecutionAttribute oldAttribute, + SignerProperty newProperty, + T oldPropertyValue, + U newPropertyValue) { + assertOldAttributeWrite_canBeReadFromNewAttributeCases(oldAttribute, newProperty, oldPropertyValue, newPropertyValue); + + assertNewPropertyWrite_canBeReadFromNewAttributeCases(oldAttribute, newProperty, oldPropertyValue, newPropertyValue); + + // Null selected auth scheme can be read with old property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + assertThat(attributes.getAttribute(oldAttribute)).isNull(); + } + + private void assertNewPropertyWrite_canBeReadFromNewAttribute(ExecutionAttribute oldAttribute, + SignerProperty newProperty, + T oldPropertyValue, + U newPropertyValue) { + AuthSchemeOption newOption = + EMPTY_SELECTED_AUTH_SCHEME.authSchemeOption().copy(o -> o.putSignerProperty(newProperty, newPropertyValue)); + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, + new SelectedAuthScheme<>(EMPTY_SELECTED_AUTH_SCHEME.identity(), + EMPTY_SELECTED_AUTH_SCHEME.signer(), + newOption)); + assertThat(attributes.getAttribute(oldAttribute)).isEqualTo(oldPropertyValue); + } + + private void assertNewPropertyWrite_canBeReadFromNewAttributeCases(ExecutionAttribute attributeToWrite, + SignerProperty propertyToRead, + T valueToWrite, + U propertyToExpect) { + // Writing non-null new property can be read with old property + assertNewPropertyWrite_canBeReadFromNewAttribute(attributeToWrite, propertyToRead, valueToWrite, propertyToExpect); + + // Writing null new property can be read with old property + assertNewPropertyWrite_canBeReadFromNewAttribute(attributeToWrite, propertyToRead, null, null); + } + + private void assertOldAttributeWrite_canBeReadFromNewAttribute(ExecutionAttribute attributeToWrite, + SignerProperty propertyToRead, + T valueToWrite, + U propertyToExpect) { + attributes.putAttribute(attributeToWrite, valueToWrite); + assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .authSchemeOption() + .signerProperty(propertyToRead)).isEqualTo(propertyToExpect); + } + + private void assertOldAttributeWrite_canBeReadFromNewAttributeCases(ExecutionAttribute attributeToWrite, + SignerProperty propertyToRead, + T valueToWrite, + U propertyToExpect) { + + // If selected auth scheme is null, writing non-null old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + assertOldAttributeWrite_canBeReadFromNewAttribute(attributeToWrite, propertyToRead, valueToWrite, propertyToExpect); + + // If selected auth scheme is null, writing null to old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + assertOldAttributeWrite_canBeReadFromNewAttribute(attributeToWrite, propertyToRead, null, null); + + // If selected auth scheme is non-null, writing non-null to old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, EMPTY_SELECTED_AUTH_SCHEME); + assertOldAttributeWrite_canBeReadFromNewAttribute(attributeToWrite, propertyToRead, valueToWrite, propertyToExpect); + + // If selected auth scheme is non-null, writing null to old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, EMPTY_SELECTED_AUTH_SCHEME); + assertOldAttributeWrite_canBeReadFromNewAttribute(attributeToWrite, propertyToRead, null, null); + } +} diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/signer/S3SignerExecutionAttributeTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/signer/S3SignerExecutionAttributeTest.java new file mode 100644 index 000000000000..8ccc5f96a797 --- /dev/null +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/signer/S3SignerExecutionAttributeTest.java @@ -0,0 +1,118 @@ +/* + * 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.auth.signer; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import software.amazon.awssdk.core.SelectedAuthScheme; +import software.amazon.awssdk.core.interceptor.ExecutionAttribute; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.Identity; + +class S3SignerExecutionAttributeTest { + private static final SelectedAuthScheme EMPTY_SELECTED_AUTH_SCHEME = + new SelectedAuthScheme<>(CompletableFuture.completedFuture(Mockito.mock(Identity.class)), + (HttpSigner) Mockito.mock(HttpSigner.class), + AuthSchemeOption.builder().schemeId("mock").build()); + + private ExecutionAttributes attributes; + + @BeforeEach + public void setup() { + this.attributes = new ExecutionAttributes(); + } + + @Test + public void enableChunkedEncoding_oldAndNewAttributeAreMirrored() { + assertOldAndNewBooleanAttributesAreMirrored(S3SignerExecutionAttribute.ENABLE_CHUNKED_ENCODING, + AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED); + } + + @Test + public void enablePayloadSigning_oldAndNewAttributeAreMirrored() { + assertOldAndNewBooleanAttributesAreMirrored(S3SignerExecutionAttribute.ENABLE_PAYLOAD_SIGNING, + AwsV4FamilyHttpSigner.PAYLOAD_SIGNING_ENABLED); + } + + private void assertOldAndNewBooleanAttributesAreMirrored(ExecutionAttribute attribute, + SignerProperty property) { + assertOldAndNewAttributesAreMirrored(attribute, property, true, true); + assertOldAndNewAttributesAreMirrored(attribute, property, false, false); + } + + private void assertOldAndNewAttributesAreMirrored(ExecutionAttribute oldAttribute, + SignerProperty newProperty, + T oldPropertyValue, + U newPropertyValue) { + // If selected auth scheme is null, writing non-null old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + assertOldAttributeWrite_canBeReadFromNewAttribute(oldAttribute, newProperty, oldPropertyValue, newPropertyValue); + + // If selected auth scheme is null, writing null to old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + assertOldAttributeWrite_canBeReadFromNewAttribute(oldAttribute, newProperty, null, null); + + // If selected auth scheme is non-null, writing non-null to old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, EMPTY_SELECTED_AUTH_SCHEME); + assertOldAttributeWrite_canBeReadFromNewAttribute(oldAttribute, newProperty, oldPropertyValue, newPropertyValue); + + // If selected auth scheme is non-null, writing null to old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, EMPTY_SELECTED_AUTH_SCHEME); + assertOldAttributeWrite_canBeReadFromNewAttribute(oldAttribute, newProperty, null, null); + + // Writing non-null new property can be read with old property + assertNewPropertyWrite_canBeReadFromNewAttribute(oldAttribute, newProperty, oldPropertyValue, newPropertyValue); + + // Writing null new property can be read with old property + assertNewPropertyWrite_canBeReadFromNewAttribute(oldAttribute, newProperty, null, null); + + // Null selected auth scheme can be read with old property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + assertThat(attributes.getAttribute(oldAttribute)).isNull(); + } + + private void assertNewPropertyWrite_canBeReadFromNewAttribute(ExecutionAttribute oldAttribute, + SignerProperty newProperty, + T oldPropertyValue, + U newPropertyValue) { + AuthSchemeOption newOption = + EMPTY_SELECTED_AUTH_SCHEME.authSchemeOption().copy(o -> o.putSignerProperty(newProperty, newPropertyValue)); + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, + new SelectedAuthScheme<>(EMPTY_SELECTED_AUTH_SCHEME.identity(), + EMPTY_SELECTED_AUTH_SCHEME.signer(), + newOption)); + assertThat(attributes.getAttribute(oldAttribute)).isEqualTo(oldPropertyValue); + } + + private void assertOldAttributeWrite_canBeReadFromNewAttribute(ExecutionAttribute attributeToWrite, + SignerProperty propertyToRead, + T valueToWrite, + U propertyToExpect) { + attributes.putAttribute(attributeToWrite, valueToWrite); + assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .authSchemeOption() + .signerProperty(propertyToRead)).isEqualTo(propertyToExpect); + } +} \ No newline at end of file diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/token/credentials/SdkTokenProviderChainTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/token/credentials/SdkTokenProviderChainTest.java index 800759fe8714..4a9c07502f2b 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/token/credentials/SdkTokenProviderChainTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/token/credentials/SdkTokenProviderChainTest.java @@ -15,17 +15,18 @@ package software.amazon.awssdk.auth.token.credentials; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.time.Instant; +import java.util.Arrays; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; import software.amazon.awssdk.auth.token.TestBearerToken; -import software.amazon.awssdk.auth.token.credentials.SdkToken; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProviderChain; -import software.amazon.awssdk.auth.token.credentials.StaticTokenProvider; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; public class SdkTokenProviderChainTest { @@ -37,7 +38,7 @@ public class SdkTokenProviderChainTest { * for any additional calls to getToken. */ @Test - public void testReusingLastProvider() { + public void resolveToken_reuseEnabled_reusesLastProvider() { MockTokenProvider provider1 = new MockTokenProvider("Failed!"); MockTokenProvider provider2 = new MockTokenProvider(); SdkTokenProviderChain chain = SdkTokenProviderChain.builder() @@ -65,7 +66,7 @@ public void testReusingLastProvider() { * first, until it finds a provider that can return token. */ @Test - public void testDisableReusingLastProvider() { + public void resolveToken_reuseDisabled_alwaysGoesThroughChain() { MockTokenProvider provider1 = new MockTokenProvider("Failed!"); MockTokenProvider provider2 = new MockTokenProvider(); SdkTokenProviderChain chain = SdkTokenProviderChain.builder() @@ -90,16 +91,66 @@ public void testDisableReusingLastProvider() { * Tests that getToken throws an Exception if all providers in the chain fail to provide token. */ @Test - public void testGetTokenException() { + public void resolveToken_allProvidersFail_throwsExceptionWithMessageFromAllProviders() { MockTokenProvider provider1 = new MockTokenProvider("Failed!"); MockTokenProvider provider2 = new MockTokenProvider("Bad!"); SdkTokenProviderChain chain = SdkTokenProviderChain.builder() .tokenProviders(provider1, provider2) .build(); - assertThrows(SdkClientException.class, () -> chain.resolveToken()); + SdkClientException e = assertThrows(SdkClientException.class, () -> chain.resolveToken()); + assertThat(e.getMessage()).contains(provider1.exceptionMessage); + assertThat(e.getMessage()).contains(provider2.exceptionMessage); } + @Test + public void resolveToken_emptyChain_throwsException() { + assertThrowsIllegalArgument(() -> SdkTokenProviderChain.of()); + + assertThrowsIllegalArgument(() -> SdkTokenProviderChain + .builder() + .tokenProviders() + .build()); + + assertThrowsIllegalArgument(() -> SdkTokenProviderChain + .builder() + .tokenProviders(Arrays.asList()) + .build()); + } + + private void assertThrowsIllegalArgument(Executable executable) { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, executable); + assertThat(e.getMessage()).contains("No token providers were specified."); + } + + /** + * Tests that the chain is setup correctly with the overloaded methods that accept the AwsCredentialsProvider type. + */ + @Test + public void createMethods_withOldTokenType_work() { + SdkTokenProvider provider = new MockTokenProvider(); + assertChainResolvesCorrectly(SdkTokenProviderChain.of(provider)); + assertChainResolvesCorrectly(SdkTokenProviderChain.builder().tokenProviders(provider).build()); + assertChainResolvesCorrectly(SdkTokenProviderChain.builder().tokenProviders(Arrays.asList(provider)).build()); + assertChainResolvesCorrectly(SdkTokenProviderChain.builder().addTokenProvider(provider).build()); + } + + /** + * Tests that the chain is setup correctly with the overloaded methods that accept the IdentityProvider type. + */ + @Test + public void createMethods_withNewTokenType_work() { + IdentityProvider provider = new MockTokenProvider(); + assertChainResolvesCorrectly(SdkTokenProviderChain.of(provider)); + assertChainResolvesCorrectly(SdkTokenProviderChain.builder().tokenProviders(provider).build()); + assertChainResolvesCorrectly(SdkTokenProviderChain.builder().tokenIdentityProviders(Arrays.asList(provider)).build()); + assertChainResolvesCorrectly(SdkTokenProviderChain.builder().addTokenProvider(provider).build()); + } + + private static void assertChainResolvesCorrectly(SdkTokenProviderChain chain) { + SdkToken token = chain.resolveToken(); + assertThat(token.token()).isEqualTo(SAMPLE_TOKEN_STRING); + } private static final class MockTokenProvider implements SdkTokenProvider { private final SdkTokenProvider sdkTokenProvider; diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/token/credentials/internal/TokenUtilsTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/token/credentials/internal/TokenUtilsTest.java new file mode 100644 index 000000000000..16d1696d397e --- /dev/null +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/token/credentials/internal/TokenUtilsTest.java @@ -0,0 +1,45 @@ +/* + * 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.auth.token.credentials.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.token.TestBearerToken; +import software.amazon.awssdk.auth.token.credentials.SdkToken; +import software.amazon.awssdk.identity.spi.TokenIdentity; + +public class TokenUtilsTest { + + @Test + public void toSdkToken_null_returnsNull() { + assertThat(TokenUtils.toSdkToken(null)).isNull(); + } + + @Test + public void toSdkToken_SdkToken_returnsAsIs() { + TokenIdentity input = TestBearerToken.create("t"); + SdkToken output = TokenUtils.toSdkToken(input); + assertThat(output).isSameAs(input); + } + + @Test + public void toSdkToken_TokenIdentity_returnsSdkToken() { + TokenIdentity tokenIdentity = TokenIdentity.create("token"); + SdkToken sdkToken = TokenUtils.toSdkToken(tokenIdentity); + assertThat(sdkToken.token()).isEqualTo("token"); + } +} \ No newline at end of file diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/token/signer/SdkTokenExecutionAttributeTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/token/signer/SdkTokenExecutionAttributeTest.java new file mode 100644 index 000000000000..319c4fb7ccd0 --- /dev/null +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/token/signer/SdkTokenExecutionAttributeTest.java @@ -0,0 +1,87 @@ +/* + * 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.auth.token.signer; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import software.amazon.awssdk.auth.token.credentials.SdkToken; +import software.amazon.awssdk.core.SelectedAuthScheme; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.identity.spi.Identity; + +class SdkTokenExecutionAttributeTest { + private static final SelectedAuthScheme EMPTY_SELECTED_AUTH_SCHEME = + new SelectedAuthScheme<>(CompletableFuture.completedFuture(Mockito.mock(Identity.class)), + (HttpSigner) Mockito.mock(HttpSigner.class), + AuthSchemeOption.builder().schemeId("mock").build()); + + private ExecutionAttributes attributes; + + @BeforeEach + public void setup() { + this.attributes = new ExecutionAttributes(); + } + + @Test + public void awsCredentials_oldAndNewAttributeAreMirrored() { + SdkToken token = Mockito.mock(SdkToken.class); + + // If selected auth scheme is null, writing non-null old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + attributes.putAttribute(SdkTokenExecutionAttribute.SDK_TOKEN, token); + assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME).identity().join()).isSameAs(token); + + // If selected auth scheme is null, writing null to old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + attributes.putAttribute(SdkTokenExecutionAttribute.SDK_TOKEN, null); + assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME).identity().join()).isNull(); + + // If selected auth scheme is non-null, writing non-null to old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, EMPTY_SELECTED_AUTH_SCHEME); + attributes.putAttribute(SdkTokenExecutionAttribute.SDK_TOKEN, token); + assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME).identity().join()).isSameAs(token); + + // If selected auth scheme is non-null, writing null to old property can be read with new property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, EMPTY_SELECTED_AUTH_SCHEME); + attributes.putAttribute(SdkTokenExecutionAttribute.SDK_TOKEN, null); + assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME).identity().join()).isNull(); + + // Writing non-null new property can be read with old property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, + new SelectedAuthScheme<>(CompletableFuture.completedFuture(token), + EMPTY_SELECTED_AUTH_SCHEME.signer(), + EMPTY_SELECTED_AUTH_SCHEME.authSchemeOption())); + assertThat(attributes.getAttribute(SdkTokenExecutionAttribute.SDK_TOKEN)).isSameAs(token); + + // Writing null new property can be read with old property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, + new SelectedAuthScheme<>(CompletableFuture.completedFuture(null), + EMPTY_SELECTED_AUTH_SCHEME.signer(), + EMPTY_SELECTED_AUTH_SCHEME.authSchemeOption())); + assertThat(attributes.getAttribute(SdkTokenExecutionAttribute.SDK_TOKEN)).isNull(); + + // Null selected auth scheme can be read with old property + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + assertThat(attributes.getAttribute(SdkTokenExecutionAttribute.SDK_TOKEN)).isNull(); + } +} \ No newline at end of file diff --git a/core/aws-core/pom.xml b/core/aws-core/pom.xml index d92530f026e1..f0219802dcd9 100644 --- a/core/aws-core/pom.xml +++ b/core/aws-core/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk core - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT aws-core @@ -48,6 +48,21 @@ auth ${awsjavasdk.version}
+ + software.amazon.awssdk + http-auth-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth + ${awsjavasdk.version} + software.amazon.awssdk profiles diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsRequestOverrideConfiguration.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsRequestOverrideConfiguration.java index f101e0b06493..cde970af6402 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsRequestOverrideConfiguration.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsRequestOverrideConfiguration.java @@ -19,7 +19,10 @@ import java.util.Optional; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.CredentialUtils; import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.utils.builder.SdkBuilder; /** @@ -27,11 +30,11 @@ */ @SdkPublicApi public final class AwsRequestOverrideConfiguration extends RequestOverrideConfiguration { - private final AwsCredentialsProvider credentialsProvider; + private final IdentityProvider credentialsProvider; - private AwsRequestOverrideConfiguration(Builder builder) { + private AwsRequestOverrideConfiguration(BuilderImpl builder) { super(builder); - this.credentialsProvider = builder.credentialsProvider(); + this.credentialsProvider = builder.awsCredentialsProvider; } /** @@ -59,6 +62,16 @@ public static AwsRequestOverrideConfiguration from(RequestOverrideConfiguration * @return The optional {@link AwsCredentialsProvider}. */ public Optional credentialsProvider() { + return Optional.ofNullable(CredentialUtils.toCredentialsProvider(credentialsProvider)); + } + + /** + * The optional {@link IdentityProvider} that will provide credentials to be used to + * authenticate this request. + * + * @return The optional {@link IdentityProvider}. + */ + public Optional> credentialsIdentityProvider() { return Optional.ofNullable(credentialsProvider); } @@ -103,7 +116,20 @@ public interface Builder extends RequestOverrideConfiguration.Builder, * @param credentialsProvider The {@link AwsCredentialsProvider}. * @return This object for chaining. */ - Builder credentialsProvider(AwsCredentialsProvider credentialsProvider); + default Builder credentialsProvider(AwsCredentialsProvider credentialsProvider) { + return credentialsProvider((IdentityProvider) credentialsProvider); + } + + /** + * Set the optional {@link IdentityProvider} that will provide credentials to be used + * to authenticate this request. + * + * @param credentialsProvider The {@link IdentityProvider}. + * @return This object for chaining. + */ + default Builder credentialsProvider(IdentityProvider credentialsProvider) { + throw new UnsupportedOperationException(); + } /** * Return the optional {@link AwsCredentialsProvider} that will provide credentials to be used to authenticate this @@ -119,8 +145,7 @@ public interface Builder extends RequestOverrideConfiguration.Builder, private static final class BuilderImpl extends RequestOverrideConfiguration.BuilderImpl implements Builder { - private AwsCredentialsProvider awsCredentialsProvider; - + private IdentityProvider awsCredentialsProvider; private BuilderImpl() { } @@ -135,14 +160,14 @@ private BuilderImpl(AwsRequestOverrideConfiguration awsRequestOverrideConfig) { } @Override - public Builder credentialsProvider(AwsCredentialsProvider credentialsProvider) { + public Builder credentialsProvider(IdentityProvider credentialsProvider) { this.awsCredentialsProvider = credentialsProvider; return this; } @Override public AwsCredentialsProvider credentialsProvider() { - return awsCredentialsProvider; + return CredentialUtils.toCredentialsProvider(awsCredentialsProvider); } @Override diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsServiceClientConfiguration.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsServiceClientConfiguration.java index 50aadac858ba..cc9dc2c10c5f 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsServiceClientConfiguration.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsServiceClientConfiguration.java @@ -16,11 +16,17 @@ package software.amazon.awssdk.awscore; import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.SdkServiceClientConfiguration; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; /** @@ -30,10 +36,12 @@ public abstract class AwsServiceClientConfiguration extends SdkServiceClientConfiguration { private final Region region; + private final IdentityProvider credentialsProvider; protected AwsServiceClientConfiguration(Builder builder) { super(builder); this.region = builder.region(); + this.credentialsProvider = builder.credentialsProvider(); } /** @@ -44,14 +52,11 @@ public Region region() { return this.region; } - @Override - public boolean equals(Object o) { - if (!super.equals(o)) { - return false; - } - - AwsServiceClientConfiguration serviceClientConfiguration = (AwsServiceClientConfiguration) o; - return Objects.equals(region, serviceClientConfiguration.region); + /** + * @return The configured identity provider + */ + public IdentityProvider credentialsProvider() { + return credentialsProvider; } @Override @@ -59,9 +64,21 @@ public int hashCode() { int result = 1; result = 31 * result + super.hashCode(); result = 31 * result + (region != null ? region.hashCode() : 0); + result = 31 * result + (credentialsProvider != null ? credentialsProvider.hashCode() : 0); return result; } + @Override + public boolean equals(Object o) { + if (!super.equals(o)) { + return false; + } + + AwsServiceClientConfiguration that = (AwsServiceClientConfiguration) o; + return Objects.equals(region, that.region) + && Objects.equals(credentialsProvider, that.credentialsProvider); + } + /** * The base interface for all AWS service client configuration builders */ @@ -95,6 +112,22 @@ default Builder endpointProvider(EndpointProvider endpointProvider) { throw new UnsupportedOperationException(); } + @Override + default Builder putAuthScheme(AuthScheme authScheme) { + throw new UnsupportedOperationException(); + } + + /** + * Configure the credentials provider + */ + default Builder credentialsProvider(IdentityProvider credentialsProvider) { + throw new UnsupportedOperationException(); + } + + default IdentityProvider credentialsProvider() { + throw new UnsupportedOperationException(); + } + @Override AwsServiceClientConfiguration build(); } @@ -104,8 +137,11 @@ protected abstract static class BuilderImpl implements Builder { protected Region region; protected URI endpointOverride; protected EndpointProvider endpointProvider; + protected IdentityProvider credentialsProvider; + protected Map> authSchemes; protected BuilderImpl() { + } protected BuilderImpl(AwsServiceClientConfiguration awsServiceClientConfiguration) { @@ -135,6 +171,32 @@ public final EndpointProvider endpointProvider() { return endpointProvider; } - } + @Override + public final Builder credentialsProvider(IdentityProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + @Override + public final IdentityProvider credentialsProvider() { + return credentialsProvider; + } + + @Override + public final Builder putAuthScheme(AuthScheme authScheme) { + if (authSchemes == null) { + authSchemes = new HashMap<>(); + } + authSchemes.put(authScheme.schemeId(), authScheme); + return this; + } + @Override + public final Map> authSchemes() { + if (authSchemes == null) { + return Collections.emptyMap(); + } + return Collections.unmodifiableMap(new HashMap<>(authSchemes)); + } + } } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsClientBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsClientBuilder.java index 73d757919235..1e6c3f1c8e2d 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsClientBuilder.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsClientBuilder.java @@ -19,6 +19,8 @@ import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode; import software.amazon.awssdk.core.client.builder.SdkClientBuilder; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; /** @@ -49,8 +51,36 @@ public interface AwsClientBuilderIf the credentials are not found in any of the locations above, an exception will be thrown at {@link #build()} time. *

+ * + *

The last of {@link #credentialsProvider(AwsCredentialsProvider)} or {@link #credentialsProvider(IdentityProvider)} + * wins.

+ */ + default BuilderT credentialsProvider(AwsCredentialsProvider credentialsProvider) { + return credentialsProvider((IdentityProvider) credentialsProvider); + } + + /** + * Configure the credentials that should be used to authenticate with AWS. + * + *

The default provider will attempt to identify the credentials automatically using the following checks: + *

    + *
  1. Java System Properties - aws.accessKeyId and aws.secretAccessKey
  2. + *
  3. Environment Variables - AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
  4. + *
  5. Credential profiles file at the default location (~/.aws/credentials) shared by all AWS SDKs and the AWS CLI
  6. + *
  7. Credentials delivered through the Amazon EC2 container service if AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment + * variable is set and security manager has permission to access the variable.
  8. + *
  9. Instance profile credentials delivered through the Amazon EC2 metadata service
  10. + *
+ * + *

If the credentials are not found in any of the locations above, an exception will be thrown at {@link #build()} time. + *

+ * + *

The last of {@link #credentialsProvider(AwsCredentialsProvider)} or {@link #credentialsProvider(IdentityProvider)} + * wins.

*/ - BuilderT credentialsProvider(AwsCredentialsProvider credentialsProvider); + default BuilderT credentialsProvider(IdentityProvider credentialsProvider) { + throw new UnsupportedOperationException(); + } /** * Configure the region with which the SDK should communicate. diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java index c41e604afccb..14e492d2a2d0 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java @@ -25,6 +25,7 @@ import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.annotations.SdkTestInternalApi; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.CredentialUtils; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.awscore.client.config.AwsAdvancedClientOption; import software.amazon.awssdk.awscore.client.config.AwsClientOption; @@ -48,6 +49,8 @@ import software.amazon.awssdk.core.retry.RetryPolicy; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.ServiceMetadata; @@ -190,13 +193,31 @@ protected final SdkClientConfiguration finalizeChildConfiguration(SdkClientConfi configuration = mergeSmartDefaults(configuration); - return configuration.toBuilder() - .option(AwsClientOption.CREDENTIALS_PROVIDER, resolveCredentials(configuration)) - .option(SdkClientOption.ENDPOINT, resolveEndpoint(configuration)) - .option(SdkClientOption.EXECUTION_INTERCEPTORS, addAwsInterceptors(configuration)) - .option(AwsClientOption.SIGNING_REGION, resolveSigningRegion(configuration)) - .option(SdkClientOption.RETRY_POLICY, resolveAwsRetryPolicy(configuration)) - .build(); + IdentityProvider identityProvider = resolveCredentialsIdentityProvider(configuration); + SdkClientConfiguration.Builder configBuilder = + configuration.toBuilder() + .option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER, identityProvider) + // CREDENTIALS_PROVIDER is also set, since older clients may be relying on it + .option(AwsClientOption.CREDENTIALS_PROVIDER, toCredentialsProvider(identityProvider)) + .option(SdkClientOption.ENDPOINT, resolveEndpoint(configuration)) + .option(SdkClientOption.EXECUTION_INTERCEPTORS, addAwsInterceptors(configuration)) + .option(AwsClientOption.SIGNING_REGION, resolveSigningRegion(configuration)) + .option(SdkClientOption.RETRY_POLICY, resolveAwsRetryPolicy(configuration)); + + // Add the identityProvider to the IdentityProviders configured for the client. + // Currently, it is not possible for identityProvider to be null as default provider is used while building the client if + // the clientConfig is null. However, we do want to support ability to unset a identity provider later. + // Moreover, putIdentityProvider will throw NPE on null, so adding the null check here. Also, validateClientOptions + // currently asserts it is not null, which will have to change when we allow unsetting default identity provider. + if (identityProvider != null) { + configBuilder.option(SdkClientOption.IDENTITY_PROVIDERS, + configuration.option(SdkClientOption.IDENTITY_PROVIDERS) + .toBuilder() + .putIdentityProvider(identityProvider) + .build()); + } + + return configBuilder.build(); } private SdkClientConfiguration mergeSmartDefaults(SdkClientConfiguration configuration) { @@ -349,15 +370,22 @@ private Boolean resolveUseFipsFromDefaultProvider(SdkClientConfiguration config) /** * Resolve the credentials that should be used based on the customer's configuration. */ - private AwsCredentialsProvider resolveCredentials(SdkClientConfiguration config) { - return config.option(AwsClientOption.CREDENTIALS_PROVIDER) != null - ? config.option(AwsClientOption.CREDENTIALS_PROVIDER) + private IdentityProvider resolveCredentialsIdentityProvider(SdkClientConfiguration config) { + return config.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER) != null + ? config.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER) : DefaultCredentialsProvider.builder() .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) .build(); } + // If resolveCredentialsIdentityProvider returns a DefaultCredentialsProvider (which is the more common usage), this avoids + // wrapping it in another AwsCredentialsProvider. + private AwsCredentialsProvider toCredentialsProvider(IdentityProvider identityProvider) { + return identityProvider instanceof AwsCredentialsProvider ? (AwsCredentialsProvider) identityProvider : + CredentialUtils.toCredentialsProvider(identityProvider); + } + private RetryPolicy resolveAwsRetryPolicy(SdkClientConfiguration config) { RetryPolicy policy = config.option(SdkClientOption.RETRY_POLICY); @@ -419,14 +447,18 @@ public final void setFipsEnabled(Boolean fipsEndpointEnabled) { fipsEnabled(fipsEndpointEnabled); } + public final void setCredentialsProvider(AwsCredentialsProvider credentialsProvider) { + credentialsProvider(credentialsProvider); + } + @Override - public final BuilderT credentialsProvider(AwsCredentialsProvider credentialsProvider) { - clientConfiguration.option(AwsClientOption.CREDENTIALS_PROVIDER, credentialsProvider); + public final BuilderT credentialsProvider(IdentityProvider identityProvider) { + clientConfiguration.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER, identityProvider); return thisBuilder(); } - public final void setCredentialsProvider(AwsCredentialsProvider credentialsProvider) { - credentialsProvider(credentialsProvider); + public final void setCredentialsProvider(IdentityProvider identityProvider) { + credentialsProvider(identityProvider); } private List addAwsInterceptors(SdkClientConfiguration config) { diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java index a063adbca51f..3388651f91b6 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java @@ -21,16 +21,30 @@ import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode; import software.amazon.awssdk.core.client.config.ClientOption; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.regions.Region; @SdkProtectedApi public final class AwsClientOption extends ClientOption { /** + * This option is deprecated in favor of {@link #CREDENTIALS_IDENTITY_PROVIDER}. * @see AwsClientBuilder#credentialsProvider(AwsCredentialsProvider) */ + @Deprecated + // smithy codegen TODO: This could be removed when doing a minor version bump where we told customers we'll be breaking + // protected APIs. Postpone this to when we do Smithy code generator migration, where we'll likely have to start + // breaking a lot of protected things. public static final AwsClientOption CREDENTIALS_PROVIDER = new AwsClientOption<>(AwsCredentialsProvider.class); + /** + * @see AwsClientBuilder#credentialsProvider(IdentityProvider) + */ + public static final AwsClientOption> CREDENTIALS_IDENTITY_PROVIDER = + new AwsClientOption<>(new UnsafeValueType(IdentityProvider.class)); + /** * AWS Region the client was configured with. Note that this is not always the signing region in the case of global * services like IAM. @@ -79,10 +93,22 @@ public final class AwsClientOption extends ClientOption { /** * Option to specific the {@link SdkTokenProvider} to use for bearer token authorization. + * This option is deprecated in favor or {@link #TOKEN_IDENTITY_PROVIDER} */ + @Deprecated public static final AwsClientOption TOKEN_PROVIDER = new AwsClientOption<>(SdkTokenProvider.class); + /** + * Option to specific the {@link SdkTokenProvider} to use for bearer token authorization. + */ + public static final AwsClientOption> TOKEN_IDENTITY_PROVIDER = + new AwsClientOption<>(new UnsafeValueType(IdentityProvider.class)); + private AwsClientOption(Class valueClass) { super(valueClass); } + + private AwsClientOption(UnsafeValueType valueType) { + super(valueType); + } } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsAsyncClientHandler.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsAsyncClientHandler.java index a3d1a0f3d637..41e014f40a85 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsAsyncClientHandler.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsAsyncClientHandler.java @@ -38,11 +38,8 @@ @SdkProtectedApi public final class AwsAsyncClientHandler extends SdkAsyncClientHandler implements AsyncClientHandler { - private final SdkClientConfiguration clientConfiguration; - public AwsAsyncClientHandler(SdkClientConfiguration clientConfiguration) { super(clientConfiguration); - this.clientConfiguration = clientConfiguration; AwsClientOptionValidation.validateAsyncClientOptions(clientConfiguration); } @@ -62,6 +59,7 @@ public Complet @Override protected ExecutionContext invokeInterceptorsAndCreateExecutionContext(ClientExecutionParams executionParams) { + SdkClientConfiguration clientConfiguration = resolveRequestConfiguration(executionParams); return AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams, clientConfiguration); } } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsSyncClientHandler.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsSyncClientHandler.java index 08d74bee972d..a877bfd4c2dd 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsSyncClientHandler.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsSyncClientHandler.java @@ -41,11 +41,8 @@ @SdkProtectedApi public final class AwsSyncClientHandler extends SdkSyncClientHandler implements SyncClientHandler { - private final SdkClientConfiguration clientConfiguration; - public AwsSyncClientHandler(SdkClientConfiguration clientConfiguration) { super(clientConfiguration); - this.clientConfiguration = clientConfiguration; AwsClientOptionValidation.validateSyncClientOptions(clientConfiguration); } @@ -66,6 +63,7 @@ public ReturnT @Override protected ExecutionContext invokeInterceptorsAndCreateExecutionContext(ClientExecutionParams executionParams) { + SdkClientConfiguration clientConfiguration = resolveRequestConfiguration(executionParams); return AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams, clientConfiguration); } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoints/authscheme/EndpointAuthScheme.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoints/authscheme/EndpointAuthScheme.java index 7e81cd39ed37..72e7951a3e7a 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoints/authscheme/EndpointAuthScheme.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoints/authscheme/EndpointAuthScheme.java @@ -24,4 +24,8 @@ @SdkProtectedApi public interface EndpointAuthScheme { String name(); + + default String schemeId() { + throw new UnsupportedOperationException(); + } } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoints/authscheme/SigV4AuthScheme.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoints/authscheme/SigV4AuthScheme.java index 51160304bc4a..c4f4417dbd69 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoints/authscheme/SigV4AuthScheme.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoints/authscheme/SigV4AuthScheme.java @@ -24,12 +24,12 @@ public final class SigV4AuthScheme implements EndpointAuthScheme { private final String signingRegion; private final String signingName; - private final boolean disableDoubleEncoding; + private final Boolean disableDoubleEncoding; private SigV4AuthScheme(Builder b) { this.signingRegion = b.signingRegion; this.signingName = b.signingName; - this.disableDoubleEncoding = b.disableDoubleEncoding == null ? false : b.disableDoubleEncoding; + this.disableDoubleEncoding = b.disableDoubleEncoding; } @Override @@ -37,6 +37,11 @@ public String name() { return "sigv4"; } + @Override + public String schemeId() { + return "aws.auth#sigv4"; + } + public String signingRegion() { return signingRegion; } @@ -46,7 +51,11 @@ public String signingName() { } public boolean disableDoubleEncoding() { - return disableDoubleEncoding; + return disableDoubleEncoding == null ? false : disableDoubleEncoding; + } + + public boolean isDisableDoubleEncodingSet() { + return disableDoubleEncoding != null; } @Override @@ -60,7 +69,8 @@ public boolean equals(Object o) { SigV4AuthScheme that = (SigV4AuthScheme) o; - if (disableDoubleEncoding != that.disableDoubleEncoding) { + if (disableDoubleEncoding != null ? !disableDoubleEncoding.equals(that.disableDoubleEncoding) + : that.disableDoubleEncoding != null) { return false; } if (signingRegion != null ? !signingRegion.equals(that.signingRegion) : that.signingRegion != null) { @@ -73,7 +83,7 @@ public boolean equals(Object o) { public int hashCode() { int result = signingRegion != null ? signingRegion.hashCode() : 0; result = 31 * result + (signingName != null ? signingName.hashCode() : 0); - result = 31 * result + (disableDoubleEncoding ? 1 : 0); + result = 31 * result + (disableDoubleEncoding != null ? disableDoubleEncoding.hashCode() : 0); return result; } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoints/authscheme/SigV4aAuthScheme.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoints/authscheme/SigV4aAuthScheme.java index 2cca72430cf0..97dfe6835248 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoints/authscheme/SigV4aAuthScheme.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoints/authscheme/SigV4aAuthScheme.java @@ -26,12 +26,12 @@ public final class SigV4aAuthScheme implements EndpointAuthScheme { private final String signingName; private final List signingRegionSet; - private final boolean disableDoubleEncoding; + private final Boolean disableDoubleEncoding; private SigV4aAuthScheme(Builder b) { this.signingName = b.signingName; this.signingRegionSet = b.signingRegionSet; - this.disableDoubleEncoding = b.disableDoubleEncoding == null ? false : b.disableDoubleEncoding; + this.disableDoubleEncoding = b.disableDoubleEncoding; } public String signingName() { @@ -39,7 +39,11 @@ public String signingName() { } public boolean disableDoubleEncoding() { - return disableDoubleEncoding; + return disableDoubleEncoding == null ? false : disableDoubleEncoding; + } + + public boolean isDisableDoubleEncodingSet() { + return disableDoubleEncoding != null; } public List signingRegionSet() { @@ -51,6 +55,11 @@ public String name() { return "sigv4a"; } + @Override + public String schemeId() { + return "aws.auth#sigv4a"; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -62,7 +71,8 @@ public boolean equals(Object o) { SigV4aAuthScheme that = (SigV4aAuthScheme) o; - if (disableDoubleEncoding != that.disableDoubleEncoding) { + if (disableDoubleEncoding != null ? !disableDoubleEncoding.equals(that.disableDoubleEncoding) + : that.disableDoubleEncoding != null) { return false; } if (signingName != null ? !signingName.equals(that.signingName) : that.signingName != null) { @@ -75,7 +85,7 @@ public boolean equals(Object o) { public int hashCode() { int result = signingName != null ? signingName.hashCode() : 0; result = 31 * result + (signingRegionSet != null ? signingRegionSet.hashCode() : 0); - result = 31 * result + (disableDoubleEncoding ? 1 : 0); + result = 31 * result + (disableDoubleEncoding != null ? disableDoubleEncoding.hashCode() : 0); return result; } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java index 07cff8936c7c..d7740f722770 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java @@ -18,18 +18,20 @@ import static software.amazon.awssdk.auth.signer.internal.util.SignerMethodResolver.resolveSigningMethodUsed; import static software.amazon.awssdk.core.interceptor.SdkExecutionAttribute.RESOLVED_CHECKSUM_SPECS; +import java.util.Map; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; import software.amazon.awssdk.awscore.AwsExecutionAttribute; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.awscore.internal.authcontext.AuthorizationStrategy; import software.amazon.awssdk.awscore.internal.authcontext.AuthorizationStrategyFactory; +import software.amazon.awssdk.awscore.util.SignerOverrideUtils; import software.amazon.awssdk.core.HttpChecksumConstant; import software.amazon.awssdk.core.RequestOverrideConfiguration; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SdkResponse; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; @@ -44,6 +46,10 @@ import software.amazon.awssdk.core.internal.util.HttpChecksumResolver; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.metrics.MetricCollector; @SdkInternalApi @@ -70,6 +76,8 @@ private AwsExecutionContextBuilder() { clientConfig.option(SdkClientOption.EXECUTION_ATTRIBUTES), originalRequest.overrideConfiguration().map(c -> c.executionAttributes()).orElse(null)); + executionAttributes.putAttributeIfAbsent(SdkExecutionAttribute.API_CALL_METRIC_COLLECTOR, metricCollector); + executionAttributes .putAttribute(InternalCoreExecutionAttribute.EXECUTION_ATTEMPT, 1) .putAttribute(AwsSignerExecutionAttribute.SERVICE_CONFIG, @@ -106,6 +114,9 @@ private AwsExecutionContextBuilder() { clientConfig.option(AwsClientOption.USE_GLOBAL_ENDPOINT)) .putAttribute(RESOLVED_CHECKSUM_SPECS, HttpChecksumResolver.resolveChecksumSpecs(executionAttributes)); + // Auth Scheme resolution related attributes + putAuthSchemeResolutionAttributes(executionAttributes, clientConfig, originalRequest); + ExecutionInterceptorChain executionInterceptorChain = new ExecutionInterceptorChain(clientConfig.option(SdkClientOption.EXECUTION_INTERCEPTORS)); @@ -117,7 +128,7 @@ private AwsExecutionContextBuilder() { interceptorContext = runInitialInterceptors(interceptorContext, executionAttributes, executionInterceptorChain); Signer signer = null; - if (isAuthenticatedRequest(executionAttributes)) { + if (loadOldSigner(executionAttributes, originalRequest)) { AuthorizationStrategyFactory authorizationStrategyFactory = new AuthorizationStrategyFactory(interceptorContext.request(), metricCollector, clientConfig); AuthorizationStrategy authorizationStrategy = @@ -141,30 +152,76 @@ private AwsExecutionContextBuilder() { } /** - * Resolves the credentials provider, with the request override configuration taking precedence over the - * provided default. - * - * @return The credentials provider that will be used by the SDK to resolve credentials + * We will load the old (non-SRA) signer if this client seems like an old version or the customer has provided a signer + * override. We assume that if there's no auth schemes defined, we're on the old code path. + *

+ * In addition, if authType=none, we don't need to use the old signer, even if overridden. */ - public static AwsCredentialsProvider resolveCredentialsProvider(SdkRequest originalRequest, - AwsCredentialsProvider defaultProvider) { + private static boolean loadOldSigner(ExecutionAttributes attributes, SdkRequest request) { + Map> authSchemes = attributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES); + if (authSchemes == null) { + // pre SRA case. + // We used to set IS_NONE_AUTH_TYPE_REQUEST = false when authType=none. Yes, false. + return attributes.getOptionalAttribute(SdkInternalExecutionAttribute.IS_NONE_AUTH_TYPE_REQUEST).orElse(true); + } + + // post SRA case. + // By default, SRA uses new HttpSigner, so we shouldn't use old non-SRA Signer, unless the customer has provided a signer + // override. + // But, if the operation was modeled as authTpye=None, we don't want to use the provided overridden Signer either. In + // post SRA, modeled authType=None would default to NoAuthAuthScheme. + // Note, for authType=None operation, technically, customer could override the AuthSchemeProvider and select a different + // AuthScheme (than NoAuthAuthScheme). In this case, we are choosing to use the customer's overridden Signer. + SelectedAuthScheme selectedAuthScheme = attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + return SignerOverrideUtils.isSignerOverridden(request, attributes) && + selectedAuthScheme != null && + !NoAuthAuthScheme.SCHEME_ID.equals(selectedAuthScheme.authSchemeOption().schemeId()); + } + + private static void putAuthSchemeResolutionAttributes(ExecutionAttributes executionAttributes, + SdkClientConfiguration clientConfig, + SdkRequest originalRequest) { + + // TODO(sra-identity-and-auth): When request-level auth scheme provider is added, use the request-level auth scheme + // provider if the customer specified an override, otherwise fall back to the one on the client. + AuthSchemeProvider authSchemeProvider = clientConfig.option(SdkClientOption.AUTH_SCHEME_PROVIDER); + + // Use auth schemes that the user specified at the request level with + // preference over those on the client. + // TODO(sra-identity-and-auth): The request level schemes should be "merged" with client level, with request preferred + // over client. + Map> authSchemes = clientConfig.option(SdkClientOption.AUTH_SCHEMES); + + IdentityProviders identityProviders = resolveIdentityProviders(originalRequest, clientConfig); + + executionAttributes + .putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER, authSchemeProvider) + .putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES, authSchemes) + .putAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS, identityProviders); + } + + // TODO(sra-identity-and-auth): This is hard coding the logic for the credentialsIdentityProvider from + // AwsRequestOverrideConfiguration. Currently, AwsRequestOverrideConfiguration does not support overriding the + // tokenIdentityProvider. When adding that support this method will need to be updated. + private static IdentityProviders resolveIdentityProviders(SdkRequest originalRequest, + SdkClientConfiguration clientConfig) { + IdentityProviders identityProviders = + clientConfig.option(SdkClientOption.IDENTITY_PROVIDERS); + + // identityProviders can be null, for new core with old client. In this case, even if AwsRequestOverrideConfiguration + // has credentialsIdentityProvider set (because it is in new core), it is ok to not setup IDENTITY_PROVIDERS, as old + // client won't have AUTH_SCHEME_PROVIDER/AUTH_SCHEMES set either, which are also needed for SRA logic. + if (identityProviders == null) { + return null; + } + return originalRequest.overrideConfiguration() .filter(c -> c instanceof AwsRequestOverrideConfiguration) .map(c -> (AwsRequestOverrideConfiguration) c) - .flatMap(AwsRequestOverrideConfiguration::credentialsProvider) - .orElse(defaultProvider); - } - - /** - * Request override signers take precedence over the default alternative, for instance what is specified in the - * client. Request override signers can also be modified by modifyRequest interceptors. - * - * @return The signer that will be used by the SDK to sign the request - */ - public static Signer resolveSigner(SdkRequest request, Signer defaultSigner) { - return request.overrideConfiguration() - .flatMap(RequestOverrideConfiguration::signer) - .orElse(defaultSigner); + .flatMap(AwsRequestOverrideConfiguration::credentialsIdentityProvider) + .map(identityProvider -> + identityProviders.copy(b -> b.putIdentityProvider(identityProvider))) + .orElse(identityProviders); } /** @@ -202,10 +259,6 @@ private static MetricCollector resolveMetricCollector(ClientExecutionParams defaultTokenProvider = + clientConfiguration.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER) == null + ? clientConfiguration.option(AwsClientOption.TOKEN_PROVIDER) + : clientConfiguration.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER); return TokenAuthorizationStrategy.builder() .request(request) .defaultSigner(defaultSigner) @@ -64,7 +73,8 @@ private TokenAuthorizationStrategy tokenAuthorizationStrategy() { private AwsCredentialsAuthorizationStrategy awsCredentialsAuthorizationStrategy() { Signer defaultSigner = clientConfiguration.option(SdkAdvancedClientOption.SIGNER); - AwsCredentialsProvider defaultCredentialsProvider = clientConfiguration.option(AwsClientOption.CREDENTIALS_PROVIDER); + IdentityProvider defaultCredentialsProvider = + clientConfiguration.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER); return AwsCredentialsAuthorizationStrategy.builder() .request(request) .defaultSigner(defaultSigner) diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/authcontext/AwsCredentialsAuthorizationStrategy.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/authcontext/AwsCredentialsAuthorizationStrategy.java index 01ad2dd5217b..ce64b5f40e75 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/authcontext/AwsCredentialsAuthorizationStrategy.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/authcontext/AwsCredentialsAuthorizationStrategy.java @@ -18,7 +18,7 @@ import java.time.Duration; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.auth.credentials.AwsCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.CredentialUtils; import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; import software.amazon.awssdk.core.RequestOverrideConfiguration; @@ -27,20 +27,27 @@ import software.amazon.awssdk.core.internal.util.MetricUtils; import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Pair; import software.amazon.awssdk.utils.Validate; /** * An authorization strategy for AWS Credentials that can resolve a compatible signer as * well as provide resolved AWS credentials as an execution attribute. + * + * @deprecated This is only used for compatibility with pre-SRA authorization logic. After we are comfortable that the new code + * paths are working, we should migrate old clients to the new code paths (where possible) and delete this code. */ +@Deprecated @SdkInternalApi public final class AwsCredentialsAuthorizationStrategy implements AuthorizationStrategy { private final SdkRequest request; private final Signer defaultSigner; - private final AwsCredentialsProvider defaultCredentialsProvider; + private final IdentityProvider defaultCredentialsProvider; private final MetricCollector metricCollector; public AwsCredentialsAuthorizationStrategy(Builder builder) { @@ -73,8 +80,9 @@ public Signer resolveSigner() { */ @Override public void addCredentialsToExecutionAttributes(ExecutionAttributes executionAttributes) { - AwsCredentialsProvider credentialsProvider = resolveCredentialsProvider(request, defaultCredentialsProvider); - AwsCredentials credentials = resolveCredentials(credentialsProvider, metricCollector); + IdentityProvider credentialsProvider = + resolveCredentialsProvider(request, defaultCredentialsProvider); + AwsCredentials credentials = CredentialUtils.toCredentials(resolveCredentials(credentialsProvider, metricCollector)); executionAttributes.putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, credentials); } @@ -84,22 +92,27 @@ public void addCredentialsToExecutionAttributes(ExecutionAttributes executionAtt * * @return The credentials provider that will be used by the SDK to resolve credentials */ - private static AwsCredentialsProvider resolveCredentialsProvider(SdkRequest originalRequest, - AwsCredentialsProvider defaultProvider) { + private static IdentityProvider resolveCredentialsProvider( + SdkRequest originalRequest, + IdentityProvider defaultProvider) { return originalRequest.overrideConfiguration() .filter(c -> c instanceof AwsRequestOverrideConfiguration) .map(c -> (AwsRequestOverrideConfiguration) c) - .flatMap(AwsRequestOverrideConfiguration::credentialsProvider) + .flatMap(AwsRequestOverrideConfiguration::credentialsIdentityProvider) .orElse(defaultProvider); } - private static AwsCredentials resolveCredentials(AwsCredentialsProvider credentialsProvider, - MetricCollector metricCollector) { + private static AwsCredentialsIdentity resolveCredentials( + IdentityProvider credentialsProvider, + MetricCollector metricCollector) { Validate.notNull(credentialsProvider, "No credentials provider exists to resolve credentials from."); - Pair measured = MetricUtils.measureDuration(credentialsProvider::resolveCredentials); + // TODO(sra-identity-and-auth): internal issue SMITHY-1677. avoid join for async clients. + Pair measured = + MetricUtils.measureDuration(() -> CompletableFutureUtils.joinLikeSync(credentialsProvider.resolveIdentity())); + metricCollector.reportMetric(CoreMetric.CREDENTIALS_FETCH_DURATION, measured.right()); - AwsCredentials credentials = measured.left(); + AwsCredentialsIdentity credentials = measured.left(); Validate.validState(credentials != null, "Credential providers must never return null."); return credentials; @@ -108,7 +121,7 @@ private static AwsCredentials resolveCredentials(AwsCredentialsProvider credenti public static final class Builder { private SdkRequest request; private Signer defaultSigner; - private AwsCredentialsProvider defaultCredentialsProvider; + private IdentityProvider defaultCredentialsProvider; private MetricCollector metricCollector; private Builder() { @@ -132,11 +145,11 @@ public Builder defaultSigner(Signer defaultSigner) { return this; } - public AwsCredentialsProvider defaultCredentialsProvider() { + public IdentityProvider defaultCredentialsProvider() { return this.defaultCredentialsProvider; } - public Builder defaultCredentialsProvider(AwsCredentialsProvider defaultCredentialsProvider) { + public Builder defaultCredentialsProvider(IdentityProvider defaultCredentialsProvider) { this.defaultCredentialsProvider = defaultCredentialsProvider; return this; } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/authcontext/TokenAuthorizationStrategy.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/authcontext/TokenAuthorizationStrategy.java index c03ae189e7ae..69b488c41467 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/authcontext/TokenAuthorizationStrategy.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/authcontext/TokenAuthorizationStrategy.java @@ -18,7 +18,7 @@ import java.time.Duration; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.auth.token.credentials.SdkToken; -import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; +import software.amazon.awssdk.auth.token.credentials.internal.TokenUtils; import software.amazon.awssdk.auth.token.signer.SdkTokenExecutionAttribute; import software.amazon.awssdk.core.RequestOverrideConfiguration; import software.amazon.awssdk.core.SdkRequest; @@ -26,20 +26,27 @@ import software.amazon.awssdk.core.internal.util.MetricUtils; import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Pair; import software.amazon.awssdk.utils.Validate; /** * An authorization strategy for tokens that can resolve a compatible signer as * well as provide a resolved token as an execution attribute. + * + * @deprecated This is only used for compatibility with pre-SRA authorization logic. After we are comfortable that the new code + * paths are working, we should migrate old clients to the new code paths (where possible) and delete this code. */ +@Deprecated @SdkInternalApi public final class TokenAuthorizationStrategy implements AuthorizationStrategy { private final SdkRequest request; private final Signer defaultSigner; - private final SdkTokenProvider defaultTokenProvider; + private final IdentityProvider defaultTokenProvider; private final MetricCollector metricCollector; public TokenAuthorizationStrategy(Builder builder) { @@ -71,25 +78,28 @@ public Signer resolveSigner() { */ @Override public void addCredentialsToExecutionAttributes(ExecutionAttributes executionAttributes) { - SdkToken credentials = resolveToken(defaultTokenProvider, metricCollector); - executionAttributes.putAttribute(SdkTokenExecutionAttribute.SDK_TOKEN, credentials); + SdkToken token = TokenUtils.toSdkToken(resolveToken(defaultTokenProvider, metricCollector)); + executionAttributes.putAttribute(SdkTokenExecutionAttribute.SDK_TOKEN, token); } - private static SdkToken resolveToken(SdkTokenProvider tokenProvider, MetricCollector metricCollector) { + private static TokenIdentity resolveToken(IdentityProvider tokenProvider, + MetricCollector metricCollector) { Validate.notNull(tokenProvider, "No token provider exists to resolve a token from."); - Pair measured = MetricUtils.measureDuration(tokenProvider::resolveToken); + // TODO(sra-identity-and-auth): internal issue SMITHY-1677. avoid join for async clients. + Pair measured = + MetricUtils.measureDuration(() -> CompletableFutureUtils.joinLikeSync(tokenProvider.resolveIdentity())); metricCollector.reportMetric(CoreMetric.TOKEN_FETCH_DURATION, measured.right()); - SdkToken credentials = measured.left(); + TokenIdentity token = measured.left(); - Validate.validState(credentials != null, "Token providers must never return null."); - return credentials; + Validate.validState(token != null, "Token providers must never return null."); + return token; } public static final class Builder { private SdkRequest request; private Signer defaultSigner; - private SdkTokenProvider defaultTokenProvider; + private IdentityProvider defaultTokenProvider; private MetricCollector metricCollector; private Builder() { @@ -113,11 +123,11 @@ public Builder defaultSigner(Signer defaultSigner) { return this; } - public SdkTokenProvider defaultTokenProvider() { + public IdentityProvider defaultTokenProvider() { return this.defaultTokenProvider; } - public Builder defaultTokenProvider(SdkTokenProvider defaultTokenProvider) { + public Builder defaultTokenProvider(IdentityProvider defaultTokenProvider) { this.defaultTokenProvider = defaultTokenProvider; return this; } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/client/config/AwsClientOptionValidation.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/client/config/AwsClientOptionValidation.java index 5dce8619099a..d72f41356aa6 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/client/config/AwsClientOptionValidation.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/client/config/AwsClientOptionValidation.java @@ -39,8 +39,7 @@ public static void validateSyncClientOptions(SdkClientConfiguration c) { } private static void validateClientOptions(SdkClientConfiguration c) { - require("credentialsProvider", c.option(AwsClientOption.CREDENTIALS_PROVIDER)); - + require("credentialsProvider", c.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER)); require("overrideConfiguration.advancedOption[AWS_REGION]", c.option(AwsClientOption.AWS_REGION)); require("overrideConfiguration.advancedOption[SIGNING_REGION]", c.option(AwsClientOption.SIGNING_REGION)); require("overrideConfiguration.advancedOption[SERVICE_SIGNING_NAME]", diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/presigner/SdkPresigner.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/presigner/SdkPresigner.java index f39bbb258b70..2d4fd8c9c1a4 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/presigner/SdkPresigner.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/presigner/SdkPresigner.java @@ -18,6 +18,8 @@ import java.net.URI; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.utils.SdkAutoCloseable; @@ -72,8 +74,37 @@ interface Builder { *

If the credentials are not found in any of the locations above, an exception will be thrown at {@link #build()} * time. *

+ * + *

The last of {@link #credentialsProvider(AwsCredentialsProvider)} or {@link #credentialsProvider(IdentityProvider)} + * wins.

+ */ + default Builder credentialsProvider(AwsCredentialsProvider credentialsProvider) { + return credentialsProvider((IdentityProvider) credentialsProvider); + } + + /** + * Configure the credentials that should be used to authenticate with AWS. + * + *

The default provider will attempt to identify the credentials automatically using the following checks: + *

    + *
  1. Java System Properties - aws.accessKeyId and aws.secretAccessKey
  2. + *
  3. Environment Variables - AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
  4. + *
  5. Credential profiles file at the default location (~/.aws/credentials) shared by all AWS SDKs and the AWS CLI
  6. + *
  7. Credentials delivered through the Amazon EC2 container service if AWS_CONTAINER_CREDENTIALS_RELATIVE_URI + * environment variable is set and security manager has permission to access the variable.
  8. + *
  9. Instance profile credentials delivered through the Amazon EC2 metadata service
  10. + *
+ * + *

If the credentials are not found in any of the locations above, an exception will be thrown at {@link #build()} + * time. + *

+ * + *

The last of {@link #credentialsProvider(AwsCredentialsProvider)} or {@link #credentialsProvider(IdentityProvider)} + * wins.

*/ - Builder credentialsProvider(AwsCredentialsProvider credentialsProvider); + default Builder credentialsProvider(IdentityProvider credentialsProvider) { + throw new UnsupportedOperationException(); + } /** * Configure whether the SDK should use the AWS dualstack endpoint. diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/util/SignerOverrideUtils.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/util/SignerOverrideUtils.java index cade1e359b18..8ec31e32bd15 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/util/SignerOverrideUtils.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/util/SignerOverrideUtils.java @@ -36,12 +36,20 @@ public final class SignerOverrideUtils { private SignerOverrideUtils() { } + /** + * @deprecated No longer used by modern clients after migration to reference architecture + */ + @Deprecated public static SdkRequest overrideSignerIfNotOverridden(SdkRequest request, ExecutionAttributes executionAttributes, Signer signer) { return overrideSignerIfNotOverridden(request, executionAttributes, () -> signer); } - + + /** + * @deprecated No longer used by modern clients after migration to reference architecture + */ + @Deprecated public static SdkRequest overrideSignerIfNotOverridden(SdkRequest request, ExecutionAttributes executionAttributes, Supplier signer) { @@ -52,13 +60,17 @@ public static SdkRequest overrideSignerIfNotOverridden(SdkRequest request, } public static boolean isSignerOverridden(SdkRequest request, ExecutionAttributes executionAttributes) { - Optional isClientSignerOverridden = Optional.ofNullable( - executionAttributes.getAttribute(SdkExecutionAttribute.SIGNER_OVERRIDDEN)); + boolean isClientSignerOverridden = + Boolean.TRUE.equals(executionAttributes.getAttribute(SdkExecutionAttribute.SIGNER_OVERRIDDEN)); Optional requestSigner = request.overrideConfiguration() .flatMap(RequestOverrideConfiguration::signer); - return isClientSignerOverridden.isPresent() || requestSigner.isPresent(); + return isClientSignerOverridden || requestSigner.isPresent(); } + /** + * @deprecated No longer used by modern clients after migration to reference architecture + */ + @Deprecated private static SdkRequest overrideSigner(SdkRequest request, Signer signer) { return request.overrideConfiguration() .flatMap(config -> config.signer() @@ -66,6 +78,10 @@ private static SdkRequest overrideSigner(SdkRequest request, Signer signer) { .orElseGet(() -> createNewRequest(request, signer)); } + /** + * @deprecated No longer used by modern clients after migration to reference architecture + */ + @Deprecated private static SdkRequest createNewRequest(SdkRequest request, Signer signer) { AwsRequest awsRequest = (AwsRequest) request; diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/AwsRequestOverrideConfigurationTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/AwsRequestOverrideConfigurationTest.java new file mode 100644 index 000000000000..ef009f8242f4 --- /dev/null +++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/AwsRequestOverrideConfigurationTest.java @@ -0,0 +1,54 @@ +/* + * 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.awscore; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; + +public class AwsRequestOverrideConfigurationTest { + + @Test + public void testCredentialsProviderWorksWithBothOldAndNewInterfaceTypes() { + AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( + AwsBasicCredentials.create("akid","skid")); + + AwsRequestOverrideConfiguration configuration1 = AwsRequestOverrideConfiguration + .builder().credentialsProvider(credentialsProvider).build(); + + AwsRequestOverrideConfiguration configuration2 = AwsRequestOverrideConfiguration + .builder().credentialsProvider((IdentityProvider) credentialsProvider).build(); + + assertCredentialsEqual(configuration1.credentialsProvider().get(), configuration1.credentialsIdentityProvider().get()); + assertCredentialsEqual(configuration2.credentialsProvider().get(), configuration2.credentialsIdentityProvider().get()); + assertCredentialsEqual(configuration1.credentialsProvider().get(), configuration2.credentialsIdentityProvider().get()); + assertCredentialsEqual(configuration2.credentialsProvider().get(), configuration1.credentialsIdentityProvider().get()); + } + + private void assertCredentialsEqual(AwsCredentialsProvider credentialsProvider, + IdentityProvider identityProvider) { + AwsCredentials creds1 = credentialsProvider.resolveCredentials(); + AwsCredentialsIdentity creds2 = identityProvider.resolveIdentity().join(); + assertThat(creds1.accessKeyId()).isEqualTo(creds2.accessKeyId()); + assertThat(creds1.secretAccessKey()).isEqualTo(creds2.secretAccessKey()); + } +} diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java index 18410f09b1fb..ba04424fee93 100644 --- a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java +++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java @@ -237,7 +237,6 @@ public void clientBuilderFieldsHaveBeanEquivalents() throws Exception { assertThat(property.getWriteMethod()).as(propertyName + " setter").isNotNull(); }); }); - } private AwsClientBuilder testClientBuilder() { diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/utils/HttpTestUtils.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/utils/HttpTestUtils.java index e92e771f390f..37577e99376e 100644 --- a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/utils/HttpTestUtils.java +++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/utils/HttpTestUtils.java @@ -54,7 +54,7 @@ public static SdkClientConfiguration testClientConfiguration() { .option(SdkClientOption.RETRY_POLICY, RetryPolicy.defaultRetryPolicy()) .option(SdkClientOption.ADDITIONAL_HTTP_HEADERS, new HashMap<>()) .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false) - .option(AwsClientOption.CREDENTIALS_PROVIDER, DefaultCredentialsProvider.create()) + .option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER, DefaultCredentialsProvider.create()) .option(SdkAdvancedClientOption.SIGNER, new NoOpSigner()) .option(SdkAdvancedClientOption.USER_AGENT_PREFIX, "") .option(SdkAdvancedClientOption.USER_AGENT_SUFFIX, "") diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilderTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilderTest.java index 67ef23a8426c..12b509ec69d1 100644 --- a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilderTest.java +++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilderTest.java @@ -17,25 +17,29 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SdkResponse; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.checksums.ChecksumSpecs; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; @@ -49,6 +53,13 @@ import software.amazon.awssdk.core.interceptor.trait.HttpChecksum; import software.amazon.awssdk.core.internal.util.HttpChecksumUtils; import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; +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.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.profiles.ProfileFile; @RunWith(MockitoJUnitRunner.class) @@ -61,12 +72,24 @@ public class AwsExecutionContextBuilderTest { ExecutionInterceptor interceptor; @Mock - Signer defaultSigner, clientOverrideSigner; + IdentityProvider defaultCredentialsProvider; + + @Mock + Signer defaultSigner; + + @Mock + Signer clientOverrideSigner; + + @Mock + Map> defaultAuthSchemes; @Before public void setUp() throws Exception { when(sdkRequest.overrideConfiguration()).thenReturn(Optional.empty()); when(interceptor.modifyRequest(any(), any())).thenReturn(sdkRequest); + when(defaultCredentialsProvider.resolveIdentity()).thenAnswer( + invocationOnMock -> CompletableFuture.completedFuture(AwsCredentialsIdentity.create("ak", "sk"))); + } @Test @@ -101,17 +124,45 @@ public void verifyCoreExecutionAttributesTakePrecedence() { assertThat(executionContext.executionAttributes().getAttribute(SdkExecutionAttribute.SERVICE_NAME)).isEqualTo("DoNotOverrideService"); } + // pre SRA, AuthorizationStrategy would setup the signer and resolve identity. @Test - public void signing_ifNoOverrides_assignDefaultSigner() { + public void preSra_signing_ifNoOverrides_assignDefaultSigner_resolveIdentity() { ExecutionContext executionContext = AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), - testClientConfiguration().build()); + preSraClientConfiguration().build()); assertThat(executionContext.signer()).isEqualTo(defaultSigner); + verify(defaultCredentialsProvider, times(1)).resolveIdentity(); + } + + // This is post SRA case. This is asserting that AuthorizationStrategy is not used. + @Test + public void postSra_ifNoOverrides_doesNotResolveIdentity_doesNotAssignSigner() { + ExecutionContext executionContext = + AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), + testClientConfiguration().build()); + + assertThat(executionContext.signer()).isNull(); + verify(defaultCredentialsProvider, times(0)).resolveIdentity(); + } + + @Test + public void preSra_signing_ifClientOverride_assignClientOverrideSigner_resolveIdentity() { + Optional overrideConfiguration = Optional.of(AwsRequestOverrideConfiguration.builder() + .signer(clientOverrideSigner) + .build()); + when(sdkRequest.overrideConfiguration()).thenReturn(overrideConfiguration); + + ExecutionContext executionContext = + AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), + preSraClientConfiguration().build()); + + assertThat(executionContext.signer()).isEqualTo(clientOverrideSigner); + verify(defaultCredentialsProvider, times(1)).resolveIdentity(); } @Test - public void signing_ifClientOverride_assignClientOverrideSigner() { + public void postSra_signing_ifClientOverride_assignClientOverrideSigner_resolveIdentity() { Optional overrideConfiguration = Optional.of(AwsRequestOverrideConfiguration.builder() .signer(clientOverrideSigner) .build()); @@ -122,6 +173,102 @@ public void signing_ifClientOverride_assignClientOverrideSigner() { testClientConfiguration().build()); assertThat(executionContext.signer()).isEqualTo(clientOverrideSigner); + verify(defaultCredentialsProvider, times(1)).resolveIdentity(); + } + + @Test + public void preSra_authTypeNone_doesNotAssignSigner_doesNotResolveIdentity() { + SdkClientConfiguration.Builder clientConfig = preSraClientConfiguration(); + clientConfig.option(SdkClientOption.EXECUTION_ATTRIBUTES) + // yes, our code would put false instead of true + .putAttribute(SdkInternalExecutionAttribute.IS_NONE_AUTH_TYPE_REQUEST, false); + + ExecutionContext executionContext = + AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), + clientConfig.build()); + + assertThat(executionContext.signer()).isNull(); + verify(defaultCredentialsProvider, times(0)).resolveIdentity(); + } + + @Test + public void postSra_authTypeNone_doesNotAssignSigner_doesNotResolveIdentity() { + SdkClientConfiguration.Builder clientConfig = noAuthAuthSchemeClientConfiguration(); + + ExecutionContext executionContext = + AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), + clientConfig.build()); + + assertThat(executionContext.signer()).isNull(); + verify(defaultCredentialsProvider, times(0)).resolveIdentity(); + } + + @Test + public void preSra_authTypeNone_signerClientOverride_doesNotAssignSigner_doesNotResolveIdentity() { + SdkClientConfiguration.Builder clientConfig = preSraClientConfiguration(); + clientConfig.option(SdkClientOption.EXECUTION_ATTRIBUTES) + // yes, our code would put false instead of true + .putAttribute(SdkInternalExecutionAttribute.IS_NONE_AUTH_TYPE_REQUEST, false); + clientConfig.option(SdkAdvancedClientOption.SIGNER, this.clientOverrideSigner) + .option(SdkClientOption.SIGNER_OVERRIDDEN, true); + + ExecutionContext executionContext = + AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), + clientConfig.build()); + + assertThat(executionContext.signer()).isNull(); + verify(defaultCredentialsProvider, times(0)).resolveIdentity(); + } + + @Test + public void postSra_authTypeNone_signerClientOverride_doesNotAssignSigner_doesNotResolveIdentity() { + SdkClientConfiguration.Builder clientConfig = noAuthAuthSchemeClientConfiguration(); + clientConfig.option(SdkAdvancedClientOption.SIGNER, this.clientOverrideSigner) + .option(SdkClientOption.SIGNER_OVERRIDDEN, true); + + ExecutionContext executionContext = + AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), + clientConfig.build()); + + assertThat(executionContext.signer()).isNull(); + verify(defaultCredentialsProvider, times(0)).resolveIdentity(); + } + + @Test + public void preSra_authTypeNone_signerRequestOverride_doesNotAssignSigner_doesNotResolveIdentity() { + SdkClientConfiguration.Builder clientConfig = preSraClientConfiguration(); + clientConfig.option(SdkClientOption.EXECUTION_ATTRIBUTES) + // yes, our code would put false instead of true + .putAttribute(SdkInternalExecutionAttribute.IS_NONE_AUTH_TYPE_REQUEST, false); + + Optional overrideConfiguration = Optional.of(AwsRequestOverrideConfiguration.builder() + .signer(clientOverrideSigner) + .build()); + when(sdkRequest.overrideConfiguration()).thenReturn(overrideConfiguration); + + ExecutionContext executionContext = + AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), + clientConfig.build()); + + assertThat(executionContext.signer()).isNull(); + verify(defaultCredentialsProvider, times(0)).resolveIdentity(); + } + + @Test + public void postSra_authTypeNone_signerRequestOverride_doesNotAssignSigner_doesNotResolveIdentity() { + SdkClientConfiguration.Builder clientConfig = noAuthAuthSchemeClientConfiguration(); + + Optional overrideConfiguration = Optional.of(AwsRequestOverrideConfiguration.builder() + .signer(clientOverrideSigner) + .build()); + when(sdkRequest.overrideConfiguration()).thenReturn(overrideConfiguration); + + ExecutionContext executionContext = + AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), + clientConfig.build()); + + assertThat(executionContext.signer()).isNull(); + verify(defaultCredentialsProvider, times(0)).resolveIdentity(); } @Test @@ -140,7 +287,7 @@ public void invokeInterceptorsAndCreateExecutionContext_noHttpChecksumTrait_reso } @Test - public void invokeInterceptorsAndCreateExecutionContext_singleExecutionContext_resolvesChecksumSpecsOnce() { + public void invokeInterceptorsAndCreateExecutionContext_singleExecutionContext_resolvesEqualChecksumSpecs() { HttpChecksum httpCrc32Checksum = HttpChecksum.builder().requestAlgorithm("crc32").isRequestStreaming(true).build(); ClientExecutionParams executionParams = clientExecutionParams() @@ -154,11 +301,11 @@ public void invokeInterceptorsAndCreateExecutionContext_singleExecutionContext_r ChecksumSpecs checksumSpecs1 = HttpChecksumUtils.checksumSpecWithRequestAlgorithm(executionAttributes).get(); ChecksumSpecs checksumSpecs2 = HttpChecksumUtils.checksumSpecWithRequestAlgorithm(executionAttributes).get(); - assertThat(checksumSpecs1).isSameAs(checksumSpecs2); + assertThat(checksumSpecs1).isEqualTo(checksumSpecs2); } @Test - public void invokeInterceptorsAndCreateExecutionContext_multipleExecutionContexts_resolvesChecksumSpecsOncePerContext() { + public void invokeInterceptorsAndCreateExecutionContext_multipleExecutionContexts_resolvesEqualChecksumSpecs() { HttpChecksum httpCrc32Checksum = HttpChecksum.builder().requestAlgorithm("crc32").isRequestStreaming(true).build(); ClientExecutionParams executionParams = clientExecutionParams() .putExecutionAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM, httpCrc32Checksum); @@ -177,8 +324,8 @@ public void invokeInterceptorsAndCreateExecutionContext_multipleExecutionContext ChecksumSpecs checksumSpecs2 = HttpChecksumUtils.checksumSpecWithRequestAlgorithm(executionAttributes2).get(); ChecksumSpecs checksumSpecs3 = HttpChecksumUtils.checksumSpecWithRequestAlgorithm(executionAttributes2).get(); - assertThat(checksumSpecs1).isNotSameAs(checksumSpecs2); - assertThat(checksumSpecs2).isSameAs(checksumSpecs3); + assertThat(checksumSpecs1).isEqualTo(checksumSpecs2); + assertThat(checksumSpecs2).isEqualTo(checksumSpecs3); } @Test @@ -196,7 +343,49 @@ public void invokeInterceptorsAndCreateExecutionContext_profileFileSupplier_stor assertThat(profileFileSupplier).isSameAs(executionAttributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER)); } - + + @Test + public void invokeInterceptorsAndCreateExecutionContext_withoutIdentityProviders_assignsNull() { + ClientExecutionParams executionParams = clientExecutionParams(); + + ExecutionContext executionContext = + AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams, + testClientConfiguration().build()); + + ExecutionAttributes executionAttributes = executionContext.executionAttributes(); + assertThat(executionAttributes.getAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS)).isNull(); + } + + @Test + public void invokeInterceptorsAndCreateExecutionContext_requestOverrideForIdentityProvider_updatesIdentityProviders() { + IdentityProvider clientCredentialsProvider = + StaticCredentialsProvider.create(AwsBasicCredentials.create("foo", "bar")); + IdentityProviders identityProviders = + IdentityProviders.builder().putIdentityProvider(clientCredentialsProvider).build(); + SdkClientConfiguration clientConfig = testClientConfiguration() + .option(SdkClientOption.IDENTITY_PROVIDERS, identityProviders) + .build(); + + IdentityProvider requestCredentialsProvider = + StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")); + Optional overrideConfiguration = + Optional.of(AwsRequestOverrideConfiguration.builder().credentialsProvider(requestCredentialsProvider).build()); + when(sdkRequest.overrideConfiguration()).thenReturn(overrideConfiguration); + + ClientExecutionParams executionParams = clientExecutionParams(); + + ExecutionContext executionContext = + AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams, clientConfig); + + IdentityProviders actualIdentityProviders = + executionContext.executionAttributes().getAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS); + + IdentityProvider actualIdentityProvider = + actualIdentityProviders.identityProvider(AwsCredentialsIdentity.class); + + assertThat(actualIdentityProvider).isSameAs(requestCredentialsProvider); + } + private ClientExecutionParams clientExecutionParams() { return new ClientExecutionParams() .withInput(sdkRequest) @@ -205,11 +394,44 @@ private ClientExecutionParams clientExecutionParams() { } private SdkClientConfiguration.Builder testClientConfiguration() { + // In real SRA case, SelectedAuthScheme is setup as an executionAttribute by {Service}AuthSchemeInterceptor that is setup + // in EXECUTION_INTERCEPTORS. But, faking it here for unit test, by already setting SELECTED_AUTH_SCHEME into the + // executionAttributes. + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(AwsCredentialsIdentity.create("ak", "sk")), + mock(HttpSigner.class), + AuthSchemeOption.builder().schemeId("blah-value-does-not-matter").build() + ); + ExecutionAttributes executionAttributes = + ExecutionAttributes.builder() + .put(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, selectedAuthScheme) + .build(); + List interceptorList = Collections.singletonList(interceptor); return SdkClientConfiguration.builder() - .option(SdkClientOption.EXECUTION_INTERCEPTORS, new ArrayList<>()) .option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptorList) - .option(AwsClientOption.CREDENTIALS_PROVIDER, DefaultCredentialsProvider.create()) - .option(SdkAdvancedClientOption.SIGNER, this.defaultSigner); + .option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER, defaultCredentialsProvider) + .option(SdkClientOption.AUTH_SCHEMES, defaultAuthSchemes) + .option(SdkClientOption.EXECUTION_ATTRIBUTES, executionAttributes); + } + + private SdkClientConfiguration.Builder noAuthAuthSchemeClientConfiguration() { + SdkClientConfiguration.Builder clientConfig = testClientConfiguration(); + SelectedAuthScheme selectedNoAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(AwsCredentialsIdentity.create("ak", "sk")), + mock(HttpSigner.class), + AuthSchemeOption.builder().schemeId(NoAuthAuthScheme.SCHEME_ID).build() + ); + clientConfig.option(SdkClientOption.EXECUTION_ATTRIBUTES) + .putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, selectedNoAuthScheme); + return clientConfig; + } + + private SdkClientConfiguration.Builder preSraClientConfiguration() { + SdkClientConfiguration.Builder clientConfiguration = testClientConfiguration(); + clientConfiguration.option(SdkClientOption.EXECUTION_ATTRIBUTES) + .putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, null); + return clientConfiguration.option(SdkClientOption.AUTH_SCHEMES, null) + .option(SdkAdvancedClientOption.SIGNER, this.defaultSigner); } } diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/authcontext/AwsCredentialsAuthorizationStrategyTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/authcontext/AwsCredentialsAuthorizationStrategyTest.java index 282e048052dc..884e80d5ae28 100644 --- a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/authcontext/AwsCredentialsAuthorizationStrategyTest.java +++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/authcontext/AwsCredentialsAuthorizationStrategyTest.java @@ -17,7 +17,6 @@ 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.when; import java.util.Optional; @@ -26,8 +25,10 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; import software.amazon.awssdk.core.SdkRequest; @@ -41,14 +42,15 @@ public class AwsCredentialsAuthorizationStrategyTest { @Mock SdkRequest sdkRequest; @Mock Signer defaultSigner; @Mock Signer requestOverrideSigner; - @Mock AwsCredentialsProvider credentialsProvider; - @Mock AwsCredentials credentials; + AwsCredentialsProvider credentialsProvider; + AwsCredentials credentials; @Mock MetricCollector metricCollector; @Before public void setUp() throws Exception { when(sdkRequest.overrideConfiguration()).thenReturn(Optional.empty()); - when(credentialsProvider.resolveCredentials()).thenReturn(credentials); + credentials = AwsBasicCredentials.create("foo", "bar"); + credentialsProvider = StaticCredentialsProvider.create(credentials); } @Test diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/authcontext/TokenAuthorizationStrategyTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/authcontext/TokenAuthorizationStrategyTest.java index a21108ce45cc..b15efe2306a8 100644 --- a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/authcontext/TokenAuthorizationStrategyTest.java +++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/authcontext/TokenAuthorizationStrategyTest.java @@ -27,6 +27,7 @@ import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.auth.token.credentials.SdkToken; import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; +import software.amazon.awssdk.auth.token.credentials.StaticTokenProvider; import software.amazon.awssdk.auth.token.signer.SdkTokenExecutionAttribute; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; import software.amazon.awssdk.awscore.internal.token.TestToken; @@ -40,18 +41,18 @@ public class TokenAuthorizationStrategyTest { private static final String TOKEN_VALUE = "token_value"; private SdkToken token; + private SdkTokenProvider tokenProvider; @Mock SdkRequest sdkRequest; @Mock Signer defaultSigner; @Mock Signer requestOverrideSigner; - @Mock SdkTokenProvider tokenProvider; @Mock MetricCollector metricCollector; @Before public void setUp() throws Exception { token = TestToken.builder().token(TOKEN_VALUE).build(); + tokenProvider = StaticTokenProvider.create(token); when(sdkRequest.overrideConfiguration()).thenReturn(Optional.empty()); - when(tokenProvider.resolveToken()).thenReturn(token); } @Test diff --git a/core/checksums-spi/pom.xml b/core/checksums-spi/pom.xml new file mode 100644 index 000000000000..1a18baebe4aa --- /dev/null +++ b/core/checksums-spi/pom.xml @@ -0,0 +1,60 @@ + + + + + 4.0.0 + + + software.amazon.awssdk + core + 2.21.0-SNAPSHOT + + + checksums-spi + AWS Java SDK :: Checksums SPI + + The AWS SDK for Java - Checksums SPI module contains checksum interfaces that are used by other modules + in the library. + + https://aws.amazon.com/sdkforjava + + + + software.amazon.awssdk + annotations + ${awsjavasdk.version} + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + software.amazon.awssdk.checksums.spi + + + + + + + + diff --git a/core/checksums-spi/src/main/java/software/amazon/awssdk/checksums/spi/ChecksumAlgorithm.java b/core/checksums-spi/src/main/java/software/amazon/awssdk/checksums/spi/ChecksumAlgorithm.java new file mode 100644 index 000000000000..e0c3435c67f1 --- /dev/null +++ b/core/checksums-spi/src/main/java/software/amazon/awssdk/checksums/spi/ChecksumAlgorithm.java @@ -0,0 +1,31 @@ +/* + * 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.checksums.spi; + +import software.amazon.awssdk.annotations.SdkProtectedApi; + +/** + * An interface for declaring the implementation of a checksum. + */ +@SdkProtectedApi +public interface ChecksumAlgorithm { + /** + * The ID of the checksum algorithm. This is matched against algorithm + * names used in smithy traits + * (e.g. "CRC32C" from the aws.protocols#HTTPChecksum smithy trait) + */ + String algorithmId(); +} diff --git a/core/checksums/pom.xml b/core/checksums/pom.xml new file mode 100644 index 000000000000..5d079f174c51 --- /dev/null +++ b/core/checksums/pom.xml @@ -0,0 +1,71 @@ + + + + + 4.0.0 + + + software.amazon.awssdk + core + 2.21.0-SNAPSHOT + + + checksums + AWS Java SDK :: Checksums + + The AWS SDK for Java - Checksums module contains checksums and related items that are used by other modules in + the library. + + https://aws.amazon.com/sdkforjava + + + + software.amazon.awssdk + annotations + ${awsjavasdk.version} + + + software.amazon.awssdk + checksums-spi + ${awsjavasdk.version} + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + software.amazon.awssdk.checksums + + + + + + + + diff --git a/core/checksums/src/main/java/software/amazon/awssdk/checksums/DefaultChecksumAlgorithm.java b/core/checksums/src/main/java/software/amazon/awssdk/checksums/DefaultChecksumAlgorithm.java new file mode 100644 index 000000000000..b83e049bac80 --- /dev/null +++ b/core/checksums/src/main/java/software/amazon/awssdk/checksums/DefaultChecksumAlgorithm.java @@ -0,0 +1,47 @@ +/* + * 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.checksums; + +import java.util.concurrent.ConcurrentHashMap; +import software.amazon.awssdk.annotations.SdkProtectedApi; +import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; + +/** + * An enumeration of supported checksum algorithms. + */ +@SdkProtectedApi +public final class DefaultChecksumAlgorithm { + public static final ChecksumAlgorithm CRC32C = of("CRC32C"); + public static final ChecksumAlgorithm CRC32 = of("CRC32"); + public static final ChecksumAlgorithm MD5 = of("MD5"); + public static final ChecksumAlgorithm SHA256 = of("SHA256"); + public static final ChecksumAlgorithm SHA1 = of("SHA1"); + + private DefaultChecksumAlgorithm() { + } + + private static ChecksumAlgorithm of(String name) { + return ChecksumAlgorithmsCache.put(name); + } + + private static final class ChecksumAlgorithmsCache { + private static final ConcurrentHashMap VALUES = new ConcurrentHashMap<>(); + + private static ChecksumAlgorithm put(String value) { + return VALUES.computeIfAbsent(value, v -> () -> v); + } + } +} diff --git a/core/checksums/src/test/java/software/amazon/awssdk/checksums/DefaultChecksumAlgorithmTest.java b/core/checksums/src/test/java/software/amazon/awssdk/checksums/DefaultChecksumAlgorithmTest.java new file mode 100644 index 000000000000..ac0a2b7791b7 --- /dev/null +++ b/core/checksums/src/test/java/software/amazon/awssdk/checksums/DefaultChecksumAlgorithmTest.java @@ -0,0 +1,49 @@ +/* + * 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.checksums; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class DefaultChecksumAlgorithmTest { + + @Test + public void hasCRC32C() { + assertEquals("CRC32C", DefaultChecksumAlgorithm.CRC32C.algorithmId()); + } + + @Test + public void hasCRC32() { + assertEquals("CRC32", DefaultChecksumAlgorithm.CRC32.algorithmId()); + } + + @Test + public void hasMD5() { + assertEquals("MD5", DefaultChecksumAlgorithm.MD5.algorithmId()); + } + + @Test + public void hasSHA256() { + assertEquals("SHA256", DefaultChecksumAlgorithm.SHA256.algorithmId()); + } + + @Test + public void hasSHA1() { + assertEquals("SHA1", DefaultChecksumAlgorithm.SHA1.algorithmId()); + } +} + diff --git a/core/crt-core/pom.xml b/core/crt-core/pom.xml index 1a3bef60e863..2f25edb8a37b 100644 --- a/core/crt-core/pom.xml +++ b/core/crt-core/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk core - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT crt-core diff --git a/core/endpoints-spi/pom.xml b/core/endpoints-spi/pom.xml index 4b7c60dcc7ca..d7e8e8db678a 100644 --- a/core/endpoints-spi/pom.xml +++ b/core/endpoints-spi/pom.xml @@ -20,7 +20,7 @@ core software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/core/http-auth-aws-crt/pom.xml b/core/http-auth-aws-crt/pom.xml new file mode 100644 index 000000000000..3ff767974176 --- /dev/null +++ b/core/http-auth-aws-crt/pom.xml @@ -0,0 +1,78 @@ + + + + + 4.0.0 + + + software.amazon.awssdk + core + 2.21.0-SNAPSHOT + + + http-auth-aws-crt + AWS Java SDK :: HTTP Auth AWS CRT + + The AWS SDK for Java - HTTP Auth AWS CRT module holds the AWS Common Runtime based implementations + that are used for authentication with HTTP services. + + https://aws.amazon.com/sdkforjava + + + + software.amazon.awssdk + annotations + ${awsjavasdk.version} + + + software.amazon.awssdk.crt + aws-crt + ${awscrt.version} + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + software.amazon.awssdk.http.auth.aws.crt + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + software.amazon.awssdk.crt:aws-crt + + + + + + + diff --git a/core/http-auth-aws-crt/src/main/java/software/amazon/awssdk/http/auth/aws/crt/HttpAuthAwsCrt.java b/core/http-auth-aws-crt/src/main/java/software/amazon/awssdk/http/auth/aws/crt/HttpAuthAwsCrt.java new file mode 100644 index 000000000000..2c0130134139 --- /dev/null +++ b/core/http-auth-aws-crt/src/main/java/software/amazon/awssdk/http/auth/aws/crt/HttpAuthAwsCrt.java @@ -0,0 +1,27 @@ +/* + * 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.http.auth.aws.crt; + +import software.amazon.awssdk.annotations.SdkProtectedApi; + +/** + * This is a place-holder class for this module, http-auth-aws-crt. In the event that we decide to move CRT-v4a signer + * logic back to this dedicated module, no issues will arise. This module should be an optional dependency in consumers + * (http-auth-aws), and should bring in the required dependencies (aws-crt). + */ +@SdkProtectedApi +public final class HttpAuthAwsCrt { +} diff --git a/core/http-auth-aws-eventstream/pom.xml b/core/http-auth-aws-eventstream/pom.xml new file mode 100644 index 000000000000..548b4721990f --- /dev/null +++ b/core/http-auth-aws-eventstream/pom.xml @@ -0,0 +1,76 @@ + + + + + 4.0.0 + + + software.amazon.awssdk + core + 2.21.0-SNAPSHOT + + + http-auth-aws-eventstream + AWS Java SDK :: HTTP Auth Event Stream + + The AWS SDK for Java - HTTP Auth AWS Event Stream module contains interfaces and implementations for AWS + specific authentication of event streams in HTTP services. + + https://aws.amazon.com/sdkforjava + + + + software.amazon.awssdk + annotations + ${awsjavasdk.version} + + + software.amazon.eventstream + eventstream + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + software.amazon.awssdk.http.auth.aws.eventstream + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + software.amazon.eventstream:eventstream + + + + + + + diff --git a/core/http-auth-aws-eventstream/src/main/java/software/amazon/awssdk/http/auth/aws/eventstream/HttpAuthAwsEventStream.java b/core/http-auth-aws-eventstream/src/main/java/software/amazon/awssdk/http/auth/aws/eventstream/HttpAuthAwsEventStream.java new file mode 100644 index 000000000000..41fdde59a421 --- /dev/null +++ b/core/http-auth-aws-eventstream/src/main/java/software/amazon/awssdk/http/auth/aws/eventstream/HttpAuthAwsEventStream.java @@ -0,0 +1,27 @@ +/* + * 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.http.auth.aws.eventstream; + +import software.amazon.awssdk.annotations.SdkProtectedApi; + +/** + * This is a place-holder class for this module, http-auth-aws-eventstream. In the event that we decide to move back + * event-stream signer logic back to this dedicated module, no issues will arise. This module should be an optional + * dependency in consumers (http-auth-aws), and should bring in the required dependencies (eventstream). + */ +@SdkProtectedApi +public final class HttpAuthAwsEventStream { +} diff --git a/core/http-auth-aws/pom.xml b/core/http-auth-aws/pom.xml new file mode 100644 index 000000000000..02f3932a46eb --- /dev/null +++ b/core/http-auth-aws/pom.xml @@ -0,0 +1,147 @@ + + + + + 4.0.0 + + + software.amazon.awssdk + core + 2.21.0-SNAPSHOT + + + http-auth-aws + AWS Java SDK :: HTTP Auth AWS + + The AWS SDK for Java - HTTP Auth AWS module contains interfaces and implementations for HTTP + authentication specific to AWS. + + https://aws.amazon.com/sdkforjava + + + + software.amazon.awssdk + annotations + ${awsjavasdk.version} + + + software.amazon.awssdk + utils + ${awsjavasdk.version} + + + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-client-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + checksums-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + checksums + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-aws-crt + ${awsjavasdk.version} + true + + + software.amazon.awssdk.crt + aws-crt + ${awscrt.version} + true + + + software.amazon.awssdk + http-auth-aws-eventstream + ${awsjavasdk.version} + true + + + software.amazon.eventstream + eventstream + ${eventstream.version} + true + + + + + org.mockito + mockito-core + test + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + + + io.reactivex.rxjava2 + rxjava + test + + + nl.jqno.equalsverifier + equalsverifier + test + + + org.reactivestreams + reactive-streams-tck + test + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + software.amazon.awssdk.http.auth.aws + + + + + + + + diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/io/CrtInputStream.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/io/CrtInputStream.java new file mode 100644 index 000000000000..96dad781479b --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/io/CrtInputStream.java @@ -0,0 +1,80 @@ +/* + * 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.http.auth.aws.crt.internal.io; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.crt.http.HttpRequestBodyStream; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.utils.FunctionalUtils; + +@SdkInternalApi +public final class CrtInputStream implements HttpRequestBodyStream { + private static final int READ_BUFFER_SIZE = 4096; + private final ContentStreamProvider provider; + private final int bufSize; + private final byte[] readBuffer; + private InputStream providerStream; + + public CrtInputStream(ContentStreamProvider provider) { + this.provider = provider; + this.bufSize = READ_BUFFER_SIZE; + this.readBuffer = new byte[bufSize]; + } + + @Override + public boolean sendRequestBody(ByteBuffer bodyBytesOut) { + int read; + + if (providerStream == null) { + FunctionalUtils.invokeSafely(this::createNewStream); + } + + int toRead = Math.min(bufSize, bodyBytesOut.remaining()); + read = FunctionalUtils.invokeSafely(() -> providerStream.read(readBuffer, 0, toRead)); + + if (read > 0) { + bodyBytesOut.put(readBuffer, 0, read); + } else { + FunctionalUtils.invokeSafely(providerStream::close); + } + + return read < 0; + } + + @Override + public boolean resetPosition() { + if (provider == null) { + throw new IllegalStateException("Cannot reset position while provider is null"); + } + + FunctionalUtils.invokeSafely(this::createNewStream); + + return true; + } + + private void createNewStream() throws IOException { + if (provider == null) { + throw new IllegalStateException("Cannot create a new stream while provider is null"); + } + if (providerStream != null) { + providerStream.close(); + } + providerStream = provider.newStream(); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/AwsChunkedV4aPayloadSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/AwsChunkedV4aPayloadSigner.java new file mode 100644 index 000000000000..7d80636234a6 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/AwsChunkedV4aPayloadSigner.java @@ -0,0 +1,279 @@ +/* + * 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.http.auth.aws.crt.internal.signer; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.checksumHeaderName; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.fromChecksumAlgorithm; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_ECDSA_SIGNED_PAYLOAD; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_ECDSA_SIGNED_PAYLOAD_TRAILER; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_UNSIGNED_PAYLOAD_TRAILER; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_TRAILER; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.moveContentLength; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.Header; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.SdkChecksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.chunkedencoding.ChecksumTrailerProvider; +import software.amazon.awssdk.http.auth.aws.internal.signer.chunkedencoding.ChunkedEncodedInputStream; +import software.amazon.awssdk.http.auth.aws.internal.signer.chunkedencoding.TrailerProvider; +import software.amazon.awssdk.http.auth.aws.internal.signer.io.ChecksumInputStream; +import software.amazon.awssdk.http.auth.aws.internal.signer.io.ResettableContentStreamProvider; +import software.amazon.awssdk.utils.BinaryUtils; +import software.amazon.awssdk.utils.Pair; +import software.amazon.awssdk.utils.StringInputStream; +import software.amazon.awssdk.utils.Validate; + +/** + * An implementation of a V4aPayloadSigner which chunk-encodes a payload, optionally adding a chunk-signature chunk-extension, + * and/or trailers representing trailing headers with their signature at the end. + */ +@SdkInternalApi +public final class AwsChunkedV4aPayloadSigner implements V4aPayloadSigner { + + private final CredentialScope credentialScope; + private final int chunkSize; + private final ChecksumAlgorithm checksumAlgorithm; + private final List>> preExistingTrailers = new ArrayList<>(); + + private AwsChunkedV4aPayloadSigner(Builder builder) { + this.credentialScope = Validate.paramNotNull(builder.credentialScope, "CredentialScope"); + this.chunkSize = Validate.isPositive(builder.chunkSize, "ChunkSize"); + this.checksumAlgorithm = builder.checksumAlgorithm; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public ContentStreamProvider sign(ContentStreamProvider payload, V4aRequestSigningResult requestSigningResult) { + InputStream inputStream = payload != null ? payload.newStream() : new StringInputStream(""); + ChunkedEncodedInputStream.Builder chunkedEncodedInputStreamBuilder = ChunkedEncodedInputStream + .builder() + .inputStream(inputStream) + .chunkSize(chunkSize) + .header(chunk -> Integer.toHexString(chunk.length).getBytes(StandardCharsets.UTF_8)); + + preExistingTrailers.forEach(trailer -> chunkedEncodedInputStreamBuilder.addTrailer(() -> trailer)); + + switch (requestSigningResult.getSigningConfig().getSignedBodyValue()) { + case STREAMING_ECDSA_SIGNED_PAYLOAD: { + RollingSigner rollingSigner = new RollingSigner(requestSigningResult.getSignature(), + requestSigningResult.getSigningConfig()); + chunkedEncodedInputStreamBuilder.addExtension(new SigV4aChunkExtensionProvider(rollingSigner, credentialScope)); + break; + } + case STREAMING_UNSIGNED_PAYLOAD_TRAILER: + setupChecksumTrailerIfNeeded(chunkedEncodedInputStreamBuilder); + break; + case STREAMING_ECDSA_SIGNED_PAYLOAD_TRAILER: { + RollingSigner rollingSigner = new RollingSigner(requestSigningResult.getSignature(), + requestSigningResult.getSigningConfig()); + chunkedEncodedInputStreamBuilder.addExtension(new SigV4aChunkExtensionProvider(rollingSigner, credentialScope)); + setupChecksumTrailerIfNeeded(chunkedEncodedInputStreamBuilder); + chunkedEncodedInputStreamBuilder.addTrailer( + new SigV4aTrailerProvider(chunkedEncodedInputStreamBuilder.trailers(), rollingSigner, credentialScope) + ); + break; + } + default: + throw new UnsupportedOperationException(); + } + + return new ResettableContentStreamProvider(chunkedEncodedInputStreamBuilder::build); + } + + @Override + public void beforeSigning(SdkHttpRequest.Builder request, ContentStreamProvider payload, String checksum) { + long encodedContentLength = 0; + long contentLength = moveContentLength(request, payload != null ? payload.newStream() : new StringInputStream("")); + setupPreExistingTrailers(request); + + // pre-existing trailers + encodedContentLength += calculateExistingTrailersLength(); + + switch (checksum) { + case STREAMING_ECDSA_SIGNED_PAYLOAD: { + long extensionsLength = 161; // ;chunk-signature: + encodedContentLength += calculateChunksLength(contentLength, extensionsLength); + break; + } + case STREAMING_UNSIGNED_PAYLOAD_TRAILER: + if (checksumAlgorithm != null) { + encodedContentLength += calculateChecksumTrailerLength(checksumHeaderName(checksumAlgorithm)); + } + encodedContentLength += calculateChunksLength(contentLength, 0); + break; + case STREAMING_ECDSA_SIGNED_PAYLOAD_TRAILER: { + long extensionsLength = 161; // ;chunk-signature: + encodedContentLength += calculateChunksLength(contentLength, extensionsLength); + if (checksumAlgorithm != null) { + encodedContentLength += calculateChecksumTrailerLength(checksumHeaderName(checksumAlgorithm)); + } + encodedContentLength += 170; // x-amz-trailer-signature:\r\n + break; + } + default: + throw new UnsupportedOperationException(); + } + + // terminating \r\n + encodedContentLength += 2; + + if (checksumAlgorithm != null) { + String checksumHeaderName = checksumHeaderName(checksumAlgorithm); + request.appendHeader(X_AMZ_TRAILER, checksumHeaderName); + } + request.putHeader(Header.CONTENT_LENGTH, Long.toString(encodedContentLength)); + // CRT-signed request doesn't expect 'aws-chunked' Content-Encoding, so we don't add it + } + + /** + * Set up a map of pre-existing trailer (headers) for the given request to be used when chunk-encoding the payload. + *

+ * However, we need to validate that these are valid trailers. Since aws-chunked encoding adds the checksum as a trailer, it + * isn't part of the request headers, but other trailers MUST be present in the request-headers. + */ + private void setupPreExistingTrailers(SdkHttpRequest.Builder request) { + for (String header : request.matchingHeaders(X_AMZ_TRAILER)) { + List values = request.matchingHeaders(header); + if (values.isEmpty()) { + throw new IllegalArgumentException(header + " must be present in the request headers to be a valid trailer."); + } + preExistingTrailers.add(Pair.of(header, values)); + request.removeHeader(header); + } + } + + private long calculateChunksLength(long contentLength, long extensionsLength) { + long lengthInBytes = 0; + long chunkHeaderLength = Integer.toHexString(chunkSize).length(); + long numChunks = contentLength / chunkSize; + + // normal chunks + // x\r\n\r\n + lengthInBytes += numChunks * (chunkHeaderLength + extensionsLength + 2 + chunkSize + 2); + + // remaining chunk + // x\r\n\r\n + long remainingBytes = contentLength % chunkSize; + if (remainingBytes > 0) { + long remainingChunkHeaderLength = Long.toHexString(remainingBytes).length(); + lengthInBytes += remainingChunkHeaderLength + extensionsLength + 2 + remainingBytes + 2; + } + + // final chunk + // 0\r\n + lengthInBytes += 1 + extensionsLength + 2; + + return lengthInBytes; + } + + private long calculateExistingTrailersLength() { + long lengthInBytes = 0; + + for (Pair> trailer : preExistingTrailers) { + // size of trailer + lengthInBytes += calculateTrailerLength(trailer); + } + + return lengthInBytes; + } + + private long calculateTrailerLength(Pair> trailer) { + // size of trailer-header and colon + long lengthInBytes = trailer.left().length() + 1; + + // size of trailer-values + for (String value : trailer.right()) { + lengthInBytes += value.length(); + } + + // size of commas between trailer-values, 1 less comma than # of values + lengthInBytes += trailer.right().size() - 1; + + // terminating \r\n + return lengthInBytes + 2; + } + + private long calculateChecksumTrailerLength(String checksumHeaderName) { + // size of checksum trailer-header and colon + long lengthInBytes = checksumHeaderName.length() + 1; + + // get the base checksum for the algorithm + SdkChecksum sdkChecksum = fromChecksumAlgorithm(checksumAlgorithm); + // size of checksum value as encoded-string + lengthInBytes += BinaryUtils.toBase64(sdkChecksum.getChecksumBytes()).length(); + + // terminating \r\n + return lengthInBytes + 2; + } + + /** + * Add the checksum as a trailer to the chunk-encoded stream. + *

+ * If the checksum-algorithm is not present, then nothing is done. + */ + private void setupChecksumTrailerIfNeeded(ChunkedEncodedInputStream.Builder builder) { + if (checksumAlgorithm == null) { + return; + } + String checksumHeaderName = checksumHeaderName(checksumAlgorithm); + SdkChecksum sdkChecksum = fromChecksumAlgorithm(checksumAlgorithm); + ChecksumInputStream checksumInputStream = new ChecksumInputStream( + builder.inputStream(), + Collections.singleton(sdkChecksum) + ); + + TrailerProvider checksumTrailer = new ChecksumTrailerProvider(sdkChecksum, checksumHeaderName); + + builder.inputStream(checksumInputStream).addTrailer(checksumTrailer); + } + + static final class Builder { + private CredentialScope credentialScope; + private Integer chunkSize; + private ChecksumAlgorithm checksumAlgorithm; + + public Builder credentialScope(CredentialScope credentialScope) { + this.credentialScope = credentialScope; + return this; + } + + public Builder chunkSize(Integer chunkSize) { + this.chunkSize = chunkSize; + return this; + } + + public Builder checksumAlgorithm(ChecksumAlgorithm checksumAlgorithm) { + this.checksumAlgorithm = checksumAlgorithm; + return this; + } + + public AwsChunkedV4aPayloadSigner build() { + return new AwsChunkedV4aPayloadSigner(this); + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/DefaultAwsCrtV4aHttpSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/DefaultAwsCrtV4aHttpSigner.java new file mode 100644 index 000000000000..f050d906e522 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/DefaultAwsCrtV4aHttpSigner.java @@ -0,0 +1,262 @@ +/* + * 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.http.auth.aws.crt.internal.signer; + +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignedBodyHeaderType.X_AMZ_CONTENT_SHA256; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignedBodyValue.STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignedBodyValue.STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD_TRAILER; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignedBodyValue.STREAMING_UNSIGNED_PAYLOAD_TRAILER; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignedBodyValue.UNSIGNED_PAYLOAD; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSigningAlgorithm.SIGV4_ASYMMETRIC; +import static software.amazon.awssdk.http.auth.aws.crt.internal.util.CrtHttpRequestConverter.toRequest; +import static software.amazon.awssdk.http.auth.aws.crt.internal.util.CrtUtils.sanitizeRequest; +import static software.amazon.awssdk.http.auth.aws.crt.internal.util.CrtUtils.toCredentials; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.checksumHeaderName; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.CredentialUtils.isAnonymous; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.CredentialUtils.sanitizeCredentials; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.PRESIGN_URL_MAX_EXPIRATION_DURATION; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_TRAILER; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.CompletableFuture; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; +import software.amazon.awssdk.crt.auth.signing.AwsSigner; +import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig; +import software.amazon.awssdk.crt.auth.signing.AwsSigningResult; +import software.amazon.awssdk.crt.http.HttpRequest; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.BaseSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.CompletableFutureUtils; + +/** + * An implementation of a {@link AwsV4aHttpSigner} that uses properties to compose v4a-signers in order to delegate signing of a + * request and payload (if applicable) accordingly. + */ +@SdkInternalApi +public final class DefaultAwsCrtV4aHttpSigner implements AwsV4aHttpSigner { + + private static final int DEFAULT_CHUNK_SIZE_IN_BYTES = 128 * 1024; + + private static V4aProperties v4aProperties(BaseSignRequest request) { + Clock signingClock = request.requireProperty(SIGNING_CLOCK, Clock.systemUTC()); + Instant signingInstant = signingClock.instant(); + AwsCredentialsIdentity credentials = sanitizeCredentials(request.identity()); + RegionSet regionSet = request.requireProperty(REGION_SET); + String serviceSigningName = request.requireProperty(SERVICE_SIGNING_NAME); + CredentialScope credentialScope = new CredentialScope(regionSet.asString(), serviceSigningName, signingInstant); + boolean doubleUrlEncode = request.requireProperty(DOUBLE_URL_ENCODE, true); + boolean normalizePath = request.requireProperty(NORMALIZE_PATH, true); + + return V4aProperties + .builder() + .credentials(credentials) + .credentialScope(credentialScope) + .signingClock(signingClock) + .doubleUrlEncode(doubleUrlEncode) + .normalizePath(normalizePath) + .build(); + } + + private static V4aPayloadSigner v4aPayloadSigner( + BaseSignRequest request, + V4aProperties v4aProperties) { + + boolean isPayloadSigning = isPayloadSigning(request); + boolean isChunkEncoding = request.requireProperty(CHUNK_ENCODING_ENABLED, false); + boolean isTrailing = request.request().firstMatchingHeader(X_AMZ_TRAILER).isPresent(); + boolean isFlexible = request.hasProperty(CHECKSUM_ALGORITHM); + + if (useChunkEncoding(isPayloadSigning, isChunkEncoding, isTrailing || isFlexible)) { + return AwsChunkedV4aPayloadSigner.builder() + .credentialScope(v4aProperties.getCredentialScope()) + .chunkSize(DEFAULT_CHUNK_SIZE_IN_BYTES) + .checksumAlgorithm(request.property(CHECKSUM_ALGORITHM)) + .build(); + } + + return V4aPayloadSigner.create(); + } + + private static boolean useChunkEncoding(boolean payloadSigningEnabled, boolean chunkEncodingEnabled, + boolean isTrailingOrFlexible) { + + return (payloadSigningEnabled && chunkEncodingEnabled) || (chunkEncodingEnabled && isTrailingOrFlexible); + } + + private static Duration validateExpirationDuration(Duration expirationDuration) { + if (expirationDuration.compareTo(PRESIGN_URL_MAX_EXPIRATION_DURATION) > 0) { + throw new IllegalArgumentException( + "Requests that are pre-signed by SigV4 algorithm are valid for at most 7" + + " days. The expiration duration set on the current request [" + expirationDuration + "]" + + " has exceeded this limit." + ); + } + return expirationDuration; + } + + private static AwsSigningConfig signingConfig( + BaseSignRequest request, + V4aProperties v4aProperties) { + + AuthLocation authLocation = request.requireProperty(AUTH_LOCATION, AuthLocation.HEADER); + Duration expirationDuration = request.property(EXPIRATION_DURATION); + boolean isPayloadSigning = isPayloadSigning(request); + boolean isChunkEncoding = request.requireProperty(CHUNK_ENCODING_ENABLED, false); + boolean isTrailing = request.request().firstMatchingHeader(X_AMZ_TRAILER).isPresent(); + boolean isFlexible = request.hasProperty(CHECKSUM_ALGORITHM) && !hasChecksumHeader(request); + + AwsSigningConfig signingConfig = new AwsSigningConfig(); + signingConfig.setCredentials(toCredentials(v4aProperties.getCredentials())); + signingConfig.setService(v4aProperties.getCredentialScope().getService()); + signingConfig.setRegion(v4aProperties.getCredentialScope().getRegion()); + signingConfig.setAlgorithm(SIGV4_ASYMMETRIC); + signingConfig.setTime(v4aProperties.getCredentialScope().getInstant().toEpochMilli()); + signingConfig.setUseDoubleUriEncode(v4aProperties.shouldDoubleUrlEncode()); + signingConfig.setShouldNormalizeUriPath(v4aProperties.shouldNormalizePath()); + signingConfig.setSignedBodyHeader(X_AMZ_CONTENT_SHA256); + + switch (authLocation) { + case HEADER: + signingConfig.setSignatureType(AwsSigningConfig.AwsSignatureType.HTTP_REQUEST_VIA_HEADERS); + if (request.hasProperty(EXPIRATION_DURATION)) { + throw new UnsupportedOperationException( + String.format("%s is not supported for %s.", EXPIRATION_DURATION, AuthLocation.HEADER) + ); + } + break; + case QUERY_STRING: + signingConfig.setSignatureType(HTTP_REQUEST_VIA_QUERY_PARAMS); + if (request.hasProperty(EXPIRATION_DURATION)) { + signingConfig.setExpirationInSeconds(validateExpirationDuration(expirationDuration).getSeconds()); + } + break; + default: + throw new UnsupportedOperationException("Unknown auth-location: " + authLocation); + } + + if (isPayloadSigning) { + configurePayloadSigning(signingConfig, isChunkEncoding, isTrailing || isFlexible); + } else { + configureUnsignedPayload(signingConfig, isChunkEncoding, isTrailing || isFlexible); + } + + return signingConfig; + } + + private static boolean isPayloadSigning(BaseSignRequest request) { + boolean isAnonymous = isAnonymous(request.identity()); + boolean isPayloadSigningEnabled = request.requireProperty(PAYLOAD_SIGNING_ENABLED, true); + boolean isEncrypted = "https".equals(request.request().protocol()); + + return !isAnonymous && (isPayloadSigningEnabled || !isEncrypted); + } + + private static void configureUnsignedPayload(AwsSigningConfig signingConfig, boolean isChunkEncoding, + boolean isTrailingOrFlexible) { + if (isChunkEncoding && isTrailingOrFlexible) { + signingConfig.setSignedBodyValue(STREAMING_UNSIGNED_PAYLOAD_TRAILER); + } else { + signingConfig.setSignedBodyValue(UNSIGNED_PAYLOAD); + } + } + + private static void configurePayloadSigning(AwsSigningConfig signingConfig, boolean isChunkEncoding, + boolean isTrailingOrFlexible) { + + if (isChunkEncoding) { + if (isTrailingOrFlexible) { + signingConfig.setSignedBodyValue(STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD_TRAILER); + } else { + signingConfig.setSignedBodyValue(STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD); + } + } + // if it's non-streaming, crt should calcualte the SHA256 body-hash + } + + private static boolean hasChecksumHeader(BaseSignRequest request) { + ChecksumAlgorithm checksumAlgorithm = request.property(CHECKSUM_ALGORITHM); + + if (checksumAlgorithm != null) { + String checksumHeaderName = checksumHeaderName(checksumAlgorithm); + return request.request().firstMatchingHeader(checksumHeaderName).isPresent(); + } + + return false; + } + + private static SignedRequest doSign(SignRequest request, + AwsSigningConfig signingConfig, + V4aPayloadSigner payloadSigner) { + if (isAnonymous(request.identity())) { + return SignedRequest.builder() + .request(request.request()) + .payload(request.payload().orElse(null)) + .build(); + } + + SdkHttpRequest.Builder requestBuilder = request.request().toBuilder(); + + payloadSigner.beforeSigning(requestBuilder, request.payload().orElse(null), signingConfig.getSignedBodyValue()); + + SdkHttpRequest sanitizedRequest = sanitizeRequest(requestBuilder.build()); + + HttpRequest crtRequest = toRequest(sanitizedRequest, request.payload().orElse(null)); + + V4aRequestSigningResult requestSigningResult = sign(requestBuilder.build(), crtRequest, signingConfig); + + ContentStreamProvider payload = payloadSigner.sign(request.payload().orElse(null), requestSigningResult); + + return SignedRequest.builder() + .request(requestSigningResult.getSignedRequest().build()) + .payload(payload) + .build(); + } + + private static V4aRequestSigningResult sign(SdkHttpRequest request, HttpRequest crtRequest, AwsSigningConfig signingConfig) { + AwsSigningResult signingResult = CompletableFutureUtils.joinLikeSync(AwsSigner.sign(crtRequest, signingConfig)); + return new V4aRequestSigningResult( + toRequest(request, signingResult.getSignedRequest()).toBuilder(), + signingResult.getSignature(), + signingConfig); + } + + @Override + public SignedRequest sign(SignRequest request) { + V4aProperties v4aProperties = v4aProperties(request); + AwsSigningConfig signingConfig = signingConfig(request, v4aProperties); + V4aPayloadSigner payloadSigner = v4aPayloadSigner(request, v4aProperties); + return doSign(request, signingConfig, payloadSigner); + } + + @Override + public CompletableFuture signAsync(AsyncSignRequest request) { + // There isn't currently a concept of async for crt signers + throw new UnsupportedOperationException(); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/DefaultV4aPayloadSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/DefaultV4aPayloadSigner.java new file mode 100644 index 000000000000..e5b02c6a01de --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/DefaultV4aPayloadSigner.java @@ -0,0 +1,30 @@ +/* + * 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.http.auth.aws.crt.internal.signer; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.ContentStreamProvider; + +/** + * A default implementation of a payload signer that is a no-op. + */ +@SdkInternalApi +public class DefaultV4aPayloadSigner implements V4aPayloadSigner { + @Override + public ContentStreamProvider sign(ContentStreamProvider payload, V4aRequestSigningResult requestSigningResult) { + return payload; + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/RollingSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/RollingSigner.java new file mode 100644 index 000000000000..1a22cd8a427f --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/RollingSigner.java @@ -0,0 +1,92 @@ +/* + * 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.http.auth.aws.crt.internal.signer; + +import java.io.ByteArrayInputStream; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.crt.auth.signing.AwsSigner; +import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig; +import software.amazon.awssdk.crt.auth.signing.AwsSigningResult; +import software.amazon.awssdk.crt.http.HttpHeader; +import software.amazon.awssdk.crt.http.HttpRequestBodyStream; +import software.amazon.awssdk.http.auth.aws.crt.internal.io.CrtInputStream; +import software.amazon.awssdk.utils.CompletableFutureUtils; + +/** + * A class which calculates a rolling signature of arbitrary data using HMAC-SHA256. Each time a signature is calculated, the + * prior calculation is incorporated, hence "rolling". + */ +@SdkInternalApi +public final class RollingSigner { + + private final byte[] seedSignature; + private final AwsSigningConfig signingConfig; + private byte[] previousSignature; + + public RollingSigner(byte[] seedSignature, AwsSigningConfig signingConfig) { + this.seedSignature = seedSignature.clone(); + this.previousSignature = seedSignature.clone(); + this.signingConfig = signingConfig; + } + + private static byte[] signChunk(byte[] chunkBody, byte[] previousSignature, AwsSigningConfig signingConfig) { + // All the config remains the same as signing config except the Signature Type. + AwsSigningConfig configCopy = signingConfig.clone(); + configCopy.setSignatureType(AwsSigningConfig.AwsSignatureType.HTTP_REQUEST_CHUNK); + configCopy.setSignedBodyHeader(AwsSigningConfig.AwsSignedBodyHeaderType.NONE); + configCopy.setSignedBodyValue(null); + + HttpRequestBodyStream crtBody = new CrtInputStream(() -> new ByteArrayInputStream(chunkBody)); + return CompletableFutureUtils.joinLikeSync(AwsSigner.signChunk(crtBody, previousSignature, configCopy)); + } + + private static AwsSigningResult signTrailerHeaders(Map> headerMap, byte[] previousSignature, + AwsSigningConfig signingConfig) { + + List httpHeaderList = + headerMap.entrySet().stream().map(entry -> new HttpHeader( + entry.getKey(), String.join(",", entry.getValue()))).collect(Collectors.toList()); + + // All the config remains the same as signing config except the Signature Type. + AwsSigningConfig configCopy = signingConfig.clone(); + configCopy.setSignatureType(AwsSigningConfig.AwsSignatureType.HTTP_REQUEST_TRAILING_HEADERS); + configCopy.setSignedBodyHeader(AwsSigningConfig.AwsSignedBodyHeaderType.NONE); + configCopy.setSignedBodyValue(null); + + return CompletableFutureUtils.joinLikeSync(AwsSigner.sign(httpHeaderList, previousSignature, configCopy)); + } + + /** + * Using a template that incorporates the previous calculated signature, sign the string and return it. + */ + public byte[] sign(byte[] chunkBody) { + previousSignature = signChunk(chunkBody, previousSignature, signingConfig); + return previousSignature; + } + + public byte[] sign(Map> headerMap) { + AwsSigningResult result = signTrailerHeaders(headerMap, previousSignature, signingConfig); + previousSignature = result != null ? result.getSignature() : new byte[0]; + return previousSignature; + } + + public void reset() { + previousSignature = seedSignature; + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/SigV4aChunkExtensionProvider.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/SigV4aChunkExtensionProvider.java new file mode 100644 index 000000000000..9e6173e47c0c --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/SigV4aChunkExtensionProvider.java @@ -0,0 +1,48 @@ +/* + * 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.http.auth.aws.crt.internal.signer; + +import java.nio.charset.StandardCharsets; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.http.auth.aws.internal.signer.chunkedencoding.ChunkExtensionProvider; +import software.amazon.awssdk.utils.Pair; + +@SdkInternalApi +public class SigV4aChunkExtensionProvider implements ChunkExtensionProvider { + + private final RollingSigner signer; + private final CredentialScope credentialScope; + + public SigV4aChunkExtensionProvider(RollingSigner signer, CredentialScope credentialScope) { + this.signer = signer; + this.credentialScope = credentialScope; + } + + @Override + public void reset() { + signer.reset(); + } + + @Override + public Pair get(byte[] chunk) { + byte[] chunkSig = signer.sign(chunk); + return Pair.of( + "chunk-signature".getBytes(StandardCharsets.UTF_8), + chunkSig + ); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/SigV4aTrailerProvider.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/SigV4aTrailerProvider.java new file mode 100644 index 000000000000..5e48ca269951 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/SigV4aTrailerProvider.java @@ -0,0 +1,62 @@ +/* + * 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.http.auth.aws.crt.internal.signer; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.http.auth.aws.internal.signer.chunkedencoding.TrailerProvider; +import software.amazon.awssdk.utils.Pair; + +@SdkInternalApi +public class SigV4aTrailerProvider implements TrailerProvider { + + private final List trailerProviders = new ArrayList<>(); + private final RollingSigner signer; + private final CredentialScope credentialScope; + + public SigV4aTrailerProvider(List trailerProviders, RollingSigner signer, CredentialScope credentialScope) { + this.trailerProviders.addAll(trailerProviders); + this.signer = signer; + this.credentialScope = credentialScope; + } + + @Override + public void reset() { + trailerProviders.forEach(TrailerProvider::reset); + signer.reset(); + } + + @Override + public Pair> get() { + byte[] trailerSig = signer.sign(getTrailers()); + return Pair.of( + "x-amz-trailer-signature", + Collections.singletonList(new String(trailerSig, StandardCharsets.UTF_8)) + ); + } + + private Map> getTrailers() { + // Get the headers by calling get() on each of the trailers + return trailerProviders.stream().map(TrailerProvider::get).collect(Collectors.toMap(Pair::left, Pair::right)); + + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/V4aPayloadSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/V4aPayloadSigner.java new file mode 100644 index 000000000000..c42bf68e014a --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/V4aPayloadSigner.java @@ -0,0 +1,44 @@ +/* + * 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.http.auth.aws.crt.internal.signer; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.SdkHttpRequest; + +/** + * An interface for defining how to sign a payload via SigV4a. + */ +@SdkInternalApi +public interface V4aPayloadSigner { + /** + * Get a default implementation of a SigV4a payload signer. + */ + static V4aPayloadSigner create() { + return new DefaultV4aPayloadSigner(); + } + + /** + * Given a payload and result of request signing, sign the payload via the SigV4a process. + */ + ContentStreamProvider sign(ContentStreamProvider payload, V4aRequestSigningResult requestSigningResult); + + /** + * Modify a request before it is signed, such as changing headers or query-parameters. + */ + default void beforeSigning(SdkHttpRequest.Builder request, ContentStreamProvider payload, String checksum) { + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/V4aProperties.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/V4aProperties.java new file mode 100644 index 000000000000..d24e1e62f3f2 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/V4aProperties.java @@ -0,0 +1,110 @@ +/* + * 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.http.auth.aws.crt.internal.signer; + +import java.time.Clock; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.http.auth.spi.signer.BaseSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.Validate; + + +/** + * A class which contains "properties" relevant to SigV4a. These properties can be derived {@link SignerProperty}'s on a + * {@link BaseSignRequest}. + */ +@SdkInternalApi +@Immutable +public final class V4aProperties { + private final AwsCredentialsIdentity credentials; + private final CredentialScope credentialScope; + private final Clock signingClock; + private final boolean doubleUrlEncode; + private final boolean normalizePath; + + + private V4aProperties(Builder builder) { + this.credentials = Validate.paramNotNull(builder.credentials, "Credentials"); + this.credentialScope = Validate.paramNotNull(builder.credentialScope, "CredentialScope"); + this.signingClock = Validate.paramNotNull(builder.signingClock, "SigningClock"); + this.doubleUrlEncode = Validate.getOrDefault(builder.doubleUrlEncode, () -> true); + this.normalizePath = Validate.getOrDefault(builder.normalizePath, () -> true); + } + + public static Builder builder() { + return new Builder(); + } + + public AwsCredentialsIdentity getCredentials() { + return credentials; + } + + public CredentialScope getCredentialScope() { + return credentialScope; + } + + public Clock getSigningClock() { + return signingClock; + } + + public boolean shouldDoubleUrlEncode() { + return doubleUrlEncode; + } + + public boolean shouldNormalizePath() { + return normalizePath; + } + + public static class Builder { + private AwsCredentialsIdentity credentials; + private CredentialScope credentialScope; + private Clock signingClock; + private Boolean doubleUrlEncode; + private Boolean normalizePath; + + public Builder credentials(AwsCredentialsIdentity credentials) { + this.credentials = Validate.paramNotNull(credentials, "Credentials"); + return this; + } + + public Builder credentialScope(CredentialScope credentialScope) { + this.credentialScope = Validate.paramNotNull(credentialScope, "CredentialScope"); + return this; + } + + public Builder signingClock(Clock signingClock) { + this.signingClock = signingClock; + return this; + } + + public Builder doubleUrlEncode(Boolean doubleUrlEncode) { + this.doubleUrlEncode = doubleUrlEncode; + return this; + } + + public Builder normalizePath(Boolean normalizePath) { + this.normalizePath = normalizePath; + return this; + } + + public V4aProperties build() { + return new V4aProperties(this); + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/V4aRequestSigningResult.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/V4aRequestSigningResult.java new file mode 100644 index 000000000000..4640db38e22a --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/V4aRequestSigningResult.java @@ -0,0 +1,48 @@ +/* + * 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.http.auth.aws.crt.internal.signer; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig; +import software.amazon.awssdk.http.SdkHttpRequest; + +/** + * A container for data produced during and as a result of the SigV4a request signing with CRT. + */ +@SdkInternalApi +public final class V4aRequestSigningResult { + private final SdkHttpRequest.Builder signedRequest; + private final byte[] signature; + private final AwsSigningConfig signingConfig; + + public V4aRequestSigningResult(SdkHttpRequest.Builder signedRequest, byte[] signature, AwsSigningConfig signingConfig) { + this.signedRequest = signedRequest; + this.signature = signature.clone(); + this.signingConfig = signingConfig; + } + + public SdkHttpRequest.Builder getSignedRequest() { + return signedRequest; + } + + public byte[] getSignature() { + return signature; + } + + public AwsSigningConfig getSigningConfig() { + return signingConfig; + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/util/CrtHttpRequestConverter.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/util/CrtHttpRequestConverter.java new file mode 100644 index 000000000000..46eedd9a3ce6 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/util/CrtHttpRequestConverter.java @@ -0,0 +1,151 @@ +/* + * 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.http.auth.aws.crt.internal.util; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.HOST; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.crt.http.HttpHeader; +import software.amazon.awssdk.crt.http.HttpRequest; +import software.amazon.awssdk.crt.http.HttpRequestBodyStream; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.crt.internal.io.CrtInputStream; +import software.amazon.awssdk.utils.StringUtils; +import software.amazon.awssdk.utils.http.SdkHttpUtils; + +@SdkInternalApi +public final class CrtHttpRequestConverter { + + private CrtHttpRequestConverter() { + } + + /** + * Convert an {@link SdkHttpRequest} to an {@link HttpRequest}. + */ + public static HttpRequest toRequest(SdkHttpRequest request, ContentStreamProvider payload) { + String method = request.method().name(); + String encodedPath = encodedPathToCrtFormat(request.encodedPath()); + + String encodedQueryString = request.encodedQueryParameters().map(value -> "?" + value).orElse(""); + + HttpHeader[] crtHeaderArray = createHttpHeaderArray(request); + + HttpRequestBodyStream crtInputStream = null; + if (payload != null) { + crtInputStream = new CrtInputStream(payload); + } + + return new HttpRequest(method, encodedPath + encodedQueryString, crtHeaderArray, crtInputStream); + } + + /** + * Convert an {@link HttpRequest} to an {@link SdkHttpRequest}. + */ + public static SdkHttpRequest toRequest(SdkHttpRequest request, HttpRequest crtRequest) { + SdkHttpRequest.Builder builder = request.toBuilder(); + + builder.clearHeaders(); + for (HttpHeader header : crtRequest.getHeaders()) { + builder.appendHeader(header.getName(), header.getValue()); + } + + URI fullUri; + try { + String portString = SdkHttpUtils.isUsingStandardPort(builder.protocol(), builder.port()) ? "" : ":" + builder.port(); + String encodedPath = encodedPathFromCrtFormat(request.encodedPath(), crtRequest.getEncodedPath()); + String fullUriString = builder.protocol() + "://" + builder.host() + portString + encodedPath; + fullUri = new URI(fullUriString); + } catch (URISyntaxException e) { + throw new RuntimeException("Full URI could not be formed.", e); + } + + builder.encodedPath(fullUri.getRawPath()); + String remainingQuery = fullUri.getQuery(); + + builder.clearQueryParameters(); + while (remainingQuery != null && !remainingQuery.isEmpty()) { + int nextQuery = remainingQuery.indexOf('&'); + int nextAssign = remainingQuery.indexOf('='); + if (nextAssign < nextQuery || (nextAssign >= 0 && nextQuery < 0)) { + String queryName = remainingQuery.substring(0, nextAssign); + String queryValue = remainingQuery.substring(nextAssign + 1); + if (nextQuery >= 0) { + queryValue = remainingQuery.substring(nextAssign + 1, nextQuery); + } + + builder.appendRawQueryParameter(queryName, queryValue); + } else { + String queryName = remainingQuery; + if (nextQuery >= 0) { + queryName = remainingQuery.substring(0, nextQuery); + } + + builder.appendRawQueryParameter(queryName, null); + } + + if (nextQuery >= 0) { + remainingQuery = remainingQuery.substring(nextQuery + 1); + } else { + break; + } + } + + return builder.build(); + } + + private static HttpHeader[] createHttpHeaderArray(SdkHttpRequest request) { + List crtHeaderList = new ArrayList<>(request.numHeaders() + 2); + + // Set Host Header if needed + if (!request.firstMatchingHeader(HOST).isPresent()) { + crtHeaderList.add(new HttpHeader(HOST, request.host())); + } + + // Add the rest of the Headers + request.forEachHeader((name, values) -> { + for (String val : values) { + HttpHeader h = new HttpHeader(name, val); + crtHeaderList.add(h); + } + }); + + return crtHeaderList.toArray(new HttpHeader[0]); + } + + private static String encodedPathToCrtFormat(String sdkEncodedPath) { + if (StringUtils.isEmpty(sdkEncodedPath)) { + return "/"; + } + return sdkEncodedPath; + } + + private static String encodedPathFromCrtFormat(String sdkEncodedPath, String crtEncodedPath) { + if ("/".equals(crtEncodedPath) && StringUtils.isEmpty(sdkEncodedPath)) { + return ""; + } + return crtEncodedPath; + } + + public static HttpRequestBodyStream toCrtStream(byte[] data) { + return new CrtInputStream(() -> new ByteArrayInputStream(data)); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/util/CrtUtils.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/util/CrtUtils.java new file mode 100644 index 000000000000..8ac8cac1bbbc --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/crt/internal/util/CrtUtils.java @@ -0,0 +1,124 @@ +/* + * 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.http.auth.aws.crt.internal.util; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.AUTHORIZATION; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.HOST; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_ALGORITHM; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_CREDENTIAL; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_DATE; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_EXPIRES; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_SIGNATURE; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_SIGNED_HEADERS; + +import java.nio.charset.StandardCharsets; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.crt.auth.credentials.Credentials; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; +import software.amazon.awssdk.utils.http.SdkHttpUtils; + +@SdkInternalApi +public final class CrtUtils { + private static final String BODY_HASH_NAME = "x-amz-content-sha256"; + private static final String REGION_SET_NAME = "X-amz-region-set"; + + private static final Set FORBIDDEN_HEADERS = + Stream.of(BODY_HASH_NAME, X_AMZ_DATE, AUTHORIZATION, REGION_SET_NAME) + .collect(Collectors.toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))); + private static final Set FORBIDDEN_PARAMS = + Stream.of(X_AMZ_SIGNATURE, X_AMZ_DATE, X_AMZ_CREDENTIAL, X_AMZ_ALGORITHM, X_AMZ_SIGNED_HEADERS, REGION_SET_NAME, + X_AMZ_EXPIRES) + .collect(Collectors.toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))); + + private CrtUtils() { + } + + /** + * Sanitize an {@link SdkHttpRequest}, in order to prepare it for converting to a CRT request destined to be signed. + *

+ * Sanitizing includes checking the path is not empty, filtering headers and query parameters that are forbidden in CRT, and + * adding the host header (overriding if already presesnt). + */ + public static SdkHttpRequest sanitizeRequest(SdkHttpRequest request) { + + SdkHttpRequest.Builder builder = request.toBuilder(); + + // Ensure path is non-empty + String path = builder.encodedPath(); + if (path == null || path.isEmpty()) { + builder.encodedPath("/"); + } + + builder.clearHeaders(); + + // Filter headers that will cause signing to fail + request.forEachHeader((name, value) -> { + if (!FORBIDDEN_HEADERS.contains(name)) { + builder.putHeader(name, value); + } + }); + + // Add host, which must be signed. We ignore any pre-existing Host header to match the behavior of the SigV4 signer. + String hostHeader = SdkHttpUtils.isUsingStandardPort(request.protocol(), request.port()) + ? request.host() + : request.host() + ":" + request.port(); + builder.putHeader(HOST, hostHeader); + + builder.clearQueryParameters(); + + // Filter query parameters that will cause signing to fail + request.forEachRawQueryParameter((key, value) -> { + if (!FORBIDDEN_PARAMS.contains(key)) { + builder.putRawQueryParameter(key, value); + } + }); + + return builder.build(); + } + + /** + * Convert an {@link AwsCredentialsIdentity} to the CRT equivalent of credentials ({@link Credentials}). + */ + public static Credentials toCredentials(AwsCredentialsIdentity credentialsIdentity) { + byte[] sessionToken = null; + + if (credentialsIdentity == null || + credentialsIdentity.accessKeyId() == null || + credentialsIdentity.secretAccessKey() == null + ) { + return null; + } + + if (credentialsIdentity instanceof AwsSessionCredentialsIdentity) { + sessionToken = ((AwsSessionCredentialsIdentity) credentialsIdentity) + .sessionToken() + .getBytes(StandardCharsets.UTF_8); + } + + return new Credentials( + credentialsIdentity.accessKeyId().getBytes(StandardCharsets.UTF_8), + credentialsIdentity.secretAccessKey().getBytes(StandardCharsets.UTF_8), + sessionToken + ); + } + +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/io/SigV4DataFramePublisher.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/io/SigV4DataFramePublisher.java new file mode 100644 index 000000000000..04d25964b509 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/io/SigV4DataFramePublisher.java @@ -0,0 +1,280 @@ +/* + * 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.http.auth.aws.eventstream.internal.io; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.computeSignature; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.deriveSigningKey; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.hash; + +import java.nio.ByteBuffer; +import java.time.Clock; +import java.time.Instant; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Function; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.BinaryUtils; +import software.amazon.awssdk.utils.Logger; +import software.amazon.awssdk.utils.Validate; +import software.amazon.awssdk.utils.internal.MappingSubscriber; +import software.amazon.eventstream.HeaderValue; +import software.amazon.eventstream.Message; + +/** + * A publisher which adapts a publisher by SigV4-signing each frame, and appends an empty trailing frame. + */ +@SdkInternalApi +public final class SigV4DataFramePublisher implements Publisher { + + private static final Logger LOG = Logger.loggerFor(SigV4DataFramePublisher.class); + private static final String CHUNK_SIGNATURE = ":chunk-signature"; + private static final int PAYLOAD_TRUNCATE_LENGTH = 32; + + private final Publisher sigv4Publisher; + + private SigV4DataFramePublisher(Builder builder) { + Validate.paramNotNull(builder.publisher, "Publisher"); + Validate.paramNotNull(builder.credentials, "Credentials"); + Validate.paramNotNull(builder.credentialScope, "CredentialScope"); + Validate.paramNotNull(builder.signature, "Signature"); + Validate.paramNotNull(builder.signingClock, "SigningClock"); + + + // Adapt the publisher with a trailing-empty frame publisher + Publisher trailingPublisher = new TrailingDataFramePublisher(builder.publisher); + + // Map publisher with signing function + this.sigv4Publisher = subscriber -> { + Subscriber adaptedSubscriber = + MappingSubscriber.create(subscriber, getDataFrameSigner(builder.credentials, + builder.credentialScope, + builder.signature, + builder.signingClock + ) + ); + trailingPublisher.subscribe(adaptedSubscriber); + }; + } + + private static Function getDataFrameSigner(AwsCredentialsIdentity credentials, + CredentialScope credentialScope, + String signature, + Clock signingClock) { + return new Function() { + + /** + * Initiate rolling signature with an initial signature + */ + String priorSignature = signature; + + @Override + public ByteBuffer apply(ByteBuffer byteBuffer) { + /** + * Signing Date + */ + Map eventHeaders = new HashMap<>(); + Instant signingInstant = signingClock.instant(); + eventHeaders.put(":date", HeaderValue.fromTimestamp(signingInstant)); + + /** + * Derive Signing Key - since a stream of events could be over a period of time, we should update + * the credential scope with the new instant every time the data-frame signer is called + */ + CredentialScope updatedCredentialScope = new CredentialScope(credentialScope.getRegion(), + credentialScope.getService(), signingInstant); + byte[] signingKey = deriveSigningKey(credentials, updatedCredentialScope); + + /** + * Calculate rolling signature + */ + byte[] payload = new byte[byteBuffer.remaining()]; + byteBuffer.get(payload); + byte[] signatureBytes = signEvent(priorSignature, signingKey, updatedCredentialScope, eventHeaders, payload); + priorSignature = BinaryUtils.toHex(signatureBytes); + + /** + * Add signing layer headers + */ + Map headers = new HashMap<>(eventHeaders); + //Signature headers + headers.put(CHUNK_SIGNATURE, HeaderValue.fromByteArray(signatureBytes)); + + /** + * Wrap payload and headers in a Message object and then encode to bytes + */ + Message signedMessage = new Message(sortHeaders(headers), payload); + + if (LOG.isLoggingLevelEnabled("trace")) { + LOG.trace(() -> "Signed message: " + toDebugString(signedMessage, false)); + } else { + LOG.debug(() -> "Signed message: " + toDebugString(signedMessage, true)); + } + + return signedMessage.toByteBuffer(); + } + }; + } + + /** + * Sign an event/chunk via SigV4 + * + * @param priorSignature signature of previous frame + * @param signingKey derived signing key + * @param credentialScope the credential-scope used to provide region, service, and time + * @param eventHeaders headers pertinent to the event + * @param event an event of a bytes to sign + * @return encoded event with signature + */ + private static byte[] signEvent( + String priorSignature, + byte[] signingKey, + CredentialScope credentialScope, + Map eventHeaders, + byte[] event) { + + // String to sign + String eventHeadersSignature = BinaryUtils.toHex(hash(Message.encodeHeaders(sortHeaders(eventHeaders).entrySet()))); + String eventHash = BinaryUtils.toHex(hash(event)); + String stringToSign = + "AWS4-HMAC-SHA256-PAYLOAD" + SignerConstant.LINE_SEPARATOR + + credentialScope.getDatetime() + SignerConstant.LINE_SEPARATOR + + credentialScope.scope() + SignerConstant.LINE_SEPARATOR + + priorSignature + SignerConstant.LINE_SEPARATOR + + eventHeadersSignature + SignerConstant.LINE_SEPARATOR + + eventHash; + + // calculate signature + return computeSignature(stringToSign, signingKey); + } + + /** + * Sort event headers in alphabetic order, with exception that CHUNK_SIGNATURE header always at last + * + * @param headers unsorted event headers + * @return sorted event headers + */ + private static TreeMap sortHeaders(Map headers) { + TreeMap sortedHeaders = new TreeMap<>((header1, header2) -> { + // CHUNK_SIGNATURE should always be the last header + if (header1.equals(CHUNK_SIGNATURE)) { + return 1; // put header1 at last + } + if (header2.equals(CHUNK_SIGNATURE)) { + return -1; // put header2 at last + } + return header1.compareTo(header2); + }); + sortedHeaders.putAll(headers); + return sortedHeaders; + } + + private static String toDebugString(Message m, boolean truncatePayload) { + StringBuilder sb = new StringBuilder("Message = {headers={"); + Map headers = m.getHeaders(); + + Iterator> headersIter = headers.entrySet().iterator(); + + while (headersIter.hasNext()) { + Map.Entry h = headersIter.next(); + + sb.append(h.getKey()).append("={").append(h.getValue().toString()).append("}"); + + if (headersIter.hasNext()) { + sb.append(", "); + } + } + + sb.append("}, payload="); + + byte[] payload = m.getPayload(); + byte[] payloadToLog; + + // We don't actually need to truncate if the payload length is already within the truncate limit + truncatePayload = truncatePayload && payload.length > PAYLOAD_TRUNCATE_LENGTH; + + if (truncatePayload) { + // Would be nice if BinaryUtils.toHex() could take an array index range instead, so we don't need to copy + payloadToLog = Arrays.copyOf(payload, PAYLOAD_TRUNCATE_LENGTH); + } else { + payloadToLog = payload; + } + + sb.append(BinaryUtils.toHex(payloadToLog)); + + if (truncatePayload) { + sb.append("..."); + } + + sb.append("}"); + + return sb.toString(); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public void subscribe(Subscriber subscriber) { + sigv4Publisher.subscribe(subscriber); + } + + public static class Builder { + private Publisher publisher; + private AwsCredentialsIdentity credentials; + private CredentialScope credentialScope; + private String signature; + private Clock signingClock; + + public Builder publisher(Publisher publisher) { + this.publisher = publisher; + return this; + } + + public Builder credentials(AwsCredentialsIdentity credentials) { + this.credentials = credentials; + return this; + } + + public Builder credentialScope(CredentialScope credentialScope) { + this.credentialScope = credentialScope; + return this; + } + + public Builder signature(String signature) { + this.signature = signature; + return this; + } + + public Builder signingClock(Clock signingClock) { + this.signingClock = signingClock; + return this; + } + + public SigV4DataFramePublisher build() { + return new SigV4DataFramePublisher(this); + } + + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/io/TrailingDataFramePublisher.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/io/TrailingDataFramePublisher.java new file mode 100644 index 000000000000..bebaf95b6c1d --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/io/TrailingDataFramePublisher.java @@ -0,0 +1,114 @@ +/* + * 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.http.auth.aws.eventstream.internal.io; + +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * A publisher which wraps a publisher and appends a trailing frame once the wrapped publisher is finished. + */ +@SdkInternalApi +public final class TrailingDataFramePublisher implements Publisher { + + private final Publisher trailingPublisher; + + public TrailingDataFramePublisher(Publisher publisher) { + this.trailingPublisher = subscriber -> { + Subscriber adaptedSubscriber = new SubscriberAdapter(subscriber); + publisher.subscribe(adaptedSubscriber); + }; + } + + @Override + public void subscribe(Subscriber subscriber) { + trailingPublisher.subscribe(subscriber); + } + + static class SubscriberAdapter implements Subscriber { + private final AtomicBoolean upstreamDone = new AtomicBoolean(false); + private final AtomicLong downstreamDemand = new AtomicLong(); + private final Object lock = new Object(); + private final Subscriber delegate; + private volatile boolean sentTrailingFrame = false; + + SubscriberAdapter(Subscriber actual) { + this.delegate = actual; + } + + @Override + public void onSubscribe(Subscription s) { + delegate.onSubscribe(new Subscription() { + @Override + public void request(long n) { + if (n <= 0) { + onError(new IllegalArgumentException("n > 0 required but it was " + n)); + } + + downstreamDemand.getAndAdd(n); + + if (upstreamDone.get()) { + sendTrailingEmptyFrame(); + } else { + s.request(n); + } + } + + @Override + public void cancel() { + s.cancel(); + } + }); + } + + @Override + public void onNext(ByteBuffer byteBuffer) { + downstreamDemand.decrementAndGet(); + delegate.onNext(byteBuffer); + } + + @Override + public void onError(Throwable t) { + upstreamDone.compareAndSet(false, true); + delegate.onError(t); + } + + @Override + public void onComplete() { + upstreamDone.compareAndSet(false, true); + + if (downstreamDemand.get() > 0) { + sendTrailingEmptyFrame(); + } + } + + private void sendTrailingEmptyFrame() { + // when upstream complete, send a trailing empty frame + synchronized (lock) { + if (!sentTrailingFrame) { + sentTrailingFrame = true; + delegate.onNext(ByteBuffer.wrap(new byte[] {})); + delegate.onComplete(); + } + } + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/signer/EventStreamV4PayloadSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/signer/EventStreamV4PayloadSigner.java new file mode 100644 index 000000000000..9d06411cd1a6 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/signer/EventStreamV4PayloadSigner.java @@ -0,0 +1,90 @@ +/* + * 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.http.auth.aws.eventstream.internal.signer; + +import java.nio.ByteBuffer; +import java.time.Clock; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.auth.aws.eventstream.internal.io.SigV4DataFramePublisher; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.http.auth.aws.internal.signer.V4PayloadSigner; +import software.amazon.awssdk.http.auth.aws.internal.signer.V4RequestSigningResult; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.Validate; + +/** + * An implementation which supports async signing of event-stream payloads. + */ +@SdkInternalApi +public class EventStreamV4PayloadSigner implements V4PayloadSigner { + + private final AwsCredentialsIdentity credentials; + private final CredentialScope credentialScope; + private final Clock signingClock; + + public EventStreamV4PayloadSigner(Builder builder) { + this.credentials = Validate.paramNotNull(builder.credentials, "Credentials"); + this.credentialScope = Validate.paramNotNull(builder.credentialScope, "CredentialScope"); + this.signingClock = Validate.paramNotNull(builder.signingClock, "SigningClock"); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public ContentStreamProvider sign(ContentStreamProvider payload, V4RequestSigningResult requestSigningResult) { + throw new UnsupportedOperationException(); + } + + @Override + public Publisher signAsync(Publisher payload, V4RequestSigningResult requestSigningResult) { + return SigV4DataFramePublisher.builder() + .publisher(payload) + .credentials(credentials) + .credentialScope(credentialScope) + .signature(requestSigningResult.getSignature()) + .signingClock(signingClock) + .build(); + } + + public static class Builder { + private AwsCredentialsIdentity credentials; + private CredentialScope credentialScope; + private Clock signingClock; + + public Builder credentials(AwsCredentialsIdentity credentials) { + this.credentials = credentials; + return this; + } + + public Builder credentialScope(CredentialScope credentialScope) { + this.credentialScope = credentialScope; + return this; + } + + public Builder signingClock(Clock signingClock) { + this.signingClock = signingClock; + return this; + } + + public EventStreamV4PayloadSigner build() { + return new EventStreamV4PayloadSigner(this); + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/scheme/DefaultAwsV4AuthScheme.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/scheme/DefaultAwsV4AuthScheme.java new file mode 100644 index 000000000000..b0d808a8fc16 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/scheme/DefaultAwsV4AuthScheme.java @@ -0,0 +1,54 @@ +/* + * 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.http.auth.aws.internal.scheme; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; + +/** + * A default implementation of {@link AwsV4AuthScheme}. + */ +@SdkInternalApi +public final class DefaultAwsV4AuthScheme implements AwsV4AuthScheme { + private static final DefaultAwsV4AuthScheme DEFAULT = new DefaultAwsV4AuthScheme(); + private static final AwsV4HttpSigner DEFAULT_SIGNER = AwsV4HttpSigner.create(); + + /** + * Returns an instance of the {@link DefaultAwsV4AuthScheme}. + */ + public static DefaultAwsV4AuthScheme create() { + return DEFAULT; + } + + @Override + public String schemeId() { + return SCHEME_ID; + } + + @Override + public IdentityProvider identityProvider(IdentityProviders providers) { + return providers.identityProvider(AwsCredentialsIdentity.class); + } + + @Override + public AwsV4HttpSigner signer() { + return DEFAULT_SIGNER; + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/scheme/DefaultAwsV4aAuthScheme.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/scheme/DefaultAwsV4aAuthScheme.java new file mode 100644 index 000000000000..8be2e8c43e80 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/scheme/DefaultAwsV4aAuthScheme.java @@ -0,0 +1,61 @@ +/* + * 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.http.auth.aws.internal.scheme; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; + +/** + * A default implementation of {@link AwsV4aAuthScheme}. + */ +@SdkInternalApi +public final class DefaultAwsV4aAuthScheme implements AwsV4aAuthScheme { + private static final DefaultAwsV4aAuthScheme DEFAULT = new DefaultAwsV4aAuthScheme(); + + /** + * Returns an instance of the {@link DefaultAwsV4aAuthScheme}. + */ + public static DefaultAwsV4aAuthScheme create() { + return DEFAULT; + } + + @Override + public String schemeId() { + return SCHEME_ID; + } + + @Override + public IdentityProvider identityProvider(IdentityProviders providers) { + return providers.identityProvider(AwsCredentialsIdentity.class); + } + + /** + * AwsV4aHttpSigner.create() returns the CRT implementation and requires the optional dependency http-auth-aws-crt to be + * added. So lazily creating the instance only when this method is called. + */ + @Override + public AwsV4aHttpSigner signer() { + return SignerSingletonHolder.INSTANCE; + } + + private static class SignerSingletonHolder { + private static final AwsV4aHttpSigner INSTANCE = AwsV4aHttpSigner.create(); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/AwsChunkedV4PayloadSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/AwsChunkedV4PayloadSigner.java new file mode 100644 index 000000000000..669dd2a4d434 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/AwsChunkedV4PayloadSigner.java @@ -0,0 +1,298 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.checksumHeaderName; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.fromChecksumAlgorithm; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.AWS_CHUNKED; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.CONTENT_ENCODING; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_SIGNED_PAYLOAD; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_SIGNED_PAYLOAD_TRAILER; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_UNSIGNED_PAYLOAD_TRAILER; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_CONTENT_SHA256; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_TRAILER; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.moveContentLength; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.Header; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.SdkChecksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.chunkedencoding.ChecksumTrailerProvider; +import software.amazon.awssdk.http.auth.aws.internal.signer.chunkedencoding.ChunkedEncodedInputStream; +import software.amazon.awssdk.http.auth.aws.internal.signer.chunkedencoding.SigV4ChunkExtensionProvider; +import software.amazon.awssdk.http.auth.aws.internal.signer.chunkedencoding.SigV4TrailerProvider; +import software.amazon.awssdk.http.auth.aws.internal.signer.chunkedencoding.TrailerProvider; +import software.amazon.awssdk.http.auth.aws.internal.signer.io.ChecksumInputStream; +import software.amazon.awssdk.http.auth.aws.internal.signer.io.ResettableContentStreamProvider; +import software.amazon.awssdk.utils.BinaryUtils; +import software.amazon.awssdk.utils.Pair; +import software.amazon.awssdk.utils.StringInputStream; +import software.amazon.awssdk.utils.Validate; + +/** + * An implementation of a V4PayloadSigner which chunk-encodes a payload, optionally adding a chunk-signature chunk-extension, + * and/or trailers representing trailing headers with their signature at the end. + */ +@SdkInternalApi +public final class AwsChunkedV4PayloadSigner implements V4PayloadSigner { + + private final CredentialScope credentialScope; + private final int chunkSize; + private final ChecksumAlgorithm checksumAlgorithm; + private final List>> preExistingTrailers = new ArrayList<>(); + + private AwsChunkedV4PayloadSigner(Builder builder) { + this.credentialScope = Validate.paramNotNull(builder.credentialScope, "CredentialScope"); + this.chunkSize = Validate.isPositive(builder.chunkSize, "ChunkSize"); + this.checksumAlgorithm = builder.checksumAlgorithm; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public ContentStreamProvider sign(ContentStreamProvider payload, V4RequestSigningResult requestSigningResult) { + SdkHttpRequest.Builder request = requestSigningResult.getSignedRequest(); + + String checksum = request.firstMatchingHeader(X_AMZ_CONTENT_SHA256).orElseThrow( + () -> new IllegalArgumentException(X_AMZ_CONTENT_SHA256 + " must be set!") + ); + + ChunkedEncodedInputStream.Builder chunkedEncodedInputStreamBuilder = ChunkedEncodedInputStream + .builder() + .inputStream(payload.newStream()) + .chunkSize(chunkSize) + .header(chunk -> Integer.toHexString(chunk.length).getBytes(StandardCharsets.UTF_8)); + + preExistingTrailers.forEach(trailer -> chunkedEncodedInputStreamBuilder.addTrailer(() -> trailer)); + + switch (checksum) { + case STREAMING_SIGNED_PAYLOAD: { + RollingSigner rollingSigner = new RollingSigner(requestSigningResult.getSigningKey(), + requestSigningResult.getSignature()); + chunkedEncodedInputStreamBuilder.addExtension(new SigV4ChunkExtensionProvider(rollingSigner, credentialScope)); + break; + } + case STREAMING_UNSIGNED_PAYLOAD_TRAILER: + setupChecksumTrailerIfNeeded(chunkedEncodedInputStreamBuilder); + break; + case STREAMING_SIGNED_PAYLOAD_TRAILER: { + RollingSigner rollingSigner = new RollingSigner(requestSigningResult.getSigningKey(), + requestSigningResult.getSignature()); + chunkedEncodedInputStreamBuilder.addExtension(new SigV4ChunkExtensionProvider(rollingSigner, credentialScope)); + setupChecksumTrailerIfNeeded(chunkedEncodedInputStreamBuilder); + chunkedEncodedInputStreamBuilder.addTrailer( + new SigV4TrailerProvider(chunkedEncodedInputStreamBuilder.trailers(), rollingSigner, credentialScope) + ); + break; + } + default: + throw new UnsupportedOperationException(); + } + + return new ResettableContentStreamProvider(chunkedEncodedInputStreamBuilder::build); + } + + @Override + public Publisher signAsync(Publisher payload, V4RequestSigningResult requestSigningResult) { + throw new UnsupportedOperationException(); + } + + @Override + public void beforeSigning(SdkHttpRequest.Builder request, ContentStreamProvider payload) { + long encodedContentLength = 0; + long contentLength = moveContentLength(request, payload != null ? payload.newStream() : new StringInputStream("")); + setupPreExistingTrailers(request); + + // pre-existing trailers + encodedContentLength += calculateExistingTrailersLength(); + + String checksum = request.firstMatchingHeader(X_AMZ_CONTENT_SHA256).orElseThrow( + () -> new IllegalArgumentException(X_AMZ_CONTENT_SHA256 + " must be set!") + ); + + switch (checksum) { + case STREAMING_SIGNED_PAYLOAD: { + long extensionsLength = 81; // ;chunk-signature: + encodedContentLength += calculateChunksLength(contentLength, extensionsLength); + break; + } + case STREAMING_UNSIGNED_PAYLOAD_TRAILER: + if (checksumAlgorithm != null) { + encodedContentLength += calculateChecksumTrailerLength(checksumHeaderName(checksumAlgorithm)); + } + encodedContentLength += calculateChunksLength(contentLength, 0); + break; + case STREAMING_SIGNED_PAYLOAD_TRAILER: { + long extensionsLength = 81; // ;chunk-signature: + encodedContentLength += calculateChunksLength(contentLength, extensionsLength); + if (checksumAlgorithm != null) { + encodedContentLength += calculateChecksumTrailerLength(checksumHeaderName(checksumAlgorithm)); + } + encodedContentLength += 90; // x-amz-trailer-signature:\r\n + break; + } + default: + throw new UnsupportedOperationException(); + } + + // terminating \r\n + encodedContentLength += 2; + + if (checksumAlgorithm != null) { + String checksumHeaderName = checksumHeaderName(checksumAlgorithm); + request.appendHeader(X_AMZ_TRAILER, checksumHeaderName); + } + request.putHeader(Header.CONTENT_LENGTH, Long.toString(encodedContentLength)); + request.appendHeader(CONTENT_ENCODING, AWS_CHUNKED); + } + + /** + * Set up a map of pre-existing trailer (headers) for the given request to be used when chunk-encoding the payload. + *

+ * However, we need to validate that these are valid trailers. Since aws-chunked encoding adds the checksum as a trailer, it + * isn't part of the request headers, but other trailers MUST be present in the request-headers. + */ + private void setupPreExistingTrailers(SdkHttpRequest.Builder request) { + for (String header : request.matchingHeaders(X_AMZ_TRAILER)) { + List values = request.matchingHeaders(header); + if (values.isEmpty()) { + throw new IllegalArgumentException(header + " must be present in the request headers to be a valid trailer."); + } + preExistingTrailers.add(Pair.of(header, values)); + request.removeHeader(header); + } + } + + private long calculateChunksLength(long contentLength, long extensionsLength) { + long lengthInBytes = 0; + long chunkHeaderLength = Integer.toHexString(chunkSize).length(); + long numChunks = contentLength / chunkSize; + + // normal chunks + // x\r\n\r\n + lengthInBytes += numChunks * (chunkHeaderLength + extensionsLength + 2 + chunkSize + 2); + + // remaining chunk + // x\r\n\r\n + long remainingBytes = contentLength % chunkSize; + if (remainingBytes > 0) { + long remainingChunkHeaderLength = Long.toHexString(remainingBytes).length(); + lengthInBytes += remainingChunkHeaderLength + extensionsLength + 2 + remainingBytes + 2; + } + + // final chunk + // 0\r\n + lengthInBytes += 1 + extensionsLength + 2; + + return lengthInBytes; + } + + private long calculateExistingTrailersLength() { + long lengthInBytes = 0; + + for (Pair> trailer : preExistingTrailers) { + // size of trailer + lengthInBytes += calculateTrailerLength(trailer); + } + + return lengthInBytes; + } + + private long calculateTrailerLength(Pair> trailer) { + // size of trailer-header and colon + long lengthInBytes = trailer.left().length() + 1; + + // size of trailer-values + for (String value : trailer.right()) { + lengthInBytes += value.length(); + } + + // size of commas between trailer-values, 1 less comma than # of values + lengthInBytes += trailer.right().size() - 1; + + // terminating \r\n + return lengthInBytes + 2; + } + + private long calculateChecksumTrailerLength(String checksumHeaderName) { + // size of checksum trailer-header and colon + long lengthInBytes = checksumHeaderName.length() + 1; + + // get the base checksum for the algorithm + SdkChecksum sdkChecksum = fromChecksumAlgorithm(checksumAlgorithm); + // size of checksum value as encoded-string + lengthInBytes += BinaryUtils.toBase64(sdkChecksum.getChecksumBytes()).length(); + + // terminating \r\n + return lengthInBytes + 2; + } + + /** + * Add the checksum as a trailer to the chunk-encoded stream. + *

+ * If the checksum-algorithm is not present, then nothing is done. + */ + private void setupChecksumTrailerIfNeeded(ChunkedEncodedInputStream.Builder builder) { + if (checksumAlgorithm == null) { + return; + } + String checksumHeaderName = checksumHeaderName(checksumAlgorithm); + SdkChecksum sdkChecksum = fromChecksumAlgorithm(checksumAlgorithm); + ChecksumInputStream checksumInputStream = new ChecksumInputStream( + builder.inputStream(), + Collections.singleton(sdkChecksum) + ); + + TrailerProvider checksumTrailer = new ChecksumTrailerProvider(sdkChecksum, checksumHeaderName); + + builder.inputStream(checksumInputStream).addTrailer(checksumTrailer); + } + + static class Builder { + private CredentialScope credentialScope; + private Integer chunkSize; + private ChecksumAlgorithm checksumAlgorithm; + + public Builder credentialScope(CredentialScope credentialScope) { + this.credentialScope = credentialScope; + return this; + } + + public Builder chunkSize(Integer chunkSize) { + this.chunkSize = chunkSize; + return this; + } + + public Builder checksumAlgorithm(ChecksumAlgorithm checksumAlgorithm) { + this.checksumAlgorithm = checksumAlgorithm; + return this; + } + + public AwsChunkedV4PayloadSigner build() { + return new AwsChunkedV4PayloadSigner(this); + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/Checksummer.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/Checksummer.java new file mode 100644 index 000000000000..119d00d10e4f --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/Checksummer.java @@ -0,0 +1,106 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.SHA256; +import static software.amazon.awssdk.http.auth.aws.internal.signer.FlexibleChecksummer.option; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.ConstantChecksumAlgorithm; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.checksumHeaderName; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_CONTENT_SHA256; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.utils.BinaryUtils; + +/** + * An interface for defining how a checksum is formed from a payload synchronously and asynchronously. + *

+ * The implementation may choose to also manipulate the request with the checksum, such as adding it as a header. + */ +@SdkInternalApi +public interface Checksummer { + /** + * Get a default implementation of a checksummer, which calculates the SHA-256 checksum and places it in the + * x-amz-content-sha256 header. + */ + static Checksummer create() { + return new FlexibleChecksummer( + option().headerName(X_AMZ_CONTENT_SHA256).algorithm(SHA256).formatter(BinaryUtils::toHex).build() + ); + } + + /** + * Get a flexible checksummer that performs two checksums: the given checksum-algorithm and the SHA-256 checksum. It places + * the SHA-256 checksum in x-amz-content-sha256 header, and the given checksum-algorithm in the x-amz-checksum-[name] header. + */ + static Checksummer forFlexibleChecksum(ChecksumAlgorithm checksumAlgorithm) { + if (checksumAlgorithm != null) { + return new FlexibleChecksummer( + option().headerName(X_AMZ_CONTENT_SHA256).algorithm(SHA256).formatter(BinaryUtils::toHex) + .build(), + option().headerName(checksumHeaderName(checksumAlgorithm)).algorithm(checksumAlgorithm) + .formatter(BinaryUtils::toBase64).build() + ); + } + + throw new IllegalArgumentException("Checksum Algorithm cannot be null!"); + } + + /** + * Get a precomputed checksummer which places the precomputed checksum to the x-amz-content-sha256 header. + */ + static Checksummer forPrecomputed256Checksum(String precomputedSha256) { + return new PrecomputedSha256Checksummer(() -> precomputedSha256); + } + + /** + * Get a flexible checksummer that performs two checksums: the given checksum-algorithm and a precomputed checksum from the + * given checksum string. It places the precomputed checksum in x-amz-content-sha256 header, and the given checksum-algorithm + * in the x-amz-checksum-[name] header. + */ + static Checksummer forFlexibleChecksum(String precomputedSha256, ChecksumAlgorithm checksumAlgorithm) { + if (checksumAlgorithm != null) { + return new FlexibleChecksummer( + option().headerName(X_AMZ_CONTENT_SHA256).algorithm(new ConstantChecksumAlgorithm(precomputedSha256)) + .formatter(b -> new String(b, StandardCharsets.UTF_8)).build(), + option().headerName(checksumHeaderName(checksumAlgorithm)).algorithm(checksumAlgorithm) + .formatter(BinaryUtils::toBase64).build() + ); + } + + throw new IllegalArgumentException("Checksum Algorithm cannot be null!"); + } + + static Checksummer forNoOp() { + return new FlexibleChecksummer(); + } + + /** + * Given a payload, calculate a checksum and add it to the request. + */ + void checksum(ContentStreamProvider payload, SdkHttpRequest.Builder request); + + /** + * Given a payload, asynchronously calculate a checksum and promise to add it to the request. + */ + CompletableFuture checksum(Publisher payload, SdkHttpRequest.Builder request); +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/CredentialScope.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/CredentialScope.java new file mode 100644 index 000000000000..2bafec156174 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/CredentialScope.java @@ -0,0 +1,68 @@ +/* + * 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.http.auth.aws.internal.signer; + + +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.AWS4_TERMINATOR; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.formatDate; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.formatDateTime; + +import java.time.Instant; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; + +@SdkInternalApi +@Immutable +public final class CredentialScope { + private final String region; + private final String service; + private final Instant instant; + + public CredentialScope(String region, String service, Instant instant) { + this.region = region; + this.service = service; + this.instant = instant; + } + + public String getRegion() { + return region; + } + + public String getService() { + return service; + } + + public Instant getInstant() { + return instant; + } + + public String getDate() { + return formatDate(instant); + } + + public String getDatetime() { + return formatDateTime(instant); + } + + public String scope() { + return getDate() + "/" + region + "/" + service + "/" + AWS4_TERMINATOR; + } + + public String scope(AwsCredentialsIdentity credentials) { + return credentials.accessKeyId() + "/" + scope(); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultAwsV4HttpSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultAwsV4HttpSigner.java new file mode 100644 index 000000000000..05120eda8e29 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultAwsV4HttpSigner.java @@ -0,0 +1,311 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.checksumHeaderName; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.CredentialUtils.sanitizeCredentials; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.OptionalDependencyLoaderUtil.getEventStreamV4PayloadSigner; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.PRESIGN_URL_MAX_EXPIRATION_DURATION; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_EVENTS_PAYLOAD; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_SIGNED_PAYLOAD; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_SIGNED_PAYLOAD_TRAILER; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_UNSIGNED_PAYLOAD_TRAILER; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.UNSIGNED_PAYLOAD; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_TRAILER; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.Header; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.internal.signer.util.CredentialUtils; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.BaseSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; + +/** + * An implementation of a {@link AwsV4HttpSigner} that uses properties to compose v4-signers in order to delegate signing of a + * request and payload (if applicable) accordingly. + */ +@SdkInternalApi +public final class DefaultAwsV4HttpSigner implements AwsV4HttpSigner { + + private static final int DEFAULT_CHUNK_SIZE_IN_BYTES = 128 * 1024; + + @Override + public SignedRequest sign(SignRequest request) { + Checksummer checksummer = checksummer(request); + V4Properties v4Properties = v4Properties(request); + V4RequestSigner v4RequestSigner = v4RequestSigner(request, v4Properties); + V4PayloadSigner payloadSigner = v4PayloadSigner(request, v4Properties); + + return doSign(request, checksummer, v4RequestSigner, payloadSigner); + } + + @Override + public CompletableFuture signAsync(AsyncSignRequest request) { + Checksummer checksummer = checksummer(request); + V4Properties v4Properties = v4Properties(request); + V4RequestSigner v4RequestSigner = v4RequestSigner(request, v4Properties); + V4PayloadSigner payloadSigner = v4PayloadAsyncSigner(request, v4Properties); + + return doSign(request, checksummer, v4RequestSigner, payloadSigner); + } + + private static V4Properties v4Properties(BaseSignRequest request) { + Clock signingClock = request.requireProperty(SIGNING_CLOCK, Clock.systemUTC()); + Instant signingInstant = signingClock.instant(); + AwsCredentialsIdentity credentials = sanitizeCredentials(request.identity()); + String regionName = request.requireProperty(AwsV4HttpSigner.REGION_NAME); + String serviceSigningName = request.requireProperty(SERVICE_SIGNING_NAME); + CredentialScope credentialScope = new CredentialScope(regionName, serviceSigningName, signingInstant); + boolean doubleUrlEncode = request.requireProperty(DOUBLE_URL_ENCODE, true); + boolean normalizePath = request.requireProperty(NORMALIZE_PATH, true); + + return V4Properties.builder() + .credentials(credentials) + .credentialScope(credentialScope) + .signingClock(signingClock) + .doubleUrlEncode(doubleUrlEncode) + .normalizePath(normalizePath) + .build(); + } + + private static V4RequestSigner v4RequestSigner( + BaseSignRequest request, + V4Properties v4Properties) { + + AuthLocation authLocation = request.requireProperty(AUTH_LOCATION, AuthLocation.HEADER); + Duration expirationDuration = request.property(EXPIRATION_DURATION); + boolean isAnonymous = CredentialUtils.isAnonymous(request.identity()); + + if (isAnonymous) { + return V4RequestSigner.anonymous(v4Properties); + } + + Function requestSigner; + switch (authLocation) { + case HEADER: + if (expirationDuration != null) { + throw new UnsupportedOperationException( + String.format("%s is not supported for %s.", EXPIRATION_DURATION, AuthLocation.HEADER)); + } + requestSigner = V4RequestSigner::header; + break; + case QUERY_STRING: + requestSigner = expirationDuration == null ? V4RequestSigner::query : + properties -> V4RequestSigner.presigned(properties, + validateExpirationDuration(expirationDuration)); + break; + default: + throw new UnsupportedOperationException("Unsupported authLocation " + authLocation); + } + + return requestSigner.apply(v4Properties); + } + + private static Checksummer checksummer(BaseSignRequest request) { + boolean isPayloadSigning = isPayloadSigning(request); + boolean isEventStreaming = isEventStreaming(request.request()); + boolean hasChecksumHeader = hasChecksumHeader(request); + boolean isChunkEncoding = request.requireProperty(CHUNK_ENCODING_ENABLED, false); + boolean isTrailing = request.request().firstMatchingHeader(X_AMZ_TRAILER).isPresent(); + boolean isFlexible = request.hasProperty(CHECKSUM_ALGORITHM) && !hasChecksumHeader; + boolean isAnonymous = CredentialUtils.isAnonymous(request.identity()); + + if (isEventStreaming) { + return Checksummer.forPrecomputed256Checksum(STREAMING_EVENTS_PAYLOAD); + } + + if (isPayloadSigning) { + if (isChunkEncoding) { + if (isFlexible || isTrailing) { + return Checksummer.forPrecomputed256Checksum(STREAMING_SIGNED_PAYLOAD_TRAILER); + } + return Checksummer.forPrecomputed256Checksum(STREAMING_SIGNED_PAYLOAD); + } + + if (isFlexible) { + return Checksummer.forFlexibleChecksum(request.property(CHECKSUM_ALGORITHM)); + } + return Checksummer.create(); + } + + if (isFlexible || isTrailing) { + if (isChunkEncoding) { + return Checksummer.forPrecomputed256Checksum(STREAMING_UNSIGNED_PAYLOAD_TRAILER); + } + } + + if (isFlexible) { + return Checksummer.forFlexibleChecksum(UNSIGNED_PAYLOAD, request.property(CHECKSUM_ALGORITHM)); + } + + if (isAnonymous) { + return Checksummer.forNoOp(); + } + + return Checksummer.forPrecomputed256Checksum(UNSIGNED_PAYLOAD); + } + + private static V4PayloadSigner v4PayloadSigner( + SignRequest request, + V4Properties properties) { + + boolean isPayloadSigning = isPayloadSigning(request); + boolean isEventStreaming = isEventStreaming(request.request()); + boolean isChunkEncoding = request.requireProperty(CHUNK_ENCODING_ENABLED, false); + boolean isTrailing = request.request().firstMatchingHeader(X_AMZ_TRAILER).isPresent(); + boolean isFlexible = request.hasProperty(CHECKSUM_ALGORITHM) && !hasChecksumHeader(request); + + if (isEventStreaming) { + if (isPayloadSigning) { + return getEventStreamV4PayloadSigner( + properties.getCredentials(), + properties.getCredentialScope(), + properties.getSigningClock() + ); + } + throw new UnsupportedOperationException("Unsigned payload is not supported with event-streaming."); + } + + if (useChunkEncoding(isPayloadSigning, isChunkEncoding, isTrailing || isFlexible)) { + return AwsChunkedV4PayloadSigner.builder() + .credentialScope(properties.getCredentialScope()) + .chunkSize(DEFAULT_CHUNK_SIZE_IN_BYTES) + .checksumAlgorithm(request.property(CHECKSUM_ALGORITHM)) + .build(); + } + + return V4PayloadSigner.create(); + } + + private static V4PayloadSigner v4PayloadAsyncSigner( + AsyncSignRequest request, + V4Properties properties) { + + boolean isPayloadSigning = request.requireProperty(PAYLOAD_SIGNING_ENABLED, true); + boolean isEventStreaming = isEventStreaming(request.request()); + boolean isChunkEncoding = request.requireProperty(CHUNK_ENCODING_ENABLED, false); + + if (isEventStreaming) { + if (isPayloadSigning) { + return getEventStreamV4PayloadSigner( + properties.getCredentials(), + properties.getCredentialScope(), + properties.getSigningClock() + ); + } + throw new UnsupportedOperationException("Unsigned payload is not supported with event-streaming."); + } + + if (isChunkEncoding && isPayloadSigning) { + throw new UnsupportedOperationException("Chunked encoding and payload signing is not supported in async client. Use" + + " sync client instead"); + } + + return V4PayloadSigner.create(); + } + + private static SignedRequest doSign(SignRequest request, + Checksummer checksummer, + V4RequestSigner requestSigner, + V4PayloadSigner payloadSigner) { + + SdkHttpRequest.Builder requestBuilder = request.request().toBuilder(); + + checksummer.checksum(request.payload().orElse(null), requestBuilder); + + payloadSigner.beforeSigning(requestBuilder, request.payload().orElse(null)); + + V4RequestSigningResult requestSigningResult = requestSigner.sign(requestBuilder); + + ContentStreamProvider payload = request.payload().map(p -> payloadSigner.sign(p, requestSigningResult)).orElse(null); + + return SignedRequest.builder() + .request(requestSigningResult.getSignedRequest().build()) + .payload(payload) + .build(); + } + + private static CompletableFuture doSign(AsyncSignRequest request, + Checksummer checksummer, + V4RequestSigner requestSigner, + V4PayloadSigner payloadSigner) { + + SdkHttpRequest.Builder requestBuilder = request.request().toBuilder(); + + CompletableFuture resultSigningResultFuture = + checksummer.checksum(request.payload().orElse(null), requestBuilder) + .thenApply(__ -> requestSigner.sign(requestBuilder)); + + return resultSigningResultFuture.thenApply( + resultSigningResult -> AsyncSignedRequest.builder() + .request(resultSigningResult.getSignedRequest().build()) + .payload(payloadSigner.signAsync(request.payload().orElse(null), resultSigningResult)) + .build() + ); + } + + private static Duration validateExpirationDuration(Duration expirationDuration) { + if (expirationDuration.compareTo(PRESIGN_URL_MAX_EXPIRATION_DURATION) > 0) { + throw new IllegalArgumentException( + "Requests that are pre-signed by SigV4 algorithm are valid for at most 7" + + " days. The expiration duration set on the current request [" + expirationDuration + "]" + + " has exceeded this limit." + ); + } + return expirationDuration; + } + + private static boolean isPayloadSigning(BaseSignRequest request) { + boolean isAnonymous = CredentialUtils.isAnonymous(request.identity()); + boolean isPayloadSigningEnabled = request.requireProperty(PAYLOAD_SIGNING_ENABLED, true); + boolean isEncrypted = "https".equals(request.request().protocol()); + + return !isAnonymous && (isPayloadSigningEnabled || !isEncrypted); + } + + private static boolean isEventStreaming(SdkHttpRequest request) { + return "application/vnd.amazon.eventstream".equals(request.firstMatchingHeader(Header.CONTENT_TYPE).orElse("")); + } + + private static boolean hasChecksumHeader(BaseSignRequest request) { + ChecksumAlgorithm checksumAlgorithm = request.property(CHECKSUM_ALGORITHM); + + if (checksumAlgorithm != null) { + String checksumHeaderName = checksumHeaderName(checksumAlgorithm); + return request.request().firstMatchingHeader(checksumHeaderName).isPresent(); + } + + return false; + } + + private static boolean useChunkEncoding(boolean payloadSigningEnabled, boolean chunkEncodingEnabled, + boolean isTrailingOrFlexible) { + + return (payloadSigningEnabled && chunkEncodingEnabled) || (chunkEncodingEnabled && isTrailingOrFlexible); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultV4PayloadSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultV4PayloadSigner.java new file mode 100644 index 000000000000..407da90b8394 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultV4PayloadSigner.java @@ -0,0 +1,37 @@ +/* + * 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.http.auth.aws.internal.signer; + +import java.nio.ByteBuffer; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.ContentStreamProvider; + +/** + * A default implementation of a payload signer that is a no-op, since payloads are most commonly unsigned. + */ +@SdkInternalApi +public class DefaultV4PayloadSigner implements V4PayloadSigner { + @Override + public ContentStreamProvider sign(ContentStreamProvider payload, V4RequestSigningResult requestSigningResult) { + return payload; + } + + @Override + public Publisher signAsync(Publisher payload, V4RequestSigningResult requestSigningResult) { + return payload; + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultV4RequestSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultV4RequestSigner.java new file mode 100644 index 000000000000..1935fa10107a --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultV4RequestSigner.java @@ -0,0 +1,99 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.AWS4_SIGNING_ALGORITHM; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.deriveSigningKey; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.hashCanonicalRequest; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant; +import software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils; +import software.amazon.awssdk.utils.BinaryUtils; +import software.amazon.awssdk.utils.Logger; + +/** + * The default implementation of a v4-request-signer. It performs each step of the SigV4 signing process, but does not add the + * signature or auth information to the request itself. + *

+ * All signing information, such as signature, signing key, canonical request, etc. is present in result object that is returned. + * This can be used by the caller to add the auth info to the request, such as adding the signature as a query parameter or + * building an authorization header using the signature and canonical request headers. + */ +@SdkInternalApi +public final class DefaultV4RequestSigner implements V4RequestSigner { + + private static final Logger LOG = Logger.loggerFor(DefaultV4RequestSigner.class); + + private final V4Properties properties; + private final String contentHash; + + public DefaultV4RequestSigner(V4Properties properties, String contentHash) { + this.properties = properties; + this.contentHash = contentHash; + } + + @Override + public V4RequestSigningResult sign(SdkHttpRequest.Builder requestBuilder) { + // Step 1: Create a canonical request + V4CanonicalRequest canonicalRequest = createCanonicalRequest(requestBuilder.build(), contentHash); + + // Step 2: Create a hash of the canonical request + String canonicalRequestHash = hashCanonicalRequest(canonicalRequest.getCanonicalRequestString()); + + // Step 2: Create a hash of the canonical request + String stringToSign = createSignString(canonicalRequestHash); + + // Step 4: Calculate the signature + byte[] signingKey = createSigningKey(); + + String signature = createSignature(stringToSign, signingKey); + + // Step 5: Return the results (including signature) of request signing + return new V4RequestSigningResult(contentHash, signingKey, signature, canonicalRequest, requestBuilder); + } + + private V4CanonicalRequest createCanonicalRequest(SdkHttpRequest request, String contentHash) { + return new V4CanonicalRequest(request, contentHash, new V4CanonicalRequest.Options( + properties.shouldDoubleUrlEncode(), + properties.shouldNormalizePath() + )); + } + + private String createSignString(String canonicalRequestHash) { + LOG.debug(() -> "AWS4 Canonical Request Hash: " + canonicalRequestHash); + + String stringToSign = AWS4_SIGNING_ALGORITHM + + SignerConstant.LINE_SEPARATOR + + properties.getCredentialScope().getDatetime() + + SignerConstant.LINE_SEPARATOR + + properties.getCredentialScope().scope() + + SignerConstant.LINE_SEPARATOR + + canonicalRequestHash; + + LOG.debug(() -> "AWS4 String to sign: " + stringToSign); + return stringToSign; + } + + private byte[] createSigningKey() { + return deriveSigningKey(properties.getCredentials(), properties.getCredentialScope()); + } + + private String createSignature(String stringToSign, byte[] signingKey) { + return BinaryUtils.toHex(SignerUtils.computeSignature(stringToSign, signingKey)); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummer.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummer.java new file mode 100644 index 000000000000..e65d14c2c4ee --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummer.java @@ -0,0 +1,134 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.fromChecksumAlgorithm; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.readAll; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.getBinaryRequestPayloadStream; + +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.SdkChecksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.io.ChecksumInputStream; +import software.amazon.awssdk.http.auth.aws.internal.signer.io.ChecksumSubscriber; +import software.amazon.awssdk.utils.Validate; + +/** + * A "flexible" implementation of a checksummer. It takes a map of checksums and their header names, computes them efficiently by + * updating each checksum while reading the payload (once), and adds the computed checksum strings to the request using the given + * header names in the map. This should be used in cases where a (flexible) checksum algorithm is present during signing. + */ +@SdkInternalApi +public final class FlexibleChecksummer implements Checksummer { + private final Collection

+ * https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html#create-canonical-request + *

+ */ +@SdkInternalApi +@Immutable +public final class V4CanonicalRequest { + private static final List HEADERS_TO_IGNORE_IN_LOWER_CASE = + Arrays.asList("connection", "x-amzn-trace-id", "user-agent", "expect"); + + private final SdkHttpRequest request; + private final String contentHash; + private final Options options; + + private final String canonicalUri; + private final SortedMap> canonicalParams; + private final List>> canonicalHeaders; + private final String canonicalParamsString; + private final String canonicalHeadersString; + private final String signedHeadersString; + private final String canonicalRequestString; + + /** + * Create a canonical request. + *

+ * Each parameter of a canonical request is set upon creation of this object. + *

+ * To get such a parameter (i.e. the canonical request string), simply call the getter for that parameter (i.e. + * getCanonicalRequestString()) + */ + public V4CanonicalRequest(SdkHttpRequest request, String contentHash, Options options) { + this.request = request; + this.contentHash = contentHash; + this.options = options; + + this.canonicalParams = getCanonicalQueryParams(request); + this.canonicalHeaders = getCanonicalHeaders(request); + this.canonicalUri = getCanonicalUri(request, options); + this.canonicalParamsString = getCanonicalQueryString(canonicalParams); + this.canonicalHeadersString = getCanonicalHeadersString(canonicalHeaders); + this.signedHeadersString = getSignedHeadersString(canonicalHeaders); + this.canonicalRequestString = getCanonicalRequestString(request.method().toString(), canonicalUri, canonicalParamsString, + canonicalHeadersString, signedHeadersString, contentHash); + } + + /** + * Get the canonical request string. + *

+ * Each {@link String} parameter is separated by a newline character. + */ + private static String getCanonicalRequestString(String httpMethod, String canonicalUri, String canonicalParamsString, + String canonicalHeadersString, String signedHeadersString, + String contentHash) { + return httpMethod + SignerConstant.LINE_SEPARATOR + + canonicalUri + SignerConstant.LINE_SEPARATOR + + canonicalParamsString + SignerConstant.LINE_SEPARATOR + + canonicalHeadersString + SignerConstant.LINE_SEPARATOR + + signedHeadersString + SignerConstant.LINE_SEPARATOR + + contentHash; + } + + /** + * Get the uri-encoded version of the absolute path component URL. + *

+ * If the path is empty, a single-forward slash ('/') is returned. + */ + private static String getCanonicalUri(SdkHttpRequest request, Options options) { + String path = options.normalizePath ? request.getUri().normalize().getRawPath() + : request.encodedPath(); + + if (StringUtils.isEmpty(path)) { + return "/"; + } + + if (options.doubleUrlEncode) { + path = SdkHttpUtils.urlEncodeIgnoreSlashes(path); + } + + if (!path.startsWith("/")) { + path += "/"; + } + + // Normalization can leave a trailing slash at the end of the resource path, + // even if the input path doesn't end with one. Example input: /foo/bar/. + // Remove the trailing slash if the input path doesn't end with one. + boolean trimTrailingSlash = options.normalizePath && + path.length() > 1 && + !request.getUri().getPath().endsWith("/") && + path.charAt(path.length() - 1) == '/'; + + if (trimTrailingSlash) { + path = path.substring(0, path.length() - 1); + } + return path; + } + + /** + * Get the sorted map of query parameters that are to be signed. + */ + private static SortedMap> getCanonicalQueryParams(SdkHttpRequest request) { + SortedMap> sorted = new TreeMap<>(); + + // Signing protocol expects the param values also to be sorted after url + // encoding in addition to sorted parameter names. + request.forEachRawQueryParameter((key, values) -> { + if (StringUtils.isEmpty(key)) { + // Do not sign empty keys. + return; + } + + String encodedParamName = SdkHttpUtils.urlEncode(key); + + List encodedValues = new ArrayList<>(values.size()); + for (String value : values) { + String encodedValue = SdkHttpUtils.urlEncode(value); + + // Null values should be treated as empty for the purposes of signing, not missing. + // For example "?foo=" instead of "?foo". + String signatureFormattedEncodedValue = encodedValue == null ? "" : encodedValue; + + encodedValues.add(signatureFormattedEncodedValue); + } + Collections.sort(encodedValues); + sorted.put(encodedParamName, encodedValues); + + }); + return sorted; + } + + /** + * Get the string representing query string parameters. Parameters are URL-encoded and separated by an ampersand. + *

+ * Reserved characters are percent-encoded, names and values are encoded separately and empty parameters have an equals-sign + * appended before encoding. + *

+ * After encoding, parameters are sorted alphanetically by key name. + *

+ * If no query string is given, an empty string ("") is returned. + */ + private static String getCanonicalQueryString(SortedMap> canonicalParams) { + StringBuilder stringBuilder = new StringBuilder(512); + SdkHttpUtils.flattenQueryParameters(stringBuilder, canonicalParams); + + return stringBuilder.toString(); + } + + /** + * Get the list of headers that are to be signed. + *

+ * If calling from a site with the request object handy, this method should be used instead of passing the headers themselves, + * as doing so creates a redundant copy. + */ + public static List>> getCanonicalHeaders(SdkHttpRequest request) { + List>> result = new ArrayList<>(request.numHeaders()); + + // headers retrieved from the request are already sorted case-insensitively + request.forEachHeader((key, value) -> { + String lowerCaseHeader = lowerCase(key); + if (!HEADERS_TO_IGNORE_IN_LOWER_CASE.contains(lowerCaseHeader)) { + result.add(Pair.of(lowerCaseHeader, value)); + } + }); + + result.sort(Comparator.comparing(Pair::left)); + + return result; + } + + /** + * Get the list of headers that are to be signed. The supplied map of headers is expected to be sorted case-insensitively. + */ + public static List>> getCanonicalHeaders(Map> headers) { + List>> result = new ArrayList<>(headers.size()); + + headers.forEach((key, value) -> { + String lowerCaseHeader = lowerCase(key); + if (!HEADERS_TO_IGNORE_IN_LOWER_CASE.contains(lowerCaseHeader)) { + result.add(Pair.of(lowerCaseHeader, value)); + } + }); + + result.sort(Comparator.comparing(Pair::left)); + + return result; + } + + /** + * Get the string representing the headers that will be signed and their values. The input list is expected to be sorted + * case-insensitively. + *

+ * The output string will have header names as lower-case, sorted in alphabetical order, and followed by a colon. + *

+ * Values are trimmed of any leading/trailing spaces, sequential spaces are converted to single space, and multiple values are + * comma separated. + *

+ * Each header-value pair is separated by a newline. + */ + public static String getCanonicalHeadersString(List>> canonicalHeaders) { + StringBuilder result = new StringBuilder(512); + canonicalHeaders.forEach(header -> { + result.append(header.left()); + result.append(":"); + for (String headerValue : header.right()) { + addAndTrim(result, headerValue); + result.append(","); + } + result.setLength(result.length() - 1); + result.append("\n"); + }); + return result.toString(); + } + + /** + * Get the string representing which headers are part of the signing process. Header names are separated by a semicolon. + */ + public static String getSignedHeadersString(List>> canonicalHeaders) { + String signedHeadersString; + StringBuilder headersString = new StringBuilder(512); + for (Pair> header : canonicalHeaders) { + headersString.append(header.left()).append(";"); + } + // get rid of trailing semicolon + signedHeadersString = headersString.toString(); + + boolean trimTrailingSemicolon = signedHeadersString.length() > 1 && + signedHeadersString.endsWith(";"); + + if (trimTrailingSemicolon) { + signedHeadersString = signedHeadersString.substring(0, signedHeadersString.length() - 1); + } + return signedHeadersString; + } + + private static boolean isWhiteSpace(char ch) { + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\u000b' || ch == '\r' || ch == '\f'; + } + + /** + * "The addAndTrim function removes excess white space before and after values, and converts sequential spaces to a single + * space." + *

+ * https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + *

+ * The collapse-whitespace logic is equivalent to: + *

+     *     value.replaceAll("\\s+", " ")
+     * 
+ * but does not create a Pattern object that needs to compile the match string; it also prevents us from having to make a + * Matcher object as well. + */ + private static void addAndTrim(StringBuilder result, String value) { + int lengthBefore = result.length(); + boolean isStart = true; + boolean previousIsWhiteSpace = false; + + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + if (isWhiteSpace(ch)) { + if (previousIsWhiteSpace || isStart) { + continue; + } + result.append(' '); + previousIsWhiteSpace = true; + } else { + result.append(ch); + isStart = false; + previousIsWhiteSpace = false; + } + } + + if (lengthBefore == result.length()) { + return; + } + + int lastNonWhitespaceChar = result.length() - 1; + while (isWhiteSpace(result.charAt(lastNonWhitespaceChar))) { + --lastNonWhitespaceChar; + } + + result.setLength(lastNonWhitespaceChar + 1); + } + + /** + * Get the canonical request string. + */ + public String getCanonicalRequestString() { + return canonicalRequestString; + } + + public String getSignedHeadersString() { + return signedHeadersString; + } + + /** + * A class for representing options used when creating a {@link V4CanonicalRequest} + */ + public static class Options { + final boolean doubleUrlEncode; + final boolean normalizePath; + + public Options(boolean doubleUrlEncode, boolean normalizePath) { + this.doubleUrlEncode = doubleUrlEncode; + this.normalizePath = normalizePath; + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4PayloadSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4PayloadSigner.java new file mode 100644 index 000000000000..189fbe420085 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4PayloadSigner.java @@ -0,0 +1,51 @@ +/* + * 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.http.auth.aws.internal.signer; + +import java.nio.ByteBuffer; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.SdkHttpRequest; + +/** + * An interface for defining how to sign a payload via SigV4. + */ +@SdkInternalApi +public interface V4PayloadSigner { + /** + * Get a default implementation of a SigV4 payload signer. + */ + static V4PayloadSigner create() { + return new DefaultV4PayloadSigner(); + } + + /** + * Given a payload and result of request signing, sign the payload via the SigV4 process. + */ + ContentStreamProvider sign(ContentStreamProvider payload, V4RequestSigningResult requestSigningResult); + + /** + * Given a payload and result of request signing, sign the payload via the SigV4 process. + */ + Publisher signAsync(Publisher payload, V4RequestSigningResult requestSigningResult); + + /** + * Modify a request before it is signed, such as changing headers or query-parameters. + */ + default void beforeSigning(SdkHttpRequest.Builder request, ContentStreamProvider payload) { + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4Properties.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4Properties.java new file mode 100644 index 000000000000..9f62687d90ee --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4Properties.java @@ -0,0 +1,109 @@ +/* + * 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.http.auth.aws.internal.signer; + +import java.time.Clock; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.spi.signer.BaseSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.Validate; + + +/** + * A class which contains "properties" relevant to SigV4. These properties can be derived {@link SignerProperty}'s on a + * {@link BaseSignRequest}. + */ +@SdkInternalApi +@Immutable +public final class V4Properties { + private final AwsCredentialsIdentity credentials; + private final CredentialScope credentialScope; + private final Clock signingClock; + private final boolean doubleUrlEncode; + private final boolean normalizePath; + + + private V4Properties(Builder builder) { + this.credentials = Validate.paramNotNull(builder.credentials, "Credentials"); + this.credentialScope = Validate.paramNotNull(builder.credentialScope, "CredentialScope"); + this.signingClock = Validate.paramNotNull(builder.signingClock, "SigningClock"); + this.doubleUrlEncode = Validate.getOrDefault(builder.doubleUrlEncode, () -> true); + this.normalizePath = Validate.getOrDefault(builder.normalizePath, () -> true); + } + + public static Builder builder() { + return new Builder(); + } + + public AwsCredentialsIdentity getCredentials() { + return credentials; + } + + public CredentialScope getCredentialScope() { + return credentialScope; + } + + public Clock getSigningClock() { + return signingClock; + } + + public boolean shouldDoubleUrlEncode() { + return doubleUrlEncode; + } + + public boolean shouldNormalizePath() { + return normalizePath; + } + + public static class Builder { + private AwsCredentialsIdentity credentials; + private CredentialScope credentialScope; + private Clock signingClock; + private Boolean doubleUrlEncode; + private Boolean normalizePath; + + public Builder credentials(AwsCredentialsIdentity credentials) { + this.credentials = Validate.paramNotNull(credentials, "Credentials"); + return this; + } + + public Builder credentialScope(CredentialScope credentialScope) { + this.credentialScope = Validate.paramNotNull(credentialScope, "CredentialScope"); + return this; + } + + public Builder signingClock(Clock signingClock) { + this.signingClock = signingClock; + return this; + } + + public Builder doubleUrlEncode(Boolean doubleUrlEncode) { + this.doubleUrlEncode = doubleUrlEncode; + return this; + } + + public Builder normalizePath(Boolean normalizePath) { + this.normalizePath = normalizePath; + return this; + } + + public V4Properties build() { + return new V4Properties(this); + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4RequestSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4RequestSigner.java new file mode 100644 index 000000000000..67ae2af1767e --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4RequestSigner.java @@ -0,0 +1,153 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.V4CanonicalRequest.getCanonicalHeaders; +import static software.amazon.awssdk.http.auth.aws.internal.signer.V4CanonicalRequest.getSignedHeadersString; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.AWS4_SIGNING_ALGORITHM; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_CONTENT_SHA256; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.addDateHeader; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.addHostHeader; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.formatDateTime; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.getContentHash; + +import java.time.Duration; +import java.util.List; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant; +import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; +import software.amazon.awssdk.utils.Pair; + +/** + * An interface which declares an algorithm that takes a request and a content-hash and signs the request according to the SigV4 + * process. + */ +@SdkInternalApi +public interface V4RequestSigner { + /** + * Retrieve an implementation of a V4RequestSigner, which signs the request, but does not add authentication to the request. + */ + static V4RequestSigner create(V4Properties properties, String contentHash) { + return new DefaultV4RequestSigner(properties, contentHash); + } + + /** + * Retrieve an implementation of a V4RequestSigner, which signs the request and adds authentication through headers. + */ + static V4RequestSigner header(V4Properties properties) { + return requestBuilder -> { + // Add pre-requisites + if (properties.getCredentials() instanceof AwsSessionCredentialsIdentity) { + requestBuilder.putHeader(SignerConstant.X_AMZ_SECURITY_TOKEN, + ((AwsSessionCredentialsIdentity) properties.getCredentials()).sessionToken()); + } + addHostHeader(requestBuilder); + addDateHeader(requestBuilder, formatDateTime(properties.getCredentialScope().getInstant())); + + V4RequestSigningResult result = create(properties, getContentHash(requestBuilder)).sign(requestBuilder); + + // Add the signature within an authorization header + String authHeader = AWS4_SIGNING_ALGORITHM + + " Credential=" + properties.getCredentialScope().scope(properties.getCredentials()) + + ", SignedHeaders=" + result.getCanonicalRequest().getSignedHeadersString() + + ", Signature=" + result.getSignature(); + + requestBuilder.putHeader(SignerConstant.AUTHORIZATION, authHeader); + return result; + }; + } + + /** + * Retrieve an implementation of a V4RequestSigner, which signs the request and adds authentication through query parameters. + */ + static V4RequestSigner query(V4Properties properties) { + return requestBuilder -> { + // Add pre-requisites + if (properties.getCredentials() instanceof AwsSessionCredentialsIdentity) { + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_SECURITY_TOKEN, + ((AwsSessionCredentialsIdentity) properties.getCredentials()).sessionToken()); + } + // We have to add the host-header here explicitly, since query-signed request requires it in the signed-header param + addHostHeader(requestBuilder); + + List>> canonicalHeaders = getCanonicalHeaders(requestBuilder.build()); + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_ALGORITHM, AWS4_SIGNING_ALGORITHM); + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_DATE, properties.getCredentialScope().getDatetime()); + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_SIGNED_HEADERS, getSignedHeadersString(canonicalHeaders)); + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_CREDENTIAL, + properties.getCredentialScope().scope(properties.getCredentials())); + + V4RequestSigningResult result = create(properties, getContentHash(requestBuilder)).sign(requestBuilder); + + // Add the signature + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_SIGNATURE, result.getSignature()); + + return result; + }; + } + + /** + * Retrieve an implementation of a V4RequestSigner, which signs the request and adds authentication through query parameters, + * which includes an expiration param, signalling how long a request signature is valid. + */ + static V4RequestSigner presigned(V4Properties properties, Duration expirationDuration) { + return requestBuilder -> { + // Add pre-requisites + if (properties.getCredentials() instanceof AwsSessionCredentialsIdentity) { + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_SECURITY_TOKEN, + ((AwsSessionCredentialsIdentity) properties.getCredentials()).sessionToken()); + } + // We have to add the host-header here explicitly, since pre-signed request requires it in the signed-header param + addHostHeader(requestBuilder); + + // Pre-signed requests shouldn't have the content-hash header + String contentHash = getContentHash(requestBuilder); + requestBuilder.removeHeader(X_AMZ_CONTENT_SHA256); + + List>> canonicalHeaders = getCanonicalHeaders(requestBuilder.build()); + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_ALGORITHM, AWS4_SIGNING_ALGORITHM); + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_DATE, properties.getCredentialScope().getDatetime()); + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_SIGNED_HEADERS, getSignedHeadersString(canonicalHeaders)); + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_CREDENTIAL, + properties.getCredentialScope().scope(properties.getCredentials())); + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_EXPIRES, Long.toString(expirationDuration.getSeconds())); + + V4RequestSigningResult result = create(properties, contentHash).sign(requestBuilder); + + // Add the signature + requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_SIGNATURE, result.getSignature()); + + return result; + + }; + } + + /** + * Retrieve an implementation of a V4RequestSigner to handle the anonymous credentials case, where the request is not + * sigend at all. + */ + static V4RequestSigner anonymous(V4Properties properties) { + return requestBuilder -> + new V4RequestSigningResult("", new byte[] {}, null, null, requestBuilder); + } + + /** + * Given a request builder, sign the request and return a result containing the signed request and its properties. + */ + V4RequestSigningResult sign(SdkHttpRequest.Builder requestBuilder); +} + diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4RequestSigningResult.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4RequestSigningResult.java new file mode 100644 index 000000000000..c1ee51e51eef --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4RequestSigningResult.java @@ -0,0 +1,63 @@ +/* + * 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.http.auth.aws.internal.signer; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.SdkHttpRequest; + +/** + * A container for data produced during and as a result of the SigV4 request signing process. + */ +@SdkInternalApi +// TODO(sra-identity-auth): This is currently not @Immutable because signedRequest is a Builder. Is Builder needed? If it could +// hold reference to SdkHttpRequest instead, this class would be @Immutable. +public final class V4RequestSigningResult { + private final String contentHash; + private final byte[] signingKey; + private final String signature; + private final V4CanonicalRequest canonicalRequest; + private final SdkHttpRequest.Builder signedRequest; + + public V4RequestSigningResult(String contentHash, byte[] signingKey, String signature, + V4CanonicalRequest canonicalRequest, SdkHttpRequest.Builder signedRequest) { + this.contentHash = contentHash; + this.signingKey = signingKey.clone(); + this.signature = signature; + this.canonicalRequest = canonicalRequest; + this.signedRequest = signedRequest; + } + + public String getContentHash() { + return contentHash; + } + + public byte[] getSigningKey() { + return signingKey.clone(); + } + + public String getSignature() { + return signature; + } + + public V4CanonicalRequest getCanonicalRequest() { + return canonicalRequest; + } + + public SdkHttpRequest.Builder getSignedRequest() { + return signedRequest; + } +} + diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/ConstantChecksum.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/ConstantChecksum.java new file mode 100644 index 000000000000..b3f33db111f3 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/ConstantChecksum.java @@ -0,0 +1,58 @@ +/* + * 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.http.auth.aws.internal.signer.checksums; + +import java.nio.charset.StandardCharsets; +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * Implementation of {@link SdkChecksum} to provide a constant checksum. + */ +@SdkInternalApi +public class ConstantChecksum implements SdkChecksum { + + private final String value; + + public ConstantChecksum(String value) { + this.value = value; + } + + @Override + public void update(int b) { + } + + @Override + public void update(byte[] b, int off, int len) { + } + + @Override + public long getValue() { + throw new UnsupportedOperationException("Use getChecksumBytes() instead."); + } + + @Override + public void reset() { + } + + @Override + public byte[] getChecksumBytes() { + return value.getBytes(StandardCharsets.UTF_8); + } + + @Override + public void mark(int readLimit) { + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Crc32CChecksum.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Crc32CChecksum.java new file mode 100644 index 000000000000..def890a88e94 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Crc32CChecksum.java @@ -0,0 +1,108 @@ +/* + * 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.http.auth.aws.internal.signer.checksums; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.zip.Checksum; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.crt.checksums.CRC32C; +import software.amazon.awssdk.utils.ClassLoaderHelper; + +/** + * Implementation of {@link SdkChecksum} to calculate an CRC32C checksum. + */ +@SdkInternalApi +public class Crc32CChecksum implements SdkChecksum { + + private static final String CRT_CLASSPATH_FOR_CRC32C = "software.amazon.awssdk.crt.checksums.CRC32C"; + + private Checksum crc32c; + private Checksum lastMarkedCrc32C; + + /** + * Creates CRT Based Crc32C checksum if Crt classpath for Crc32c is loaded, else create Sdk Implemented Crc32c + */ + public Crc32CChecksum() { + if (isCrtAvailable()) { + crc32c = new CRC32C(); + } else { + crc32c = SdkCrc32CChecksum.create(); + } + } + + private static boolean isCrtAvailable() { + try { + ClassLoaderHelper.loadClass(CRT_CLASSPATH_FOR_CRC32C, false); + } catch (ClassNotFoundException e) { + return false; + } + + return true; + } + + private static byte[] longToByte(Long input) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(input); + return buffer.array(); + } + + @Override + public byte[] getChecksumBytes() { + return Arrays.copyOfRange(longToByte(crc32c.getValue()), 4, 8); + } + + @Override + public void mark(int readLimit) { + this.lastMarkedCrc32C = cloneChecksum(crc32c); + } + + @Override + public void update(int b) { + crc32c.update(b); + } + + @Override + public void update(byte[] b, int off, int len) { + crc32c.update(b, off, len); + } + + @Override + public long getValue() { + return crc32c.getValue(); + } + + @Override + public void reset() { + if (lastMarkedCrc32C == null) { + crc32c.reset(); + } else { + crc32c = cloneChecksum(lastMarkedCrc32C); + } + } + + private Checksum cloneChecksum(Checksum checksum) { + if (checksum instanceof CRC32C) { + return (Checksum) ((CRC32C) checksum).clone(); + } + + if (checksum instanceof SdkCrc32CChecksum) { + return (Checksum) ((SdkCrc32CChecksum) checksum).clone(); + } + + throw new IllegalStateException("Unsupported checksum"); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Crc32Checksum.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Crc32Checksum.java new file mode 100644 index 000000000000..016c941e1e5e --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Crc32Checksum.java @@ -0,0 +1,108 @@ +/* + * 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.http.auth.aws.internal.signer.checksums; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.zip.Checksum; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.crt.checksums.CRC32; +import software.amazon.awssdk.utils.ClassLoaderHelper; + +/** + * Implementation of {@link SdkChecksum} to calculate an CRC32 checksum. + */ +@SdkInternalApi +public class Crc32Checksum implements SdkChecksum { + + private static final String CRT_CLASSPATH_FOR_CRC32 = "software.amazon.awssdk.crt.checksums.CRC32"; + + private Checksum crc32; + private Checksum lastMarkedCrc32; + + /** + * Creates CRT Based Crc32 checksum if Crt classpath for Crc32 is loaded, else create Sdk Implemented Crc32. + */ + public Crc32Checksum() { + if (isCrtAvailable()) { + crc32 = new CRC32(); + } else { + crc32 = SdkCrc32Checksum.create(); + } + } + + private static boolean isCrtAvailable() { + try { + ClassLoaderHelper.loadClass(CRT_CLASSPATH_FOR_CRC32, false); + } catch (ClassNotFoundException e) { + return false; + } + + return true; + } + + private static byte[] longToByte(Long input) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(input); + return buffer.array(); + } + + @Override + public byte[] getChecksumBytes() { + return Arrays.copyOfRange(longToByte(crc32.getValue()), 4, 8); + } + + @Override + public void mark(int readLimit) { + this.lastMarkedCrc32 = cloneChecksum(crc32); + } + + @Override + public void update(int b) { + crc32.update(b); + } + + @Override + public void update(byte[] b, int off, int len) { + crc32.update(b, off, len); + } + + @Override + public long getValue() { + return crc32.getValue(); + } + + @Override + public void reset() { + if (lastMarkedCrc32 == null) { + crc32.reset(); + } else { + crc32 = cloneChecksum(lastMarkedCrc32); + } + } + + private Checksum cloneChecksum(Checksum checksum) { + if (checksum instanceof CRC32) { + return (Checksum) ((CRC32) checksum).clone(); + } + + if (checksum instanceof SdkCrc32Checksum) { + return (Checksum) ((SdkCrc32Checksum) checksum).clone(); + } + + throw new IllegalStateException("Unsupported checksum"); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Md5Checksum.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Md5Checksum.java new file mode 100644 index 000000000000..3db1c5bd4fa6 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Md5Checksum.java @@ -0,0 +1,84 @@ +/* + * 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.http.auth.aws.internal.signer.checksums; + +import java.security.MessageDigest; +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * Implementation of {@link SdkChecksum} to calculate an MD5 checksum. + */ +@SdkInternalApi +public class Md5Checksum implements SdkChecksum { + + private MessageDigest digest; + + private MessageDigest digestLastMarked; + + public Md5Checksum() { + this.digest = getDigest(); + } + + @Override + public void update(int b) { + digest.update((byte) b); + } + + @Override + public void update(byte[] b, int off, int len) { + digest.update(b, off, len); + } + + @Override + public long getValue() { + throw new UnsupportedOperationException("Use getChecksumBytes() instead."); + } + + @Override + public void reset() { + digest = (digestLastMarked == null) + // This is necessary so that should there be a reset without a + // preceding mark, the MD5 would still be computed correctly. + ? getDigest() + : cloneFrom(digestLastMarked); + } + + private MessageDigest getDigest() { + try { + return MessageDigest.getInstance("MD5"); + } catch (Exception e) { + throw new IllegalStateException("Unexpected error creating MD5 checksum", e); + } + } + + @Override + public byte[] getChecksumBytes() { + return digest.digest(); + } + + @Override + public void mark(int readLimit) { + digestLastMarked = cloneFrom(digest); + } + + private MessageDigest cloneFrom(MessageDigest from) { + try { + return (MessageDigest) from.clone(); + } catch (CloneNotSupportedException e) { // should never occur + throw new IllegalStateException("unexpected", e); + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/SdkChecksum.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/SdkChecksum.java new file mode 100644 index 000000000000..ab7b5a388e4d --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/SdkChecksum.java @@ -0,0 +1,98 @@ +/* + * 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.http.auth.aws.internal.signer.checksums; + +import java.nio.ByteBuffer; +import java.util.zip.Checksum; +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * Extension of {@link Checksum} to support checksums and checksum validations used by the SDK that are not provided by the JDK. + */ +@SdkInternalApi +public interface SdkChecksum extends Checksum { + + /** + * Returns the computed checksum in a byte array rather than the long provided by {@link #getValue()}. + * + * @return byte[] containing the checksum + */ + byte[] getChecksumBytes(); + + /** + * Allows marking a checksum for checksums that support the ability to mark and reset. + * + * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid. + */ + void mark(int readLimit); + + /** + * Updates the current checksum with the specified array of bytes. + * + * @param b the array of bytes to update the checksum with + * @throws NullPointerException if {@code b} is {@code null} + */ + default void update(byte[] b) { + update(b, 0, b.length); + } + + + /** + * Updates the current checksum with the bytes from the specified buffer. + *

+ * The checksum is updated with the remaining bytes in the buffer, starting at the buffer's position. Upon return, the + * buffer's position will be updated to its limit; its limit will not have been changed. + * + * @param buffer the ByteBuffer to update the checksum with + * @throws NullPointerException if {@code buffer} is {@code null} + * @apiNote For best performance with DirectByteBuffer and other ByteBuffer implementations without a backing array + * implementers of this interface should override this method. + * @implSpec The default implementation has the following behavior.
For ByteBuffers backed by an accessible byte array. + *

{@code
+     * update(buffer.array(),
+     *        buffer.position() + buffer.arrayOffset(),
+     *        buffer.remaining());
+     * }
+ * For ByteBuffers not backed by an accessible byte array. + *
{@code
+     * byte[] b = new byte[Math.min(buffer.remaining(), 4096)];
+     * while (buffer.hasRemaining()) {
+     *     int length = Math.min(buffer.remaining(), b.length);
+     *     buffer.get(b, 0, length);
+     *     update(b, 0, length);
+     * }
+     * }
+ */ + default void update(ByteBuffer buffer) { + int pos = buffer.position(); + int limit = buffer.limit(); + int rem = limit - pos; + if (rem <= 0) { + return; + } + if (buffer.hasArray()) { + update(buffer.array(), pos + buffer.arrayOffset(), rem); + } else { + byte[] b = new byte[Math.min(buffer.remaining(), 4096)]; + while (buffer.hasRemaining()) { + int length = Math.min(buffer.remaining(), b.length); + buffer.get(b, 0, length); + update(b, 0, length); + } + } + buffer.position(limit); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/SdkCrc32CChecksum.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/SdkCrc32CChecksum.java new file mode 100644 index 000000000000..6c1d07175d41 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/SdkCrc32CChecksum.java @@ -0,0 +1,639 @@ +/* + * 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.http.auth.aws.internal.signer.checksums; + +import java.util.zip.Checksum; +import software.amazon.awssdk.annotations.SdkInternalApi; + + +/* + * THIS FILE HAS BEEN MODIFIED FROM THE ORIGINAL VERSION. + * + * The code comes from PureJavaCrc32C.java in Apache Commons Codec 1.11. + * It has been modified to add a createCopy() method. + * The createCopy method is used to save current checksum state when the checksum is marked. + */ +@SdkInternalApi +public final class SdkCrc32CChecksum implements Checksum, Cloneable { + + private static final int T8_0_START = 0; + private static final int T8_1_START = 256; + private static final int T8_2_START = 2 * 256; + private static final int T8_3_START = 3 * 256; + private static final int T_8_4_START = 4 * 256; + private static final int T8_5_START = 5 * 256; + private static final int T8_6_START = 6 * 256; + private static final int T8_7_START = 7 * 256; + + // CRC polynomial tables generated by: + // java -cp build/test/classes/:build/classes/ \ + // org.apache.hadoop.util.TestPureJavaCrc32\$Table 82F63B78 + private static final int[] T = { + /* T8_0 */ + 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, + 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, + 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, + 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, + 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, + 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, + 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, + 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, + 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, + 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, + 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, + 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, + 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, + 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, + 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, + 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, + 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, + 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, + 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, + 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, + 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, + 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, + 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, + 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, + 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, + 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, + 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, + 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, + 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, + 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, + 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, + 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, + 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, + 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, + 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, + 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, + 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, + 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, + 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, + 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, + 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, + 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, + 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, + 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, + 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, + 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, + 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, + 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, + 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, + 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, + 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, + 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, + 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, + 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, + 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, + 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, + 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, + 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, + 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, + 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, + 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351, + /* T8_1 */ + 0x00000000, 0x13A29877, 0x274530EE, 0x34E7A899, + 0x4E8A61DC, 0x5D28F9AB, 0x69CF5132, 0x7A6DC945, + 0x9D14C3B8, 0x8EB65BCF, 0xBA51F356, 0xA9F36B21, + 0xD39EA264, 0xC03C3A13, 0xF4DB928A, 0xE7790AFD, + 0x3FC5F181, 0x2C6769F6, 0x1880C16F, 0x0B225918, + 0x714F905D, 0x62ED082A, 0x560AA0B3, 0x45A838C4, + 0xA2D13239, 0xB173AA4E, 0x859402D7, 0x96369AA0, + 0xEC5B53E5, 0xFFF9CB92, 0xCB1E630B, 0xD8BCFB7C, + 0x7F8BE302, 0x6C297B75, 0x58CED3EC, 0x4B6C4B9B, + 0x310182DE, 0x22A31AA9, 0x1644B230, 0x05E62A47, + 0xE29F20BA, 0xF13DB8CD, 0xC5DA1054, 0xD6788823, + 0xAC154166, 0xBFB7D911, 0x8B507188, 0x98F2E9FF, + 0x404E1283, 0x53EC8AF4, 0x670B226D, 0x74A9BA1A, + 0x0EC4735F, 0x1D66EB28, 0x298143B1, 0x3A23DBC6, + 0xDD5AD13B, 0xCEF8494C, 0xFA1FE1D5, 0xE9BD79A2, + 0x93D0B0E7, 0x80722890, 0xB4958009, 0xA737187E, + 0xFF17C604, 0xECB55E73, 0xD852F6EA, 0xCBF06E9D, + 0xB19DA7D8, 0xA23F3FAF, 0x96D89736, 0x857A0F41, + 0x620305BC, 0x71A19DCB, 0x45463552, 0x56E4AD25, + 0x2C896460, 0x3F2BFC17, 0x0BCC548E, 0x186ECCF9, + 0xC0D23785, 0xD370AFF2, 0xE797076B, 0xF4359F1C, + 0x8E585659, 0x9DFACE2E, 0xA91D66B7, 0xBABFFEC0, + 0x5DC6F43D, 0x4E646C4A, 0x7A83C4D3, 0x69215CA4, + 0x134C95E1, 0x00EE0D96, 0x3409A50F, 0x27AB3D78, + 0x809C2506, 0x933EBD71, 0xA7D915E8, 0xB47B8D9F, + 0xCE1644DA, 0xDDB4DCAD, 0xE9537434, 0xFAF1EC43, + 0x1D88E6BE, 0x0E2A7EC9, 0x3ACDD650, 0x296F4E27, + 0x53028762, 0x40A01F15, 0x7447B78C, 0x67E52FFB, + 0xBF59D487, 0xACFB4CF0, 0x981CE469, 0x8BBE7C1E, + 0xF1D3B55B, 0xE2712D2C, 0xD69685B5, 0xC5341DC2, + 0x224D173F, 0x31EF8F48, 0x050827D1, 0x16AABFA6, + 0x6CC776E3, 0x7F65EE94, 0x4B82460D, 0x5820DE7A, + 0xFBC3FAF9, 0xE861628E, 0xDC86CA17, 0xCF245260, + 0xB5499B25, 0xA6EB0352, 0x920CABCB, 0x81AE33BC, + 0x66D73941, 0x7575A136, 0x419209AF, 0x523091D8, + 0x285D589D, 0x3BFFC0EA, 0x0F186873, 0x1CBAF004, + 0xC4060B78, 0xD7A4930F, 0xE3433B96, 0xF0E1A3E1, + 0x8A8C6AA4, 0x992EF2D3, 0xADC95A4A, 0xBE6BC23D, + 0x5912C8C0, 0x4AB050B7, 0x7E57F82E, 0x6DF56059, + 0x1798A91C, 0x043A316B, 0x30DD99F2, 0x237F0185, + 0x844819FB, 0x97EA818C, 0xA30D2915, 0xB0AFB162, + 0xCAC27827, 0xD960E050, 0xED8748C9, 0xFE25D0BE, + 0x195CDA43, 0x0AFE4234, 0x3E19EAAD, 0x2DBB72DA, + 0x57D6BB9F, 0x447423E8, 0x70938B71, 0x63311306, + 0xBB8DE87A, 0xA82F700D, 0x9CC8D894, 0x8F6A40E3, + 0xF50789A6, 0xE6A511D1, 0xD242B948, 0xC1E0213F, + 0x26992BC2, 0x353BB3B5, 0x01DC1B2C, 0x127E835B, + 0x68134A1E, 0x7BB1D269, 0x4F567AF0, 0x5CF4E287, + 0x04D43CFD, 0x1776A48A, 0x23910C13, 0x30339464, + 0x4A5E5D21, 0x59FCC556, 0x6D1B6DCF, 0x7EB9F5B8, + 0x99C0FF45, 0x8A626732, 0xBE85CFAB, 0xAD2757DC, + 0xD74A9E99, 0xC4E806EE, 0xF00FAE77, 0xE3AD3600, + 0x3B11CD7C, 0x28B3550B, 0x1C54FD92, 0x0FF665E5, + 0x759BACA0, 0x663934D7, 0x52DE9C4E, 0x417C0439, + 0xA6050EC4, 0xB5A796B3, 0x81403E2A, 0x92E2A65D, + 0xE88F6F18, 0xFB2DF76F, 0xCFCA5FF6, 0xDC68C781, + 0x7B5FDFFF, 0x68FD4788, 0x5C1AEF11, 0x4FB87766, + 0x35D5BE23, 0x26772654, 0x12908ECD, 0x013216BA, + 0xE64B1C47, 0xF5E98430, 0xC10E2CA9, 0xD2ACB4DE, + 0xA8C17D9B, 0xBB63E5EC, 0x8F844D75, 0x9C26D502, + 0x449A2E7E, 0x5738B609, 0x63DF1E90, 0x707D86E7, + 0x0A104FA2, 0x19B2D7D5, 0x2D557F4C, 0x3EF7E73B, + 0xD98EEDC6, 0xCA2C75B1, 0xFECBDD28, 0xED69455F, + 0x97048C1A, 0x84A6146D, 0xB041BCF4, 0xA3E32483, + /* T8_2 */ + 0x00000000, 0xA541927E, 0x4F6F520D, 0xEA2EC073, + 0x9EDEA41A, 0x3B9F3664, 0xD1B1F617, 0x74F06469, + 0x38513EC5, 0x9D10ACBB, 0x773E6CC8, 0xD27FFEB6, + 0xA68F9ADF, 0x03CE08A1, 0xE9E0C8D2, 0x4CA15AAC, + 0x70A27D8A, 0xD5E3EFF4, 0x3FCD2F87, 0x9A8CBDF9, + 0xEE7CD990, 0x4B3D4BEE, 0xA1138B9D, 0x045219E3, + 0x48F3434F, 0xEDB2D131, 0x079C1142, 0xA2DD833C, + 0xD62DE755, 0x736C752B, 0x9942B558, 0x3C032726, + 0xE144FB14, 0x4405696A, 0xAE2BA919, 0x0B6A3B67, + 0x7F9A5F0E, 0xDADBCD70, 0x30F50D03, 0x95B49F7D, + 0xD915C5D1, 0x7C5457AF, 0x967A97DC, 0x333B05A2, + 0x47CB61CB, 0xE28AF3B5, 0x08A433C6, 0xADE5A1B8, + 0x91E6869E, 0x34A714E0, 0xDE89D493, 0x7BC846ED, + 0x0F382284, 0xAA79B0FA, 0x40577089, 0xE516E2F7, + 0xA9B7B85B, 0x0CF62A25, 0xE6D8EA56, 0x43997828, + 0x37691C41, 0x92288E3F, 0x78064E4C, 0xDD47DC32, + 0xC76580D9, 0x622412A7, 0x880AD2D4, 0x2D4B40AA, + 0x59BB24C3, 0xFCFAB6BD, 0x16D476CE, 0xB395E4B0, + 0xFF34BE1C, 0x5A752C62, 0xB05BEC11, 0x151A7E6F, + 0x61EA1A06, 0xC4AB8878, 0x2E85480B, 0x8BC4DA75, + 0xB7C7FD53, 0x12866F2D, 0xF8A8AF5E, 0x5DE93D20, + 0x29195949, 0x8C58CB37, 0x66760B44, 0xC337993A, + 0x8F96C396, 0x2AD751E8, 0xC0F9919B, 0x65B803E5, + 0x1148678C, 0xB409F5F2, 0x5E273581, 0xFB66A7FF, + 0x26217BCD, 0x8360E9B3, 0x694E29C0, 0xCC0FBBBE, + 0xB8FFDFD7, 0x1DBE4DA9, 0xF7908DDA, 0x52D11FA4, + 0x1E704508, 0xBB31D776, 0x511F1705, 0xF45E857B, + 0x80AEE112, 0x25EF736C, 0xCFC1B31F, 0x6A802161, + 0x56830647, 0xF3C29439, 0x19EC544A, 0xBCADC634, + 0xC85DA25D, 0x6D1C3023, 0x8732F050, 0x2273622E, + 0x6ED23882, 0xCB93AAFC, 0x21BD6A8F, 0x84FCF8F1, + 0xF00C9C98, 0x554D0EE6, 0xBF63CE95, 0x1A225CEB, + 0x8B277743, 0x2E66E53D, 0xC448254E, 0x6109B730, + 0x15F9D359, 0xB0B84127, 0x5A968154, 0xFFD7132A, + 0xB3764986, 0x1637DBF8, 0xFC191B8B, 0x595889F5, + 0x2DA8ED9C, 0x88E97FE2, 0x62C7BF91, 0xC7862DEF, + 0xFB850AC9, 0x5EC498B7, 0xB4EA58C4, 0x11ABCABA, + 0x655BAED3, 0xC01A3CAD, 0x2A34FCDE, 0x8F756EA0, + 0xC3D4340C, 0x6695A672, 0x8CBB6601, 0x29FAF47F, + 0x5D0A9016, 0xF84B0268, 0x1265C21B, 0xB7245065, + 0x6A638C57, 0xCF221E29, 0x250CDE5A, 0x804D4C24, + 0xF4BD284D, 0x51FCBA33, 0xBBD27A40, 0x1E93E83E, + 0x5232B292, 0xF77320EC, 0x1D5DE09F, 0xB81C72E1, + 0xCCEC1688, 0x69AD84F6, 0x83834485, 0x26C2D6FB, + 0x1AC1F1DD, 0xBF8063A3, 0x55AEA3D0, 0xF0EF31AE, + 0x841F55C7, 0x215EC7B9, 0xCB7007CA, 0x6E3195B4, + 0x2290CF18, 0x87D15D66, 0x6DFF9D15, 0xC8BE0F6B, + 0xBC4E6B02, 0x190FF97C, 0xF321390F, 0x5660AB71, + 0x4C42F79A, 0xE90365E4, 0x032DA597, 0xA66C37E9, + 0xD29C5380, 0x77DDC1FE, 0x9DF3018D, 0x38B293F3, + 0x7413C95F, 0xD1525B21, 0x3B7C9B52, 0x9E3D092C, + 0xEACD6D45, 0x4F8CFF3B, 0xA5A23F48, 0x00E3AD36, + 0x3CE08A10, 0x99A1186E, 0x738FD81D, 0xD6CE4A63, + 0xA23E2E0A, 0x077FBC74, 0xED517C07, 0x4810EE79, + 0x04B1B4D5, 0xA1F026AB, 0x4BDEE6D8, 0xEE9F74A6, + 0x9A6F10CF, 0x3F2E82B1, 0xD50042C2, 0x7041D0BC, + 0xAD060C8E, 0x08479EF0, 0xE2695E83, 0x4728CCFD, + 0x33D8A894, 0x96993AEA, 0x7CB7FA99, 0xD9F668E7, + 0x9557324B, 0x3016A035, 0xDA386046, 0x7F79F238, + 0x0B899651, 0xAEC8042F, 0x44E6C45C, 0xE1A75622, + 0xDDA47104, 0x78E5E37A, 0x92CB2309, 0x378AB177, + 0x437AD51E, 0xE63B4760, 0x0C158713, 0xA954156D, + 0xE5F54FC1, 0x40B4DDBF, 0xAA9A1DCC, 0x0FDB8FB2, + 0x7B2BEBDB, 0xDE6A79A5, 0x3444B9D6, 0x91052BA8, + /* T8_3 */ + 0x00000000, 0xDD45AAB8, 0xBF672381, 0x62228939, + 0x7B2231F3, 0xA6679B4B, 0xC4451272, 0x1900B8CA, + 0xF64463E6, 0x2B01C95E, 0x49234067, 0x9466EADF, + 0x8D665215, 0x5023F8AD, 0x32017194, 0xEF44DB2C, + 0xE964B13D, 0x34211B85, 0x560392BC, 0x8B463804, + 0x924680CE, 0x4F032A76, 0x2D21A34F, 0xF06409F7, + 0x1F20D2DB, 0xC2657863, 0xA047F15A, 0x7D025BE2, + 0x6402E328, 0xB9474990, 0xDB65C0A9, 0x06206A11, + 0xD725148B, 0x0A60BE33, 0x6842370A, 0xB5079DB2, + 0xAC072578, 0x71428FC0, 0x136006F9, 0xCE25AC41, + 0x2161776D, 0xFC24DDD5, 0x9E0654EC, 0x4343FE54, + 0x5A43469E, 0x8706EC26, 0xE524651F, 0x3861CFA7, + 0x3E41A5B6, 0xE3040F0E, 0x81268637, 0x5C632C8F, + 0x45639445, 0x98263EFD, 0xFA04B7C4, 0x27411D7C, + 0xC805C650, 0x15406CE8, 0x7762E5D1, 0xAA274F69, + 0xB327F7A3, 0x6E625D1B, 0x0C40D422, 0xD1057E9A, + 0xABA65FE7, 0x76E3F55F, 0x14C17C66, 0xC984D6DE, + 0xD0846E14, 0x0DC1C4AC, 0x6FE34D95, 0xB2A6E72D, + 0x5DE23C01, 0x80A796B9, 0xE2851F80, 0x3FC0B538, + 0x26C00DF2, 0xFB85A74A, 0x99A72E73, 0x44E284CB, + 0x42C2EEDA, 0x9F874462, 0xFDA5CD5B, 0x20E067E3, + 0x39E0DF29, 0xE4A57591, 0x8687FCA8, 0x5BC25610, + 0xB4868D3C, 0x69C32784, 0x0BE1AEBD, 0xD6A40405, + 0xCFA4BCCF, 0x12E11677, 0x70C39F4E, 0xAD8635F6, + 0x7C834B6C, 0xA1C6E1D4, 0xC3E468ED, 0x1EA1C255, + 0x07A17A9F, 0xDAE4D027, 0xB8C6591E, 0x6583F3A6, + 0x8AC7288A, 0x57828232, 0x35A00B0B, 0xE8E5A1B3, + 0xF1E51979, 0x2CA0B3C1, 0x4E823AF8, 0x93C79040, + 0x95E7FA51, 0x48A250E9, 0x2A80D9D0, 0xF7C57368, + 0xEEC5CBA2, 0x3380611A, 0x51A2E823, 0x8CE7429B, + 0x63A399B7, 0xBEE6330F, 0xDCC4BA36, 0x0181108E, + 0x1881A844, 0xC5C402FC, 0xA7E68BC5, 0x7AA3217D, + 0x52A0C93F, 0x8FE56387, 0xEDC7EABE, 0x30824006, + 0x2982F8CC, 0xF4C75274, 0x96E5DB4D, 0x4BA071F5, + 0xA4E4AAD9, 0x79A10061, 0x1B838958, 0xC6C623E0, + 0xDFC69B2A, 0x02833192, 0x60A1B8AB, 0xBDE41213, + 0xBBC47802, 0x6681D2BA, 0x04A35B83, 0xD9E6F13B, + 0xC0E649F1, 0x1DA3E349, 0x7F816A70, 0xA2C4C0C8, + 0x4D801BE4, 0x90C5B15C, 0xF2E73865, 0x2FA292DD, + 0x36A22A17, 0xEBE780AF, 0x89C50996, 0x5480A32E, + 0x8585DDB4, 0x58C0770C, 0x3AE2FE35, 0xE7A7548D, + 0xFEA7EC47, 0x23E246FF, 0x41C0CFC6, 0x9C85657E, + 0x73C1BE52, 0xAE8414EA, 0xCCA69DD3, 0x11E3376B, + 0x08E38FA1, 0xD5A62519, 0xB784AC20, 0x6AC10698, + 0x6CE16C89, 0xB1A4C631, 0xD3864F08, 0x0EC3E5B0, + 0x17C35D7A, 0xCA86F7C2, 0xA8A47EFB, 0x75E1D443, + 0x9AA50F6F, 0x47E0A5D7, 0x25C22CEE, 0xF8878656, + 0xE1873E9C, 0x3CC29424, 0x5EE01D1D, 0x83A5B7A5, + 0xF90696D8, 0x24433C60, 0x4661B559, 0x9B241FE1, + 0x8224A72B, 0x5F610D93, 0x3D4384AA, 0xE0062E12, + 0x0F42F53E, 0xD2075F86, 0xB025D6BF, 0x6D607C07, + 0x7460C4CD, 0xA9256E75, 0xCB07E74C, 0x16424DF4, + 0x106227E5, 0xCD278D5D, 0xAF050464, 0x7240AEDC, + 0x6B401616, 0xB605BCAE, 0xD4273597, 0x09629F2F, + 0xE6264403, 0x3B63EEBB, 0x59416782, 0x8404CD3A, + 0x9D0475F0, 0x4041DF48, 0x22635671, 0xFF26FCC9, + 0x2E238253, 0xF36628EB, 0x9144A1D2, 0x4C010B6A, + 0x5501B3A0, 0x88441918, 0xEA669021, 0x37233A99, + 0xD867E1B5, 0x05224B0D, 0x6700C234, 0xBA45688C, + 0xA345D046, 0x7E007AFE, 0x1C22F3C7, 0xC167597F, + 0xC747336E, 0x1A0299D6, 0x782010EF, 0xA565BA57, + 0xBC65029D, 0x6120A825, 0x0302211C, 0xDE478BA4, + 0x31035088, 0xEC46FA30, 0x8E647309, 0x5321D9B1, + 0x4A21617B, 0x9764CBC3, 0xF54642FA, 0x2803E842, + /* T8_4 */ + 0x00000000, 0x38116FAC, 0x7022DF58, 0x4833B0F4, + 0xE045BEB0, 0xD854D11C, 0x906761E8, 0xA8760E44, + 0xC5670B91, 0xFD76643D, 0xB545D4C9, 0x8D54BB65, + 0x2522B521, 0x1D33DA8D, 0x55006A79, 0x6D1105D5, + 0x8F2261D3, 0xB7330E7F, 0xFF00BE8B, 0xC711D127, + 0x6F67DF63, 0x5776B0CF, 0x1F45003B, 0x27546F97, + 0x4A456A42, 0x725405EE, 0x3A67B51A, 0x0276DAB6, + 0xAA00D4F2, 0x9211BB5E, 0xDA220BAA, 0xE2336406, + 0x1BA8B557, 0x23B9DAFB, 0x6B8A6A0F, 0x539B05A3, + 0xFBED0BE7, 0xC3FC644B, 0x8BCFD4BF, 0xB3DEBB13, + 0xDECFBEC6, 0xE6DED16A, 0xAEED619E, 0x96FC0E32, + 0x3E8A0076, 0x069B6FDA, 0x4EA8DF2E, 0x76B9B082, + 0x948AD484, 0xAC9BBB28, 0xE4A80BDC, 0xDCB96470, + 0x74CF6A34, 0x4CDE0598, 0x04EDB56C, 0x3CFCDAC0, + 0x51EDDF15, 0x69FCB0B9, 0x21CF004D, 0x19DE6FE1, + 0xB1A861A5, 0x89B90E09, 0xC18ABEFD, 0xF99BD151, + 0x37516AAE, 0x0F400502, 0x4773B5F6, 0x7F62DA5A, + 0xD714D41E, 0xEF05BBB2, 0xA7360B46, 0x9F2764EA, + 0xF236613F, 0xCA270E93, 0x8214BE67, 0xBA05D1CB, + 0x1273DF8F, 0x2A62B023, 0x625100D7, 0x5A406F7B, + 0xB8730B7D, 0x806264D1, 0xC851D425, 0xF040BB89, + 0x5836B5CD, 0x6027DA61, 0x28146A95, 0x10050539, + 0x7D1400EC, 0x45056F40, 0x0D36DFB4, 0x3527B018, + 0x9D51BE5C, 0xA540D1F0, 0xED736104, 0xD5620EA8, + 0x2CF9DFF9, 0x14E8B055, 0x5CDB00A1, 0x64CA6F0D, + 0xCCBC6149, 0xF4AD0EE5, 0xBC9EBE11, 0x848FD1BD, + 0xE99ED468, 0xD18FBBC4, 0x99BC0B30, 0xA1AD649C, + 0x09DB6AD8, 0x31CA0574, 0x79F9B580, 0x41E8DA2C, + 0xA3DBBE2A, 0x9BCAD186, 0xD3F96172, 0xEBE80EDE, + 0x439E009A, 0x7B8F6F36, 0x33BCDFC2, 0x0BADB06E, + 0x66BCB5BB, 0x5EADDA17, 0x169E6AE3, 0x2E8F054F, + 0x86F90B0B, 0xBEE864A7, 0xF6DBD453, 0xCECABBFF, + 0x6EA2D55C, 0x56B3BAF0, 0x1E800A04, 0x269165A8, + 0x8EE76BEC, 0xB6F60440, 0xFEC5B4B4, 0xC6D4DB18, + 0xABC5DECD, 0x93D4B161, 0xDBE70195, 0xE3F66E39, + 0x4B80607D, 0x73910FD1, 0x3BA2BF25, 0x03B3D089, + 0xE180B48F, 0xD991DB23, 0x91A26BD7, 0xA9B3047B, + 0x01C50A3F, 0x39D46593, 0x71E7D567, 0x49F6BACB, + 0x24E7BF1E, 0x1CF6D0B2, 0x54C56046, 0x6CD40FEA, + 0xC4A201AE, 0xFCB36E02, 0xB480DEF6, 0x8C91B15A, + 0x750A600B, 0x4D1B0FA7, 0x0528BF53, 0x3D39D0FF, + 0x954FDEBB, 0xAD5EB117, 0xE56D01E3, 0xDD7C6E4F, + 0xB06D6B9A, 0x887C0436, 0xC04FB4C2, 0xF85EDB6E, + 0x5028D52A, 0x6839BA86, 0x200A0A72, 0x181B65DE, + 0xFA2801D8, 0xC2396E74, 0x8A0ADE80, 0xB21BB12C, + 0x1A6DBF68, 0x227CD0C4, 0x6A4F6030, 0x525E0F9C, + 0x3F4F0A49, 0x075E65E5, 0x4F6DD511, 0x777CBABD, + 0xDF0AB4F9, 0xE71BDB55, 0xAF286BA1, 0x9739040D, + 0x59F3BFF2, 0x61E2D05E, 0x29D160AA, 0x11C00F06, + 0xB9B60142, 0x81A76EEE, 0xC994DE1A, 0xF185B1B6, + 0x9C94B463, 0xA485DBCF, 0xECB66B3B, 0xD4A70497, + 0x7CD10AD3, 0x44C0657F, 0x0CF3D58B, 0x34E2BA27, + 0xD6D1DE21, 0xEEC0B18D, 0xA6F30179, 0x9EE26ED5, + 0x36946091, 0x0E850F3D, 0x46B6BFC9, 0x7EA7D065, + 0x13B6D5B0, 0x2BA7BA1C, 0x63940AE8, 0x5B856544, + 0xF3F36B00, 0xCBE204AC, 0x83D1B458, 0xBBC0DBF4, + 0x425B0AA5, 0x7A4A6509, 0x3279D5FD, 0x0A68BA51, + 0xA21EB415, 0x9A0FDBB9, 0xD23C6B4D, 0xEA2D04E1, + 0x873C0134, 0xBF2D6E98, 0xF71EDE6C, 0xCF0FB1C0, + 0x6779BF84, 0x5F68D028, 0x175B60DC, 0x2F4A0F70, + 0xCD796B76, 0xF56804DA, 0xBD5BB42E, 0x854ADB82, + 0x2D3CD5C6, 0x152DBA6A, 0x5D1E0A9E, 0x650F6532, + 0x081E60E7, 0x300F0F4B, 0x783CBFBF, 0x402DD013, + 0xE85BDE57, 0xD04AB1FB, 0x9879010F, 0xA0686EA3, + /* T8_5 */ + 0x00000000, 0xEF306B19, 0xDB8CA0C3, 0x34BCCBDA, + 0xB2F53777, 0x5DC55C6E, 0x697997B4, 0x8649FCAD, + 0x6006181F, 0x8F367306, 0xBB8AB8DC, 0x54BAD3C5, + 0xD2F32F68, 0x3DC34471, 0x097F8FAB, 0xE64FE4B2, + 0xC00C303E, 0x2F3C5B27, 0x1B8090FD, 0xF4B0FBE4, + 0x72F90749, 0x9DC96C50, 0xA975A78A, 0x4645CC93, + 0xA00A2821, 0x4F3A4338, 0x7B8688E2, 0x94B6E3FB, + 0x12FF1F56, 0xFDCF744F, 0xC973BF95, 0x2643D48C, + 0x85F4168D, 0x6AC47D94, 0x5E78B64E, 0xB148DD57, + 0x370121FA, 0xD8314AE3, 0xEC8D8139, 0x03BDEA20, + 0xE5F20E92, 0x0AC2658B, 0x3E7EAE51, 0xD14EC548, + 0x570739E5, 0xB83752FC, 0x8C8B9926, 0x63BBF23F, + 0x45F826B3, 0xAAC84DAA, 0x9E748670, 0x7144ED69, + 0xF70D11C4, 0x183D7ADD, 0x2C81B107, 0xC3B1DA1E, + 0x25FE3EAC, 0xCACE55B5, 0xFE729E6F, 0x1142F576, + 0x970B09DB, 0x783B62C2, 0x4C87A918, 0xA3B7C201, + 0x0E045BEB, 0xE13430F2, 0xD588FB28, 0x3AB89031, + 0xBCF16C9C, 0x53C10785, 0x677DCC5F, 0x884DA746, + 0x6E0243F4, 0x813228ED, 0xB58EE337, 0x5ABE882E, + 0xDCF77483, 0x33C71F9A, 0x077BD440, 0xE84BBF59, + 0xCE086BD5, 0x213800CC, 0x1584CB16, 0xFAB4A00F, + 0x7CFD5CA2, 0x93CD37BB, 0xA771FC61, 0x48419778, + 0xAE0E73CA, 0x413E18D3, 0x7582D309, 0x9AB2B810, + 0x1CFB44BD, 0xF3CB2FA4, 0xC777E47E, 0x28478F67, + 0x8BF04D66, 0x64C0267F, 0x507CEDA5, 0xBF4C86BC, + 0x39057A11, 0xD6351108, 0xE289DAD2, 0x0DB9B1CB, + 0xEBF65579, 0x04C63E60, 0x307AF5BA, 0xDF4A9EA3, + 0x5903620E, 0xB6330917, 0x828FC2CD, 0x6DBFA9D4, + 0x4BFC7D58, 0xA4CC1641, 0x9070DD9B, 0x7F40B682, + 0xF9094A2F, 0x16392136, 0x2285EAEC, 0xCDB581F5, + 0x2BFA6547, 0xC4CA0E5E, 0xF076C584, 0x1F46AE9D, + 0x990F5230, 0x763F3929, 0x4283F2F3, 0xADB399EA, + 0x1C08B7D6, 0xF338DCCF, 0xC7841715, 0x28B47C0C, + 0xAEFD80A1, 0x41CDEBB8, 0x75712062, 0x9A414B7B, + 0x7C0EAFC9, 0x933EC4D0, 0xA7820F0A, 0x48B26413, + 0xCEFB98BE, 0x21CBF3A7, 0x1577387D, 0xFA475364, + 0xDC0487E8, 0x3334ECF1, 0x0788272B, 0xE8B84C32, + 0x6EF1B09F, 0x81C1DB86, 0xB57D105C, 0x5A4D7B45, + 0xBC029FF7, 0x5332F4EE, 0x678E3F34, 0x88BE542D, + 0x0EF7A880, 0xE1C7C399, 0xD57B0843, 0x3A4B635A, + 0x99FCA15B, 0x76CCCA42, 0x42700198, 0xAD406A81, + 0x2B09962C, 0xC439FD35, 0xF08536EF, 0x1FB55DF6, + 0xF9FAB944, 0x16CAD25D, 0x22761987, 0xCD46729E, + 0x4B0F8E33, 0xA43FE52A, 0x90832EF0, 0x7FB345E9, + 0x59F09165, 0xB6C0FA7C, 0x827C31A6, 0x6D4C5ABF, + 0xEB05A612, 0x0435CD0B, 0x308906D1, 0xDFB96DC8, + 0x39F6897A, 0xD6C6E263, 0xE27A29B9, 0x0D4A42A0, + 0x8B03BE0D, 0x6433D514, 0x508F1ECE, 0xBFBF75D7, + 0x120CEC3D, 0xFD3C8724, 0xC9804CFE, 0x26B027E7, + 0xA0F9DB4A, 0x4FC9B053, 0x7B757B89, 0x94451090, + 0x720AF422, 0x9D3A9F3B, 0xA98654E1, 0x46B63FF8, + 0xC0FFC355, 0x2FCFA84C, 0x1B736396, 0xF443088F, + 0xD200DC03, 0x3D30B71A, 0x098C7CC0, 0xE6BC17D9, + 0x60F5EB74, 0x8FC5806D, 0xBB794BB7, 0x544920AE, + 0xB206C41C, 0x5D36AF05, 0x698A64DF, 0x86BA0FC6, + 0x00F3F36B, 0xEFC39872, 0xDB7F53A8, 0x344F38B1, + 0x97F8FAB0, 0x78C891A9, 0x4C745A73, 0xA344316A, + 0x250DCDC7, 0xCA3DA6DE, 0xFE816D04, 0x11B1061D, + 0xF7FEE2AF, 0x18CE89B6, 0x2C72426C, 0xC3422975, + 0x450BD5D8, 0xAA3BBEC1, 0x9E87751B, 0x71B71E02, + 0x57F4CA8E, 0xB8C4A197, 0x8C786A4D, 0x63480154, + 0xE501FDF9, 0x0A3196E0, 0x3E8D5D3A, 0xD1BD3623, + 0x37F2D291, 0xD8C2B988, 0xEC7E7252, 0x034E194B, + 0x8507E5E6, 0x6A378EFF, 0x5E8B4525, 0xB1BB2E3C, + /* T8_6 */ + 0x00000000, 0x68032CC8, 0xD0065990, 0xB8057558, + 0xA5E0C5D1, 0xCDE3E919, 0x75E69C41, 0x1DE5B089, + 0x4E2DFD53, 0x262ED19B, 0x9E2BA4C3, 0xF628880B, + 0xEBCD3882, 0x83CE144A, 0x3BCB6112, 0x53C84DDA, + 0x9C5BFAA6, 0xF458D66E, 0x4C5DA336, 0x245E8FFE, + 0x39BB3F77, 0x51B813BF, 0xE9BD66E7, 0x81BE4A2F, + 0xD27607F5, 0xBA752B3D, 0x02705E65, 0x6A7372AD, + 0x7796C224, 0x1F95EEEC, 0xA7909BB4, 0xCF93B77C, + 0x3D5B83BD, 0x5558AF75, 0xED5DDA2D, 0x855EF6E5, + 0x98BB466C, 0xF0B86AA4, 0x48BD1FFC, 0x20BE3334, + 0x73767EEE, 0x1B755226, 0xA370277E, 0xCB730BB6, + 0xD696BB3F, 0xBE9597F7, 0x0690E2AF, 0x6E93CE67, + 0xA100791B, 0xC90355D3, 0x7106208B, 0x19050C43, + 0x04E0BCCA, 0x6CE39002, 0xD4E6E55A, 0xBCE5C992, + 0xEF2D8448, 0x872EA880, 0x3F2BDDD8, 0x5728F110, + 0x4ACD4199, 0x22CE6D51, 0x9ACB1809, 0xF2C834C1, + 0x7AB7077A, 0x12B42BB2, 0xAAB15EEA, 0xC2B27222, + 0xDF57C2AB, 0xB754EE63, 0x0F519B3B, 0x6752B7F3, + 0x349AFA29, 0x5C99D6E1, 0xE49CA3B9, 0x8C9F8F71, + 0x917A3FF8, 0xF9791330, 0x417C6668, 0x297F4AA0, + 0xE6ECFDDC, 0x8EEFD114, 0x36EAA44C, 0x5EE98884, + 0x430C380D, 0x2B0F14C5, 0x930A619D, 0xFB094D55, + 0xA8C1008F, 0xC0C22C47, 0x78C7591F, 0x10C475D7, + 0x0D21C55E, 0x6522E996, 0xDD279CCE, 0xB524B006, + 0x47EC84C7, 0x2FEFA80F, 0x97EADD57, 0xFFE9F19F, + 0xE20C4116, 0x8A0F6DDE, 0x320A1886, 0x5A09344E, + 0x09C17994, 0x61C2555C, 0xD9C72004, 0xB1C40CCC, + 0xAC21BC45, 0xC422908D, 0x7C27E5D5, 0x1424C91D, + 0xDBB77E61, 0xB3B452A9, 0x0BB127F1, 0x63B20B39, + 0x7E57BBB0, 0x16549778, 0xAE51E220, 0xC652CEE8, + 0x959A8332, 0xFD99AFFA, 0x459CDAA2, 0x2D9FF66A, + 0x307A46E3, 0x58796A2B, 0xE07C1F73, 0x887F33BB, + 0xF56E0EF4, 0x9D6D223C, 0x25685764, 0x4D6B7BAC, + 0x508ECB25, 0x388DE7ED, 0x808892B5, 0xE88BBE7D, + 0xBB43F3A7, 0xD340DF6F, 0x6B45AA37, 0x034686FF, + 0x1EA33676, 0x76A01ABE, 0xCEA56FE6, 0xA6A6432E, + 0x6935F452, 0x0136D89A, 0xB933ADC2, 0xD130810A, + 0xCCD53183, 0xA4D61D4B, 0x1CD36813, 0x74D044DB, + 0x27180901, 0x4F1B25C9, 0xF71E5091, 0x9F1D7C59, + 0x82F8CCD0, 0xEAFBE018, 0x52FE9540, 0x3AFDB988, + 0xC8358D49, 0xA036A181, 0x1833D4D9, 0x7030F811, + 0x6DD54898, 0x05D66450, 0xBDD31108, 0xD5D03DC0, + 0x8618701A, 0xEE1B5CD2, 0x561E298A, 0x3E1D0542, + 0x23F8B5CB, 0x4BFB9903, 0xF3FEEC5B, 0x9BFDC093, + 0x546E77EF, 0x3C6D5B27, 0x84682E7F, 0xEC6B02B7, + 0xF18EB23E, 0x998D9EF6, 0x2188EBAE, 0x498BC766, + 0x1A438ABC, 0x7240A674, 0xCA45D32C, 0xA246FFE4, + 0xBFA34F6D, 0xD7A063A5, 0x6FA516FD, 0x07A63A35, + 0x8FD9098E, 0xE7DA2546, 0x5FDF501E, 0x37DC7CD6, + 0x2A39CC5F, 0x423AE097, 0xFA3F95CF, 0x923CB907, + 0xC1F4F4DD, 0xA9F7D815, 0x11F2AD4D, 0x79F18185, + 0x6414310C, 0x0C171DC4, 0xB412689C, 0xDC114454, + 0x1382F328, 0x7B81DFE0, 0xC384AAB8, 0xAB878670, + 0xB66236F9, 0xDE611A31, 0x66646F69, 0x0E6743A1, + 0x5DAF0E7B, 0x35AC22B3, 0x8DA957EB, 0xE5AA7B23, + 0xF84FCBAA, 0x904CE762, 0x2849923A, 0x404ABEF2, + 0xB2828A33, 0xDA81A6FB, 0x6284D3A3, 0x0A87FF6B, + 0x17624FE2, 0x7F61632A, 0xC7641672, 0xAF673ABA, + 0xFCAF7760, 0x94AC5BA8, 0x2CA92EF0, 0x44AA0238, + 0x594FB2B1, 0x314C9E79, 0x8949EB21, 0xE14AC7E9, + 0x2ED97095, 0x46DA5C5D, 0xFEDF2905, 0x96DC05CD, + 0x8B39B544, 0xE33A998C, 0x5B3FECD4, 0x333CC01C, + 0x60F48DC6, 0x08F7A10E, 0xB0F2D456, 0xD8F1F89E, + 0xC5144817, 0xAD1764DF, 0x15121187, 0x7D113D4F, + /* T8_7 */ + 0x00000000, 0x493C7D27, 0x9278FA4E, 0xDB448769, + 0x211D826D, 0x6821FF4A, 0xB3657823, 0xFA590504, + 0x423B04DA, 0x0B0779FD, 0xD043FE94, 0x997F83B3, + 0x632686B7, 0x2A1AFB90, 0xF15E7CF9, 0xB86201DE, + 0x847609B4, 0xCD4A7493, 0x160EF3FA, 0x5F328EDD, + 0xA56B8BD9, 0xEC57F6FE, 0x37137197, 0x7E2F0CB0, + 0xC64D0D6E, 0x8F717049, 0x5435F720, 0x1D098A07, + 0xE7508F03, 0xAE6CF224, 0x7528754D, 0x3C14086A, + 0x0D006599, 0x443C18BE, 0x9F789FD7, 0xD644E2F0, + 0x2C1DE7F4, 0x65219AD3, 0xBE651DBA, 0xF759609D, + 0x4F3B6143, 0x06071C64, 0xDD439B0D, 0x947FE62A, + 0x6E26E32E, 0x271A9E09, 0xFC5E1960, 0xB5626447, + 0x89766C2D, 0xC04A110A, 0x1B0E9663, 0x5232EB44, + 0xA86BEE40, 0xE1579367, 0x3A13140E, 0x732F6929, + 0xCB4D68F7, 0x827115D0, 0x593592B9, 0x1009EF9E, + 0xEA50EA9A, 0xA36C97BD, 0x782810D4, 0x31146DF3, + 0x1A00CB32, 0x533CB615, 0x8878317C, 0xC1444C5B, + 0x3B1D495F, 0x72213478, 0xA965B311, 0xE059CE36, + 0x583BCFE8, 0x1107B2CF, 0xCA4335A6, 0x837F4881, + 0x79264D85, 0x301A30A2, 0xEB5EB7CB, 0xA262CAEC, + 0x9E76C286, 0xD74ABFA1, 0x0C0E38C8, 0x453245EF, + 0xBF6B40EB, 0xF6573DCC, 0x2D13BAA5, 0x642FC782, + 0xDC4DC65C, 0x9571BB7B, 0x4E353C12, 0x07094135, + 0xFD504431, 0xB46C3916, 0x6F28BE7F, 0x2614C358, + 0x1700AEAB, 0x5E3CD38C, 0x857854E5, 0xCC4429C2, + 0x361D2CC6, 0x7F2151E1, 0xA465D688, 0xED59ABAF, + 0x553BAA71, 0x1C07D756, 0xC743503F, 0x8E7F2D18, + 0x7426281C, 0x3D1A553B, 0xE65ED252, 0xAF62AF75, + 0x9376A71F, 0xDA4ADA38, 0x010E5D51, 0x48322076, + 0xB26B2572, 0xFB575855, 0x2013DF3C, 0x692FA21B, + 0xD14DA3C5, 0x9871DEE2, 0x4335598B, 0x0A0924AC, + 0xF05021A8, 0xB96C5C8F, 0x6228DBE6, 0x2B14A6C1, + 0x34019664, 0x7D3DEB43, 0xA6796C2A, 0xEF45110D, + 0x151C1409, 0x5C20692E, 0x8764EE47, 0xCE589360, + 0x763A92BE, 0x3F06EF99, 0xE44268F0, 0xAD7E15D7, + 0x572710D3, 0x1E1B6DF4, 0xC55FEA9D, 0x8C6397BA, + 0xB0779FD0, 0xF94BE2F7, 0x220F659E, 0x6B3318B9, + 0x916A1DBD, 0xD856609A, 0x0312E7F3, 0x4A2E9AD4, + 0xF24C9B0A, 0xBB70E62D, 0x60346144, 0x29081C63, + 0xD3511967, 0x9A6D6440, 0x4129E329, 0x08159E0E, + 0x3901F3FD, 0x703D8EDA, 0xAB7909B3, 0xE2457494, + 0x181C7190, 0x51200CB7, 0x8A648BDE, 0xC358F6F9, + 0x7B3AF727, 0x32068A00, 0xE9420D69, 0xA07E704E, + 0x5A27754A, 0x131B086D, 0xC85F8F04, 0x8163F223, + 0xBD77FA49, 0xF44B876E, 0x2F0F0007, 0x66337D20, + 0x9C6A7824, 0xD5560503, 0x0E12826A, 0x472EFF4D, + 0xFF4CFE93, 0xB67083B4, 0x6D3404DD, 0x240879FA, + 0xDE517CFE, 0x976D01D9, 0x4C2986B0, 0x0515FB97, + 0x2E015D56, 0x673D2071, 0xBC79A718, 0xF545DA3F, + 0x0F1CDF3B, 0x4620A21C, 0x9D642575, 0xD4585852, + 0x6C3A598C, 0x250624AB, 0xFE42A3C2, 0xB77EDEE5, + 0x4D27DBE1, 0x041BA6C6, 0xDF5F21AF, 0x96635C88, + 0xAA7754E2, 0xE34B29C5, 0x380FAEAC, 0x7133D38B, + 0x8B6AD68F, 0xC256ABA8, 0x19122CC1, 0x502E51E6, + 0xE84C5038, 0xA1702D1F, 0x7A34AA76, 0x3308D751, + 0xC951D255, 0x806DAF72, 0x5B29281B, 0x1215553C, + 0x230138CF, 0x6A3D45E8, 0xB179C281, 0xF845BFA6, + 0x021CBAA2, 0x4B20C785, 0x906440EC, 0xD9583DCB, + 0x613A3C15, 0x28064132, 0xF342C65B, 0xBA7EBB7C, + 0x4027BE78, 0x091BC35F, 0xD25F4436, 0x9B633911, + 0xA777317B, 0xEE4B4C5C, 0x350FCB35, 0x7C33B612, + 0x866AB316, 0xCF56CE31, 0x14124958, 0x5D2E347F, + 0xE54C35A1, 0xAC704886, 0x7734CFEF, 0x3E08B2C8, + 0xC451B7CC, 0x8D6DCAEB, 0x56294D82, 0x1F1530A5 + }; + /** + * the current CRC value, bit-flipped + */ + private int crc; + + private SdkCrc32CChecksum() { + reset(); + } + + private SdkCrc32CChecksum(int crc) { + this.crc = crc; + } + + public static SdkCrc32CChecksum create() { + return new SdkCrc32CChecksum(); + } + + @Override + public long getValue() { + long ret = crc; + return ~ret & 0xffffffffL; + } + + @Override + public void reset() { + crc = 0xffffffff; + } + + @Override + public void update(byte[] b, int off, int len) { + int localCrc = crc; + + while (len > 7) { + int c0 = (b[off] ^ localCrc) & 0xff; + localCrc >>>= 8; + int c1 = (b[off + 1] ^ localCrc) & 0xff; + localCrc >>>= 8; + int c2 = (b[off + 2] ^ localCrc) & 0xff; + localCrc >>>= 8; + int c3 = (b[off + 3] ^ localCrc) & 0xff; + localCrc = T[T8_7_START + c0] ^ T[T8_6_START + c1] ^ + T[T8_5_START + c2] ^ T[T_8_4_START + c3]; + int c4 = b[off + 4] & 0xff; + int c5 = b[off + 5] & 0xff; + int c6 = b[off + 6] & 0xff; + int c7 = b[off + 7] & 0xff; + + localCrc ^= T[T8_3_START + c4] ^ T[T8_2_START + c5] ^ + T[T8_1_START + c6] ^ T[T8_0_START + c7]; + + off += 8; + len -= 8; + } + + /* loop unroll - duff's device style */ + for (int index = 0; index < len; index++) { + localCrc = (localCrc >>> 8) ^ T[T8_0_START + ((localCrc ^ b[off]) & 0xff)]; + off++; + } + + // Publish crc out to object + crc = localCrc; + } + + @Override + public void update(int b) { + crc = (crc >>> 8) ^ T[T8_0_START + ((crc ^ b) & 0xff)]; + } + + @Override + public SdkCrc32CChecksum clone() { + return new SdkCrc32CChecksum(crc); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/SdkCrc32Checksum.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/SdkCrc32Checksum.java new file mode 100644 index 000000000000..48f00aaac35e --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/SdkCrc32Checksum.java @@ -0,0 +1,623 @@ +/* + * 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.http.auth.aws.internal.signer.checksums; + +import java.util.zip.Checksum; +import software.amazon.awssdk.annotations.SdkInternalApi; + +/* + * THIS FILE HAS BEEN MODIFIED FROM THE ORIGINAL VERSION. + * + * The code comes from PureJavaCrc32.java in Apache Commons Codec 1.11. + * It has been modified to add a createCopy() method. + * The createCopy method is used to save current c checksum state when the checksum is marked. + */ +@SdkInternalApi +public final class SdkCrc32Checksum implements Checksum, Cloneable { + + /* + * CRC-32 lookup tables generated by the polynomial 0xEDB88320. + * See also TestPureJavaCrc32.Table. + */ + private static final int[] T = { + /* T8_0 */ + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, + 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, + 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, + 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, + 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, + 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, + 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, + 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, + 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, + 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, + 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, + 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, + 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, + 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, + 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, + 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D, + /* T8_1 */ + 0x00000000, 0x191B3141, 0x32366282, 0x2B2D53C3, + 0x646CC504, 0x7D77F445, 0x565AA786, 0x4F4196C7, + 0xC8D98A08, 0xD1C2BB49, 0xFAEFE88A, 0xE3F4D9CB, + 0xACB54F0C, 0xB5AE7E4D, 0x9E832D8E, 0x87981CCF, + 0x4AC21251, 0x53D92310, 0x78F470D3, 0x61EF4192, + 0x2EAED755, 0x37B5E614, 0x1C98B5D7, 0x05838496, + 0x821B9859, 0x9B00A918, 0xB02DFADB, 0xA936CB9A, + 0xE6775D5D, 0xFF6C6C1C, 0xD4413FDF, 0xCD5A0E9E, + 0x958424A2, 0x8C9F15E3, 0xA7B24620, 0xBEA97761, + 0xF1E8E1A6, 0xE8F3D0E7, 0xC3DE8324, 0xDAC5B265, + 0x5D5DAEAA, 0x44469FEB, 0x6F6BCC28, 0x7670FD69, + 0x39316BAE, 0x202A5AEF, 0x0B07092C, 0x121C386D, + 0xDF4636F3, 0xC65D07B2, 0xED705471, 0xF46B6530, + 0xBB2AF3F7, 0xA231C2B6, 0x891C9175, 0x9007A034, + 0x179FBCFB, 0x0E848DBA, 0x25A9DE79, 0x3CB2EF38, + 0x73F379FF, 0x6AE848BE, 0x41C51B7D, 0x58DE2A3C, + 0xF0794F05, 0xE9627E44, 0xC24F2D87, 0xDB541CC6, + 0x94158A01, 0x8D0EBB40, 0xA623E883, 0xBF38D9C2, + 0x38A0C50D, 0x21BBF44C, 0x0A96A78F, 0x138D96CE, + 0x5CCC0009, 0x45D73148, 0x6EFA628B, 0x77E153CA, + 0xBABB5D54, 0xA3A06C15, 0x888D3FD6, 0x91960E97, + 0xDED79850, 0xC7CCA911, 0xECE1FAD2, 0xF5FACB93, + 0x7262D75C, 0x6B79E61D, 0x4054B5DE, 0x594F849F, + 0x160E1258, 0x0F152319, 0x243870DA, 0x3D23419B, + 0x65FD6BA7, 0x7CE65AE6, 0x57CB0925, 0x4ED03864, + 0x0191AEA3, 0x188A9FE2, 0x33A7CC21, 0x2ABCFD60, + 0xAD24E1AF, 0xB43FD0EE, 0x9F12832D, 0x8609B26C, + 0xC94824AB, 0xD05315EA, 0xFB7E4629, 0xE2657768, + 0x2F3F79F6, 0x362448B7, 0x1D091B74, 0x04122A35, + 0x4B53BCF2, 0x52488DB3, 0x7965DE70, 0x607EEF31, + 0xE7E6F3FE, 0xFEFDC2BF, 0xD5D0917C, 0xCCCBA03D, + 0x838A36FA, 0x9A9107BB, 0xB1BC5478, 0xA8A76539, + 0x3B83984B, 0x2298A90A, 0x09B5FAC9, 0x10AECB88, + 0x5FEF5D4F, 0x46F46C0E, 0x6DD93FCD, 0x74C20E8C, + 0xF35A1243, 0xEA412302, 0xC16C70C1, 0xD8774180, + 0x9736D747, 0x8E2DE606, 0xA500B5C5, 0xBC1B8484, + 0x71418A1A, 0x685ABB5B, 0x4377E898, 0x5A6CD9D9, + 0x152D4F1E, 0x0C367E5F, 0x271B2D9C, 0x3E001CDD, + 0xB9980012, 0xA0833153, 0x8BAE6290, 0x92B553D1, + 0xDDF4C516, 0xC4EFF457, 0xEFC2A794, 0xF6D996D5, + 0xAE07BCE9, 0xB71C8DA8, 0x9C31DE6B, 0x852AEF2A, + 0xCA6B79ED, 0xD37048AC, 0xF85D1B6F, 0xE1462A2E, + 0x66DE36E1, 0x7FC507A0, 0x54E85463, 0x4DF36522, + 0x02B2F3E5, 0x1BA9C2A4, 0x30849167, 0x299FA026, + 0xE4C5AEB8, 0xFDDE9FF9, 0xD6F3CC3A, 0xCFE8FD7B, + 0x80A96BBC, 0x99B25AFD, 0xB29F093E, 0xAB84387F, + 0x2C1C24B0, 0x350715F1, 0x1E2A4632, 0x07317773, + 0x4870E1B4, 0x516BD0F5, 0x7A468336, 0x635DB277, + 0xCBFAD74E, 0xD2E1E60F, 0xF9CCB5CC, 0xE0D7848D, + 0xAF96124A, 0xB68D230B, 0x9DA070C8, 0x84BB4189, + 0x03235D46, 0x1A386C07, 0x31153FC4, 0x280E0E85, + 0x674F9842, 0x7E54A903, 0x5579FAC0, 0x4C62CB81, + 0x8138C51F, 0x9823F45E, 0xB30EA79D, 0xAA1596DC, + 0xE554001B, 0xFC4F315A, 0xD7626299, 0xCE7953D8, + 0x49E14F17, 0x50FA7E56, 0x7BD72D95, 0x62CC1CD4, + 0x2D8D8A13, 0x3496BB52, 0x1FBBE891, 0x06A0D9D0, + 0x5E7EF3EC, 0x4765C2AD, 0x6C48916E, 0x7553A02F, + 0x3A1236E8, 0x230907A9, 0x0824546A, 0x113F652B, + 0x96A779E4, 0x8FBC48A5, 0xA4911B66, 0xBD8A2A27, + 0xF2CBBCE0, 0xEBD08DA1, 0xC0FDDE62, 0xD9E6EF23, + 0x14BCE1BD, 0x0DA7D0FC, 0x268A833F, 0x3F91B27E, + 0x70D024B9, 0x69CB15F8, 0x42E6463B, 0x5BFD777A, + 0xDC656BB5, 0xC57E5AF4, 0xEE530937, 0xF7483876, + 0xB809AEB1, 0xA1129FF0, 0x8A3FCC33, 0x9324FD72, + /* T8_2 */ + 0x00000000, 0x01C26A37, 0x0384D46E, 0x0246BE59, + 0x0709A8DC, 0x06CBC2EB, 0x048D7CB2, 0x054F1685, + 0x0E1351B8, 0x0FD13B8F, 0x0D9785D6, 0x0C55EFE1, + 0x091AF964, 0x08D89353, 0x0A9E2D0A, 0x0B5C473D, + 0x1C26A370, 0x1DE4C947, 0x1FA2771E, 0x1E601D29, + 0x1B2F0BAC, 0x1AED619B, 0x18ABDFC2, 0x1969B5F5, + 0x1235F2C8, 0x13F798FF, 0x11B126A6, 0x10734C91, + 0x153C5A14, 0x14FE3023, 0x16B88E7A, 0x177AE44D, + 0x384D46E0, 0x398F2CD7, 0x3BC9928E, 0x3A0BF8B9, + 0x3F44EE3C, 0x3E86840B, 0x3CC03A52, 0x3D025065, + 0x365E1758, 0x379C7D6F, 0x35DAC336, 0x3418A901, + 0x3157BF84, 0x3095D5B3, 0x32D36BEA, 0x331101DD, + 0x246BE590, 0x25A98FA7, 0x27EF31FE, 0x262D5BC9, + 0x23624D4C, 0x22A0277B, 0x20E69922, 0x2124F315, + 0x2A78B428, 0x2BBADE1F, 0x29FC6046, 0x283E0A71, + 0x2D711CF4, 0x2CB376C3, 0x2EF5C89A, 0x2F37A2AD, + 0x709A8DC0, 0x7158E7F7, 0x731E59AE, 0x72DC3399, + 0x7793251C, 0x76514F2B, 0x7417F172, 0x75D59B45, + 0x7E89DC78, 0x7F4BB64F, 0x7D0D0816, 0x7CCF6221, + 0x798074A4, 0x78421E93, 0x7A04A0CA, 0x7BC6CAFD, + 0x6CBC2EB0, 0x6D7E4487, 0x6F38FADE, 0x6EFA90E9, + 0x6BB5866C, 0x6A77EC5B, 0x68315202, 0x69F33835, + 0x62AF7F08, 0x636D153F, 0x612BAB66, 0x60E9C151, + 0x65A6D7D4, 0x6464BDE3, 0x662203BA, 0x67E0698D, + 0x48D7CB20, 0x4915A117, 0x4B531F4E, 0x4A917579, + 0x4FDE63FC, 0x4E1C09CB, 0x4C5AB792, 0x4D98DDA5, + 0x46C49A98, 0x4706F0AF, 0x45404EF6, 0x448224C1, + 0x41CD3244, 0x400F5873, 0x4249E62A, 0x438B8C1D, + 0x54F16850, 0x55330267, 0x5775BC3E, 0x56B7D609, + 0x53F8C08C, 0x523AAABB, 0x507C14E2, 0x51BE7ED5, + 0x5AE239E8, 0x5B2053DF, 0x5966ED86, 0x58A487B1, + 0x5DEB9134, 0x5C29FB03, 0x5E6F455A, 0x5FAD2F6D, + 0xE1351B80, 0xE0F771B7, 0xE2B1CFEE, 0xE373A5D9, + 0xE63CB35C, 0xE7FED96B, 0xE5B86732, 0xE47A0D05, + 0xEF264A38, 0xEEE4200F, 0xECA29E56, 0xED60F461, + 0xE82FE2E4, 0xE9ED88D3, 0xEBAB368A, 0xEA695CBD, + 0xFD13B8F0, 0xFCD1D2C7, 0xFE976C9E, 0xFF5506A9, + 0xFA1A102C, 0xFBD87A1B, 0xF99EC442, 0xF85CAE75, + 0xF300E948, 0xF2C2837F, 0xF0843D26, 0xF1465711, + 0xF4094194, 0xF5CB2BA3, 0xF78D95FA, 0xF64FFFCD, + 0xD9785D60, 0xD8BA3757, 0xDAFC890E, 0xDB3EE339, + 0xDE71F5BC, 0xDFB39F8B, 0xDDF521D2, 0xDC374BE5, + 0xD76B0CD8, 0xD6A966EF, 0xD4EFD8B6, 0xD52DB281, + 0xD062A404, 0xD1A0CE33, 0xD3E6706A, 0xD2241A5D, + 0xC55EFE10, 0xC49C9427, 0xC6DA2A7E, 0xC7184049, + 0xC25756CC, 0xC3953CFB, 0xC1D382A2, 0xC011E895, + 0xCB4DAFA8, 0xCA8FC59F, 0xC8C97BC6, 0xC90B11F1, + 0xCC440774, 0xCD866D43, 0xCFC0D31A, 0xCE02B92D, + 0x91AF9640, 0x906DFC77, 0x922B422E, 0x93E92819, + 0x96A63E9C, 0x976454AB, 0x9522EAF2, 0x94E080C5, + 0x9FBCC7F8, 0x9E7EADCF, 0x9C381396, 0x9DFA79A1, + 0x98B56F24, 0x99770513, 0x9B31BB4A, 0x9AF3D17D, + 0x8D893530, 0x8C4B5F07, 0x8E0DE15E, 0x8FCF8B69, + 0x8A809DEC, 0x8B42F7DB, 0x89044982, 0x88C623B5, + 0x839A6488, 0x82580EBF, 0x801EB0E6, 0x81DCDAD1, + 0x8493CC54, 0x8551A663, 0x8717183A, 0x86D5720D, + 0xA9E2D0A0, 0xA820BA97, 0xAA6604CE, 0xABA46EF9, + 0xAEEB787C, 0xAF29124B, 0xAD6FAC12, 0xACADC625, + 0xA7F18118, 0xA633EB2F, 0xA4755576, 0xA5B73F41, + 0xA0F829C4, 0xA13A43F3, 0xA37CFDAA, 0xA2BE979D, + 0xB5C473D0, 0xB40619E7, 0xB640A7BE, 0xB782CD89, + 0xB2CDDB0C, 0xB30FB13B, 0xB1490F62, 0xB08B6555, + 0xBBD72268, 0xBA15485F, 0xB853F606, 0xB9919C31, + 0xBCDE8AB4, 0xBD1CE083, 0xBF5A5EDA, 0xBE9834ED, + /* T8_3 */ + 0x00000000, 0xB8BC6765, 0xAA09C88B, 0x12B5AFEE, + 0x8F629757, 0x37DEF032, 0x256B5FDC, 0x9DD738B9, + 0xC5B428EF, 0x7D084F8A, 0x6FBDE064, 0xD7018701, + 0x4AD6BFB8, 0xF26AD8DD, 0xE0DF7733, 0x58631056, + 0x5019579F, 0xE8A530FA, 0xFA109F14, 0x42ACF871, + 0xDF7BC0C8, 0x67C7A7AD, 0x75720843, 0xCDCE6F26, + 0x95AD7F70, 0x2D111815, 0x3FA4B7FB, 0x8718D09E, + 0x1ACFE827, 0xA2738F42, 0xB0C620AC, 0x087A47C9, + 0xA032AF3E, 0x188EC85B, 0x0A3B67B5, 0xB28700D0, + 0x2F503869, 0x97EC5F0C, 0x8559F0E2, 0x3DE59787, + 0x658687D1, 0xDD3AE0B4, 0xCF8F4F5A, 0x7733283F, + 0xEAE41086, 0x525877E3, 0x40EDD80D, 0xF851BF68, + 0xF02BF8A1, 0x48979FC4, 0x5A22302A, 0xE29E574F, + 0x7F496FF6, 0xC7F50893, 0xD540A77D, 0x6DFCC018, + 0x359FD04E, 0x8D23B72B, 0x9F9618C5, 0x272A7FA0, + 0xBAFD4719, 0x0241207C, 0x10F48F92, 0xA848E8F7, + 0x9B14583D, 0x23A83F58, 0x311D90B6, 0x89A1F7D3, + 0x1476CF6A, 0xACCAA80F, 0xBE7F07E1, 0x06C36084, + 0x5EA070D2, 0xE61C17B7, 0xF4A9B859, 0x4C15DF3C, + 0xD1C2E785, 0x697E80E0, 0x7BCB2F0E, 0xC377486B, + 0xCB0D0FA2, 0x73B168C7, 0x6104C729, 0xD9B8A04C, + 0x446F98F5, 0xFCD3FF90, 0xEE66507E, 0x56DA371B, + 0x0EB9274D, 0xB6054028, 0xA4B0EFC6, 0x1C0C88A3, + 0x81DBB01A, 0x3967D77F, 0x2BD27891, 0x936E1FF4, + 0x3B26F703, 0x839A9066, 0x912F3F88, 0x299358ED, + 0xB4446054, 0x0CF80731, 0x1E4DA8DF, 0xA6F1CFBA, + 0xFE92DFEC, 0x462EB889, 0x549B1767, 0xEC277002, + 0x71F048BB, 0xC94C2FDE, 0xDBF98030, 0x6345E755, + 0x6B3FA09C, 0xD383C7F9, 0xC1366817, 0x798A0F72, + 0xE45D37CB, 0x5CE150AE, 0x4E54FF40, 0xF6E89825, + 0xAE8B8873, 0x1637EF16, 0x048240F8, 0xBC3E279D, + 0x21E91F24, 0x99557841, 0x8BE0D7AF, 0x335CB0CA, + 0xED59B63B, 0x55E5D15E, 0x47507EB0, 0xFFEC19D5, + 0x623B216C, 0xDA874609, 0xC832E9E7, 0x708E8E82, + 0x28ED9ED4, 0x9051F9B1, 0x82E4565F, 0x3A58313A, + 0xA78F0983, 0x1F336EE6, 0x0D86C108, 0xB53AA66D, + 0xBD40E1A4, 0x05FC86C1, 0x1749292F, 0xAFF54E4A, + 0x322276F3, 0x8A9E1196, 0x982BBE78, 0x2097D91D, + 0x78F4C94B, 0xC048AE2E, 0xD2FD01C0, 0x6A4166A5, + 0xF7965E1C, 0x4F2A3979, 0x5D9F9697, 0xE523F1F2, + 0x4D6B1905, 0xF5D77E60, 0xE762D18E, 0x5FDEB6EB, + 0xC2098E52, 0x7AB5E937, 0x680046D9, 0xD0BC21BC, + 0x88DF31EA, 0x3063568F, 0x22D6F961, 0x9A6A9E04, + 0x07BDA6BD, 0xBF01C1D8, 0xADB46E36, 0x15080953, + 0x1D724E9A, 0xA5CE29FF, 0xB77B8611, 0x0FC7E174, + 0x9210D9CD, 0x2AACBEA8, 0x38191146, 0x80A57623, + 0xD8C66675, 0x607A0110, 0x72CFAEFE, 0xCA73C99B, + 0x57A4F122, 0xEF189647, 0xFDAD39A9, 0x45115ECC, + 0x764DEE06, 0xCEF18963, 0xDC44268D, 0x64F841E8, + 0xF92F7951, 0x41931E34, 0x5326B1DA, 0xEB9AD6BF, + 0xB3F9C6E9, 0x0B45A18C, 0x19F00E62, 0xA14C6907, + 0x3C9B51BE, 0x842736DB, 0x96929935, 0x2E2EFE50, + 0x2654B999, 0x9EE8DEFC, 0x8C5D7112, 0x34E11677, + 0xA9362ECE, 0x118A49AB, 0x033FE645, 0xBB838120, + 0xE3E09176, 0x5B5CF613, 0x49E959FD, 0xF1553E98, + 0x6C820621, 0xD43E6144, 0xC68BCEAA, 0x7E37A9CF, + 0xD67F4138, 0x6EC3265D, 0x7C7689B3, 0xC4CAEED6, + 0x591DD66F, 0xE1A1B10A, 0xF3141EE4, 0x4BA87981, + 0x13CB69D7, 0xAB770EB2, 0xB9C2A15C, 0x017EC639, + 0x9CA9FE80, 0x241599E5, 0x36A0360B, 0x8E1C516E, + 0x866616A7, 0x3EDA71C2, 0x2C6FDE2C, 0x94D3B949, + 0x090481F0, 0xB1B8E695, 0xA30D497B, 0x1BB12E1E, + 0x43D23E48, 0xFB6E592D, 0xE9DBF6C3, 0x516791A6, + 0xCCB0A91F, 0x740CCE7A, 0x66B96194, 0xDE0506F1, + /* T8_4 */ + 0x00000000, 0x3D6029B0, 0x7AC05360, 0x47A07AD0, + 0xF580A6C0, 0xC8E08F70, 0x8F40F5A0, 0xB220DC10, + 0x30704BC1, 0x0D106271, 0x4AB018A1, 0x77D03111, + 0xC5F0ED01, 0xF890C4B1, 0xBF30BE61, 0x825097D1, + 0x60E09782, 0x5D80BE32, 0x1A20C4E2, 0x2740ED52, + 0x95603142, 0xA80018F2, 0xEFA06222, 0xD2C04B92, + 0x5090DC43, 0x6DF0F5F3, 0x2A508F23, 0x1730A693, + 0xA5107A83, 0x98705333, 0xDFD029E3, 0xE2B00053, + 0xC1C12F04, 0xFCA106B4, 0xBB017C64, 0x866155D4, + 0x344189C4, 0x0921A074, 0x4E81DAA4, 0x73E1F314, + 0xF1B164C5, 0xCCD14D75, 0x8B7137A5, 0xB6111E15, + 0x0431C205, 0x3951EBB5, 0x7EF19165, 0x4391B8D5, + 0xA121B886, 0x9C419136, 0xDBE1EBE6, 0xE681C256, + 0x54A11E46, 0x69C137F6, 0x2E614D26, 0x13016496, + 0x9151F347, 0xAC31DAF7, 0xEB91A027, 0xD6F18997, + 0x64D15587, 0x59B17C37, 0x1E1106E7, 0x23712F57, + 0x58F35849, 0x659371F9, 0x22330B29, 0x1F532299, + 0xAD73FE89, 0x9013D739, 0xD7B3ADE9, 0xEAD38459, + 0x68831388, 0x55E33A38, 0x124340E8, 0x2F236958, + 0x9D03B548, 0xA0639CF8, 0xE7C3E628, 0xDAA3CF98, + 0x3813CFCB, 0x0573E67B, 0x42D39CAB, 0x7FB3B51B, + 0xCD93690B, 0xF0F340BB, 0xB7533A6B, 0x8A3313DB, + 0x0863840A, 0x3503ADBA, 0x72A3D76A, 0x4FC3FEDA, + 0xFDE322CA, 0xC0830B7A, 0x872371AA, 0xBA43581A, + 0x9932774D, 0xA4525EFD, 0xE3F2242D, 0xDE920D9D, + 0x6CB2D18D, 0x51D2F83D, 0x167282ED, 0x2B12AB5D, + 0xA9423C8C, 0x9422153C, 0xD3826FEC, 0xEEE2465C, + 0x5CC29A4C, 0x61A2B3FC, 0x2602C92C, 0x1B62E09C, + 0xF9D2E0CF, 0xC4B2C97F, 0x8312B3AF, 0xBE729A1F, + 0x0C52460F, 0x31326FBF, 0x7692156F, 0x4BF23CDF, + 0xC9A2AB0E, 0xF4C282BE, 0xB362F86E, 0x8E02D1DE, + 0x3C220DCE, 0x0142247E, 0x46E25EAE, 0x7B82771E, + 0xB1E6B092, 0x8C869922, 0xCB26E3F2, 0xF646CA42, + 0x44661652, 0x79063FE2, 0x3EA64532, 0x03C66C82, + 0x8196FB53, 0xBCF6D2E3, 0xFB56A833, 0xC6368183, + 0x74165D93, 0x49767423, 0x0ED60EF3, 0x33B62743, + 0xD1062710, 0xEC660EA0, 0xABC67470, 0x96A65DC0, + 0x248681D0, 0x19E6A860, 0x5E46D2B0, 0x6326FB00, + 0xE1766CD1, 0xDC164561, 0x9BB63FB1, 0xA6D61601, + 0x14F6CA11, 0x2996E3A1, 0x6E369971, 0x5356B0C1, + 0x70279F96, 0x4D47B626, 0x0AE7CCF6, 0x3787E546, + 0x85A73956, 0xB8C710E6, 0xFF676A36, 0xC2074386, + 0x4057D457, 0x7D37FDE7, 0x3A978737, 0x07F7AE87, + 0xB5D77297, 0x88B75B27, 0xCF1721F7, 0xF2770847, + 0x10C70814, 0x2DA721A4, 0x6A075B74, 0x576772C4, + 0xE547AED4, 0xD8278764, 0x9F87FDB4, 0xA2E7D404, + 0x20B743D5, 0x1DD76A65, 0x5A7710B5, 0x67173905, + 0xD537E515, 0xE857CCA5, 0xAFF7B675, 0x92979FC5, + 0xE915E8DB, 0xD475C16B, 0x93D5BBBB, 0xAEB5920B, + 0x1C954E1B, 0x21F567AB, 0x66551D7B, 0x5B3534CB, + 0xD965A31A, 0xE4058AAA, 0xA3A5F07A, 0x9EC5D9CA, + 0x2CE505DA, 0x11852C6A, 0x562556BA, 0x6B457F0A, + 0x89F57F59, 0xB49556E9, 0xF3352C39, 0xCE550589, + 0x7C75D999, 0x4115F029, 0x06B58AF9, 0x3BD5A349, + 0xB9853498, 0x84E51D28, 0xC34567F8, 0xFE254E48, + 0x4C059258, 0x7165BBE8, 0x36C5C138, 0x0BA5E888, + 0x28D4C7DF, 0x15B4EE6F, 0x521494BF, 0x6F74BD0F, + 0xDD54611F, 0xE03448AF, 0xA794327F, 0x9AF41BCF, + 0x18A48C1E, 0x25C4A5AE, 0x6264DF7E, 0x5F04F6CE, + 0xED242ADE, 0xD044036E, 0x97E479BE, 0xAA84500E, + 0x4834505D, 0x755479ED, 0x32F4033D, 0x0F942A8D, + 0xBDB4F69D, 0x80D4DF2D, 0xC774A5FD, 0xFA148C4D, + 0x78441B9C, 0x4524322C, 0x028448FC, 0x3FE4614C, + 0x8DC4BD5C, 0xB0A494EC, 0xF704EE3C, 0xCA64C78C, + /* T8_5 */ + 0x00000000, 0xCB5CD3A5, 0x4DC8A10B, 0x869472AE, + 0x9B914216, 0x50CD91B3, 0xD659E31D, 0x1D0530B8, + 0xEC53826D, 0x270F51C8, 0xA19B2366, 0x6AC7F0C3, + 0x77C2C07B, 0xBC9E13DE, 0x3A0A6170, 0xF156B2D5, + 0x03D6029B, 0xC88AD13E, 0x4E1EA390, 0x85427035, + 0x9847408D, 0x531B9328, 0xD58FE186, 0x1ED33223, + 0xEF8580F6, 0x24D95353, 0xA24D21FD, 0x6911F258, + 0x7414C2E0, 0xBF481145, 0x39DC63EB, 0xF280B04E, + 0x07AC0536, 0xCCF0D693, 0x4A64A43D, 0x81387798, + 0x9C3D4720, 0x57619485, 0xD1F5E62B, 0x1AA9358E, + 0xEBFF875B, 0x20A354FE, 0xA6372650, 0x6D6BF5F5, + 0x706EC54D, 0xBB3216E8, 0x3DA66446, 0xF6FAB7E3, + 0x047A07AD, 0xCF26D408, 0x49B2A6A6, 0x82EE7503, + 0x9FEB45BB, 0x54B7961E, 0xD223E4B0, 0x197F3715, + 0xE82985C0, 0x23755665, 0xA5E124CB, 0x6EBDF76E, + 0x73B8C7D6, 0xB8E41473, 0x3E7066DD, 0xF52CB578, + 0x0F580A6C, 0xC404D9C9, 0x4290AB67, 0x89CC78C2, + 0x94C9487A, 0x5F959BDF, 0xD901E971, 0x125D3AD4, + 0xE30B8801, 0x28575BA4, 0xAEC3290A, 0x659FFAAF, + 0x789ACA17, 0xB3C619B2, 0x35526B1C, 0xFE0EB8B9, + 0x0C8E08F7, 0xC7D2DB52, 0x4146A9FC, 0x8A1A7A59, + 0x971F4AE1, 0x5C439944, 0xDAD7EBEA, 0x118B384F, + 0xE0DD8A9A, 0x2B81593F, 0xAD152B91, 0x6649F834, + 0x7B4CC88C, 0xB0101B29, 0x36846987, 0xFDD8BA22, + 0x08F40F5A, 0xC3A8DCFF, 0x453CAE51, 0x8E607DF4, + 0x93654D4C, 0x58399EE9, 0xDEADEC47, 0x15F13FE2, + 0xE4A78D37, 0x2FFB5E92, 0xA96F2C3C, 0x6233FF99, + 0x7F36CF21, 0xB46A1C84, 0x32FE6E2A, 0xF9A2BD8F, + 0x0B220DC1, 0xC07EDE64, 0x46EAACCA, 0x8DB67F6F, + 0x90B34FD7, 0x5BEF9C72, 0xDD7BEEDC, 0x16273D79, + 0xE7718FAC, 0x2C2D5C09, 0xAAB92EA7, 0x61E5FD02, + 0x7CE0CDBA, 0xB7BC1E1F, 0x31286CB1, 0xFA74BF14, + 0x1EB014D8, 0xD5ECC77D, 0x5378B5D3, 0x98246676, + 0x852156CE, 0x4E7D856B, 0xC8E9F7C5, 0x03B52460, + 0xF2E396B5, 0x39BF4510, 0xBF2B37BE, 0x7477E41B, + 0x6972D4A3, 0xA22E0706, 0x24BA75A8, 0xEFE6A60D, + 0x1D661643, 0xD63AC5E6, 0x50AEB748, 0x9BF264ED, + 0x86F75455, 0x4DAB87F0, 0xCB3FF55E, 0x006326FB, + 0xF135942E, 0x3A69478B, 0xBCFD3525, 0x77A1E680, + 0x6AA4D638, 0xA1F8059D, 0x276C7733, 0xEC30A496, + 0x191C11EE, 0xD240C24B, 0x54D4B0E5, 0x9F886340, + 0x828D53F8, 0x49D1805D, 0xCF45F2F3, 0x04192156, + 0xF54F9383, 0x3E134026, 0xB8873288, 0x73DBE12D, + 0x6EDED195, 0xA5820230, 0x2316709E, 0xE84AA33B, + 0x1ACA1375, 0xD196C0D0, 0x5702B27E, 0x9C5E61DB, + 0x815B5163, 0x4A0782C6, 0xCC93F068, 0x07CF23CD, + 0xF6999118, 0x3DC542BD, 0xBB513013, 0x700DE3B6, + 0x6D08D30E, 0xA65400AB, 0x20C07205, 0xEB9CA1A0, + 0x11E81EB4, 0xDAB4CD11, 0x5C20BFBF, 0x977C6C1A, + 0x8A795CA2, 0x41258F07, 0xC7B1FDA9, 0x0CED2E0C, + 0xFDBB9CD9, 0x36E74F7C, 0xB0733DD2, 0x7B2FEE77, + 0x662ADECF, 0xAD760D6A, 0x2BE27FC4, 0xE0BEAC61, + 0x123E1C2F, 0xD962CF8A, 0x5FF6BD24, 0x94AA6E81, + 0x89AF5E39, 0x42F38D9C, 0xC467FF32, 0x0F3B2C97, + 0xFE6D9E42, 0x35314DE7, 0xB3A53F49, 0x78F9ECEC, + 0x65FCDC54, 0xAEA00FF1, 0x28347D5F, 0xE368AEFA, + 0x16441B82, 0xDD18C827, 0x5B8CBA89, 0x90D0692C, + 0x8DD55994, 0x46898A31, 0xC01DF89F, 0x0B412B3A, + 0xFA1799EF, 0x314B4A4A, 0xB7DF38E4, 0x7C83EB41, + 0x6186DBF9, 0xAADA085C, 0x2C4E7AF2, 0xE712A957, + 0x15921919, 0xDECECABC, 0x585AB812, 0x93066BB7, + 0x8E035B0F, 0x455F88AA, 0xC3CBFA04, 0x089729A1, + 0xF9C19B74, 0x329D48D1, 0xB4093A7F, 0x7F55E9DA, + 0x6250D962, 0xA90C0AC7, 0x2F987869, 0xE4C4ABCC, + /* T8_6 */ + 0x00000000, 0xA6770BB4, 0x979F1129, 0x31E81A9D, + 0xF44F2413, 0x52382FA7, 0x63D0353A, 0xC5A73E8E, + 0x33EF4E67, 0x959845D3, 0xA4705F4E, 0x020754FA, + 0xC7A06A74, 0x61D761C0, 0x503F7B5D, 0xF64870E9, + 0x67DE9CCE, 0xC1A9977A, 0xF0418DE7, 0x56368653, + 0x9391B8DD, 0x35E6B369, 0x040EA9F4, 0xA279A240, + 0x5431D2A9, 0xF246D91D, 0xC3AEC380, 0x65D9C834, + 0xA07EF6BA, 0x0609FD0E, 0x37E1E793, 0x9196EC27, + 0xCFBD399C, 0x69CA3228, 0x582228B5, 0xFE552301, + 0x3BF21D8F, 0x9D85163B, 0xAC6D0CA6, 0x0A1A0712, + 0xFC5277FB, 0x5A257C4F, 0x6BCD66D2, 0xCDBA6D66, + 0x081D53E8, 0xAE6A585C, 0x9F8242C1, 0x39F54975, + 0xA863A552, 0x0E14AEE6, 0x3FFCB47B, 0x998BBFCF, + 0x5C2C8141, 0xFA5B8AF5, 0xCBB39068, 0x6DC49BDC, + 0x9B8CEB35, 0x3DFBE081, 0x0C13FA1C, 0xAA64F1A8, + 0x6FC3CF26, 0xC9B4C492, 0xF85CDE0F, 0x5E2BD5BB, + 0x440B7579, 0xE27C7ECD, 0xD3946450, 0x75E36FE4, + 0xB044516A, 0x16335ADE, 0x27DB4043, 0x81AC4BF7, + 0x77E43B1E, 0xD19330AA, 0xE07B2A37, 0x460C2183, + 0x83AB1F0D, 0x25DC14B9, 0x14340E24, 0xB2430590, + 0x23D5E9B7, 0x85A2E203, 0xB44AF89E, 0x123DF32A, + 0xD79ACDA4, 0x71EDC610, 0x4005DC8D, 0xE672D739, + 0x103AA7D0, 0xB64DAC64, 0x87A5B6F9, 0x21D2BD4D, + 0xE47583C3, 0x42028877, 0x73EA92EA, 0xD59D995E, + 0x8BB64CE5, 0x2DC14751, 0x1C295DCC, 0xBA5E5678, + 0x7FF968F6, 0xD98E6342, 0xE86679DF, 0x4E11726B, + 0xB8590282, 0x1E2E0936, 0x2FC613AB, 0x89B1181F, + 0x4C162691, 0xEA612D25, 0xDB8937B8, 0x7DFE3C0C, + 0xEC68D02B, 0x4A1FDB9F, 0x7BF7C102, 0xDD80CAB6, + 0x1827F438, 0xBE50FF8C, 0x8FB8E511, 0x29CFEEA5, + 0xDF879E4C, 0x79F095F8, 0x48188F65, 0xEE6F84D1, + 0x2BC8BA5F, 0x8DBFB1EB, 0xBC57AB76, 0x1A20A0C2, + 0x8816EAF2, 0x2E61E146, 0x1F89FBDB, 0xB9FEF06F, + 0x7C59CEE1, 0xDA2EC555, 0xEBC6DFC8, 0x4DB1D47C, + 0xBBF9A495, 0x1D8EAF21, 0x2C66B5BC, 0x8A11BE08, + 0x4FB68086, 0xE9C18B32, 0xD82991AF, 0x7E5E9A1B, + 0xEFC8763C, 0x49BF7D88, 0x78576715, 0xDE206CA1, + 0x1B87522F, 0xBDF0599B, 0x8C184306, 0x2A6F48B2, + 0xDC27385B, 0x7A5033EF, 0x4BB82972, 0xEDCF22C6, + 0x28681C48, 0x8E1F17FC, 0xBFF70D61, 0x198006D5, + 0x47ABD36E, 0xE1DCD8DA, 0xD034C247, 0x7643C9F3, + 0xB3E4F77D, 0x1593FCC9, 0x247BE654, 0x820CEDE0, + 0x74449D09, 0xD23396BD, 0xE3DB8C20, 0x45AC8794, + 0x800BB91A, 0x267CB2AE, 0x1794A833, 0xB1E3A387, + 0x20754FA0, 0x86024414, 0xB7EA5E89, 0x119D553D, + 0xD43A6BB3, 0x724D6007, 0x43A57A9A, 0xE5D2712E, + 0x139A01C7, 0xB5ED0A73, 0x840510EE, 0x22721B5A, + 0xE7D525D4, 0x41A22E60, 0x704A34FD, 0xD63D3F49, + 0xCC1D9F8B, 0x6A6A943F, 0x5B828EA2, 0xFDF58516, + 0x3852BB98, 0x9E25B02C, 0xAFCDAAB1, 0x09BAA105, + 0xFFF2D1EC, 0x5985DA58, 0x686DC0C5, 0xCE1ACB71, + 0x0BBDF5FF, 0xADCAFE4B, 0x9C22E4D6, 0x3A55EF62, + 0xABC30345, 0x0DB408F1, 0x3C5C126C, 0x9A2B19D8, + 0x5F8C2756, 0xF9FB2CE2, 0xC813367F, 0x6E643DCB, + 0x982C4D22, 0x3E5B4696, 0x0FB35C0B, 0xA9C457BF, + 0x6C636931, 0xCA146285, 0xFBFC7818, 0x5D8B73AC, + 0x03A0A617, 0xA5D7ADA3, 0x943FB73E, 0x3248BC8A, + 0xF7EF8204, 0x519889B0, 0x6070932D, 0xC6079899, + 0x304FE870, 0x9638E3C4, 0xA7D0F959, 0x01A7F2ED, + 0xC400CC63, 0x6277C7D7, 0x539FDD4A, 0xF5E8D6FE, + 0x647E3AD9, 0xC209316D, 0xF3E12BF0, 0x55962044, + 0x90311ECA, 0x3646157E, 0x07AE0FE3, 0xA1D90457, + 0x579174BE, 0xF1E67F0A, 0xC00E6597, 0x66796E23, + 0xA3DE50AD, 0x05A95B19, 0x34414184, 0x92364A30, + /* T8_7 */ + 0x00000000, 0xCCAA009E, 0x4225077D, 0x8E8F07E3, + 0x844A0EFA, 0x48E00E64, 0xC66F0987, 0x0AC50919, + 0xD3E51BB5, 0x1F4F1B2B, 0x91C01CC8, 0x5D6A1C56, + 0x57AF154F, 0x9B0515D1, 0x158A1232, 0xD92012AC, + 0x7CBB312B, 0xB01131B5, 0x3E9E3656, 0xF23436C8, + 0xF8F13FD1, 0x345B3F4F, 0xBAD438AC, 0x767E3832, + 0xAF5E2A9E, 0x63F42A00, 0xED7B2DE3, 0x21D12D7D, + 0x2B142464, 0xE7BE24FA, 0x69312319, 0xA59B2387, + 0xF9766256, 0x35DC62C8, 0xBB53652B, 0x77F965B5, + 0x7D3C6CAC, 0xB1966C32, 0x3F196BD1, 0xF3B36B4F, + 0x2A9379E3, 0xE639797D, 0x68B67E9E, 0xA41C7E00, + 0xAED97719, 0x62737787, 0xECFC7064, 0x205670FA, + 0x85CD537D, 0x496753E3, 0xC7E85400, 0x0B42549E, + 0x01875D87, 0xCD2D5D19, 0x43A25AFA, 0x8F085A64, + 0x562848C8, 0x9A824856, 0x140D4FB5, 0xD8A74F2B, + 0xD2624632, 0x1EC846AC, 0x9047414F, 0x5CED41D1, + 0x299DC2ED, 0xE537C273, 0x6BB8C590, 0xA712C50E, + 0xADD7CC17, 0x617DCC89, 0xEFF2CB6A, 0x2358CBF4, + 0xFA78D958, 0x36D2D9C6, 0xB85DDE25, 0x74F7DEBB, + 0x7E32D7A2, 0xB298D73C, 0x3C17D0DF, 0xF0BDD041, + 0x5526F3C6, 0x998CF358, 0x1703F4BB, 0xDBA9F425, + 0xD16CFD3C, 0x1DC6FDA2, 0x9349FA41, 0x5FE3FADF, + 0x86C3E873, 0x4A69E8ED, 0xC4E6EF0E, 0x084CEF90, + 0x0289E689, 0xCE23E617, 0x40ACE1F4, 0x8C06E16A, + 0xD0EBA0BB, 0x1C41A025, 0x92CEA7C6, 0x5E64A758, + 0x54A1AE41, 0x980BAEDF, 0x1684A93C, 0xDA2EA9A2, + 0x030EBB0E, 0xCFA4BB90, 0x412BBC73, 0x8D81BCED, + 0x8744B5F4, 0x4BEEB56A, 0xC561B289, 0x09CBB217, + 0xAC509190, 0x60FA910E, 0xEE7596ED, 0x22DF9673, + 0x281A9F6A, 0xE4B09FF4, 0x6A3F9817, 0xA6959889, + 0x7FB58A25, 0xB31F8ABB, 0x3D908D58, 0xF13A8DC6, + 0xFBFF84DF, 0x37558441, 0xB9DA83A2, 0x7570833C, + 0x533B85DA, 0x9F918544, 0x111E82A7, 0xDDB48239, + 0xD7718B20, 0x1BDB8BBE, 0x95548C5D, 0x59FE8CC3, + 0x80DE9E6F, 0x4C749EF1, 0xC2FB9912, 0x0E51998C, + 0x04949095, 0xC83E900B, 0x46B197E8, 0x8A1B9776, + 0x2F80B4F1, 0xE32AB46F, 0x6DA5B38C, 0xA10FB312, + 0xABCABA0B, 0x6760BA95, 0xE9EFBD76, 0x2545BDE8, + 0xFC65AF44, 0x30CFAFDA, 0xBE40A839, 0x72EAA8A7, + 0x782FA1BE, 0xB485A120, 0x3A0AA6C3, 0xF6A0A65D, + 0xAA4DE78C, 0x66E7E712, 0xE868E0F1, 0x24C2E06F, + 0x2E07E976, 0xE2ADE9E8, 0x6C22EE0B, 0xA088EE95, + 0x79A8FC39, 0xB502FCA7, 0x3B8DFB44, 0xF727FBDA, + 0xFDE2F2C3, 0x3148F25D, 0xBFC7F5BE, 0x736DF520, + 0xD6F6D6A7, 0x1A5CD639, 0x94D3D1DA, 0x5879D144, + 0x52BCD85D, 0x9E16D8C3, 0x1099DF20, 0xDC33DFBE, + 0x0513CD12, 0xC9B9CD8C, 0x4736CA6F, 0x8B9CCAF1, + 0x8159C3E8, 0x4DF3C376, 0xC37CC495, 0x0FD6C40B, + 0x7AA64737, 0xB60C47A9, 0x3883404A, 0xF42940D4, + 0xFEEC49CD, 0x32464953, 0xBCC94EB0, 0x70634E2E, + 0xA9435C82, 0x65E95C1C, 0xEB665BFF, 0x27CC5B61, + 0x2D095278, 0xE1A352E6, 0x6F2C5505, 0xA386559B, + 0x061D761C, 0xCAB77682, 0x44387161, 0x889271FF, + 0x825778E6, 0x4EFD7878, 0xC0727F9B, 0x0CD87F05, + 0xD5F86DA9, 0x19526D37, 0x97DD6AD4, 0x5B776A4A, + 0x51B26353, 0x9D1863CD, 0x1397642E, 0xDF3D64B0, + 0x83D02561, 0x4F7A25FF, 0xC1F5221C, 0x0D5F2282, + 0x079A2B9B, 0xCB302B05, 0x45BF2CE6, 0x89152C78, + 0x50353ED4, 0x9C9F3E4A, 0x121039A9, 0xDEBA3937, + 0xD47F302E, 0x18D530B0, 0x965A3753, 0x5AF037CD, + 0xFF6B144A, 0x33C114D4, 0xBD4E1337, 0x71E413A9, + 0x7B211AB0, 0xB78B1A2E, 0x39041DCD, 0xF5AE1D53, + 0x2C8E0FFF, 0xE0240F61, 0x6EAB0882, 0xA201081C, + 0xA8C40105, 0x646E019B, 0xEAE10678, 0x264B06E6 + }; + /** + * the current CRC value, bit-flipped + */ + private int crc; + + private SdkCrc32Checksum() { + reset(); + } + + + private SdkCrc32Checksum(int crc) { + this.crc = crc; + + } + + public static SdkCrc32Checksum create() { + return new SdkCrc32Checksum(); + } + + @Override + public long getValue() { + return ~crc & 0xffffffffL; + } + + @Override + public void reset() { + crc = 0xffffffff; + } + + @Override + public void update(byte[] b, int offset, int len) { + int localCrc = crc; + + int remainder = len & 0x7; + int i = offset; + for (int end = offset + len - remainder; i < end; i += 8) { + int x = localCrc ^ + (((b[i] << 24) >>> 24) + ((b[i + 1] << 24) >>> 16) + + ((b[i + 2] << 24) >>> 8) + (b[i + 3] << 24)); + + localCrc = T[((x << 24) >>> 24) + 0x700] ^ T[((x << 16) >>> 24) + 0x600] ^ + T[((x << 8) >>> 24) + 0x500] ^ T[(x >>> 24) + 0x400] ^ + T[((b[i + 4] << 24) >>> 24) + 0x300] ^ T[((b[i + 5] << 24) >>> 24) + 0x200] ^ + T[((b[i + 6] << 24) >>> 24) + 0x100] ^ T[(b[i + 7] << 24) >>> 24]; + } + + /* loop unroll - duff's device style */ + + for (int index = 0; index < remainder; index++) { + localCrc = (localCrc >>> 8) ^ T[((localCrc ^ b[i]) << 24) >>> 24]; + i++; + } + + // Publish crc out to object + crc = localCrc; + } + + @Override + public void update(int b) { + crc = (crc >>> 8) ^ T[((crc ^ b) << 24) >>> 24]; + } + + @Override + public SdkCrc32Checksum clone() { + return new SdkCrc32Checksum(crc); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Sha1Checksum.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Sha1Checksum.java new file mode 100644 index 000000000000..8229140905ea --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Sha1Checksum.java @@ -0,0 +1,84 @@ +/* + * 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.http.auth.aws.internal.signer.checksums; + +import java.security.MessageDigest; +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * Implementation of {@link SdkChecksum} to calculate an Sha-1 checksum. + */ +@SdkInternalApi +public class Sha1Checksum implements SdkChecksum { + + private MessageDigest digest; + + private MessageDigest digestLastMarked; + + public Sha1Checksum() { + this.digest = getDigest(); + } + + @Override + public void update(int b) { + digest.update((byte) b); + } + + @Override + public void update(byte[] b, int off, int len) { + digest.update(b, off, len); + } + + @Override + public long getValue() { + throw new UnsupportedOperationException("Use getChecksumBytes() instead."); + } + + @Override + public void reset() { + digest = (digestLastMarked == null) + // This is necessary so that should there be a reset without a + // preceding mark, the Sha-1 would still be computed correctly. + ? getDigest() + : cloneFrom(digestLastMarked); + } + + private MessageDigest getDigest() { + try { + return MessageDigest.getInstance("SHA-1"); + } catch (Exception e) { + throw new IllegalStateException("Unexpected error creating SHA-1 checksum", e); + } + } + + @Override + public byte[] getChecksumBytes() { + return digest.digest(); + } + + @Override + public void mark(int readLimit) { + digestLastMarked = cloneFrom(digest); + } + + private MessageDigest cloneFrom(MessageDigest from) { + try { + return (MessageDigest) from.clone(); + } catch (CloneNotSupportedException e) { // should never occur + throw new IllegalStateException("unexpected", e); + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Sha256Checksum.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Sha256Checksum.java new file mode 100644 index 000000000000..f46b28b584b2 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/checksums/Sha256Checksum.java @@ -0,0 +1,84 @@ +/* + * 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.http.auth.aws.internal.signer.checksums; + +import java.security.MessageDigest; +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * Implementation of {@link SdkChecksum} to calculate an Sha-256 Checksum. + */ +@SdkInternalApi +public class Sha256Checksum implements SdkChecksum { + + private MessageDigest digest; + + private MessageDigest digestLastMarked; + + public Sha256Checksum() { + this.digest = getDigest(); + } + + @Override + public void update(int b) { + digest.update((byte) b); + } + + @Override + public void update(byte[] b, int off, int len) { + digest.update(b, off, len); + } + + @Override + public long getValue() { + throw new UnsupportedOperationException("Use getChecksumBytes() instead."); + } + + @Override + public void reset() { + digest = (digestLastMarked == null) + // This is necessary so that should there be a reset without a + // preceding mark, the Sha-256 would still be computed correctly. + ? getDigest() + : cloneFrom(digestLastMarked); + } + + private MessageDigest getDigest() { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (Exception e) { + throw new IllegalStateException("Unexpected error creating SHA-256 checksum", e); + } + } + + @Override + public byte[] getChecksumBytes() { + return digest.digest(); + } + + @Override + public void mark(int readLimit) { + digestLastMarked = cloneFrom(digest); + } + + private MessageDigest cloneFrom(MessageDigest from) { + try { + return (MessageDigest) from.clone(); + } catch (CloneNotSupportedException e) { // should never occur + throw new IllegalStateException("unexpected", e); + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChecksumTrailerProvider.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChecksumTrailerProvider.java new file mode 100644 index 000000000000..b4a36bb0d931 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChecksumTrailerProvider.java @@ -0,0 +1,48 @@ +/* + * 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.http.auth.aws.internal.signer.chunkedencoding; + +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.SdkChecksum; +import software.amazon.awssdk.utils.BinaryUtils; +import software.amazon.awssdk.utils.Pair; + +@SdkInternalApi +public class ChecksumTrailerProvider implements TrailerProvider { + + private final SdkChecksum checksum; + private final String checksumName; + + public ChecksumTrailerProvider(SdkChecksum checksum, String checksumName) { + this.checksum = checksum; + this.checksumName = checksumName; + } + + @Override + public void reset() { + checksum.reset(); + } + + @Override + public Pair> get() { + return Pair.of( + checksumName, + Collections.singletonList(BinaryUtils.toBase64(checksum.getChecksumBytes())) + ); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/Chunk.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/Chunk.java new file mode 100644 index 000000000000..06ff33bc6073 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/Chunk.java @@ -0,0 +1,44 @@ +/* + * 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.http.auth.aws.internal.signer.chunkedencoding; + +import java.io.InputStream; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.utils.SdkAutoCloseable; + +/** + * An interface which defines a "chunk" of data. + */ +@SdkInternalApi +public interface Chunk extends SdkAutoCloseable { + /** + * Get a default implementation of a chunk, which wraps a stream with a fixed size; + */ + static Chunk create(InputStream data, int sizeInBytes) { + return new DefaultChunk(new ChunkInputStream(data, sizeInBytes)); + } + + /** + * Get the underlying stream of data for a chunk. + */ + InputStream stream(); + + /** + * Whether the logical end of a chunk has been reached. + */ + boolean hasRemaining(); + +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkExtensionProvider.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkExtensionProvider.java new file mode 100644 index 000000000000..bb434c64ba53 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkExtensionProvider.java @@ -0,0 +1,36 @@ +/* + * 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.http.auth.aws.internal.signer.chunkedencoding; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.utils.Pair; + +/** + * A functional interface for defining an extension of a chunk, where the extension is a key-value pair. + *

+ * An extension usually depends on the chunk-data itself (checksum, signature, etc.), but is not required to. Per RFC-7230 The chunk-extension is defined as: + *

+ *     chunk-ext      = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
+ *     chunk-ext-name = token
+ *     chunk-ext-val  = token / quoted-string
+ * 
+ */ +@FunctionalInterface +@SdkInternalApi +public interface ChunkExtensionProvider extends Resettable { + Pair get(byte[] chunk); +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkHeaderProvider.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkHeaderProvider.java new file mode 100644 index 000000000000..2c6f95c7f1aa --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkHeaderProvider.java @@ -0,0 +1,31 @@ +/* + * 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.http.auth.aws.internal.signer.chunkedencoding; + +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * A functional interface for defining a header of a chunk. + *

+ * The header usually depends on the chunk-data itself (hex-size), but is not required to. In RFC-7230, the chunk-header is specifically the + * {@code chunk-size}, but this interface can give us greater flexibility. + */ +@FunctionalInterface +@SdkInternalApi +public interface ChunkHeaderProvider extends Resettable { + byte[] get(byte[] chunk); +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkInputStream.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkInputStream.java new file mode 100644 index 000000000000..9a2a565a4776 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkInputStream.java @@ -0,0 +1,45 @@ +/* + * 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.http.auth.aws.internal.signer.chunkedencoding; + +import java.io.IOException; +import java.io.InputStream; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.internal.signer.io.SdkLengthAwareInputStream; + +/** + * A wrapped stream to represent a "chunk" of data + */ +@SdkInternalApi +public final class ChunkInputStream extends SdkLengthAwareInputStream { + + public ChunkInputStream(InputStream inputStream, long length) { + super(inputStream, length); + } + + @Override + public void close() throws IOException { + // Drain this chunk on close, so the stream is left at the end of the chunk. + long remaining = remaining(); + if (remaining > 0) { + if (skip(remaining) < remaining) { + throw new IOException("Unable to drain stream for chunk. The underlying stream did not allow skipping the " + + "whole chunk."); + } + } + super.close(); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkedEncodedInputStream.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkedEncodedInputStream.java new file mode 100644 index 000000000000..6f800d6f8ece --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkedEncodedInputStream.java @@ -0,0 +1,262 @@ +/* + * 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.http.auth.aws.internal.signer.chunkedencoding; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.utils.Logger; +import software.amazon.awssdk.utils.Pair; +import software.amazon.awssdk.utils.Validate; + + +/** + * An implementation of chunk-transfer encoding, but by wrapping an {@link InputStream}. This implementation supports + * chunk-headers, chunk-extensions, and trailers. + *

+ * Per RFC-7230, a chunk-transfer encoded message is + * defined as: + *

+ *     chunked-body   = *chunk
+ *                      last-chunk
+ *                      trailer-part
+ *                      CRLF
+ *     chunk          = chunk-size [ chunk-ext ] CRLF
+ *                      chunk-data CRLF
+ *     chunk-size     = 1*HEXDIG
+ *     last-chunk     = 1*("0") [ chunk-ext ] CRLF
+ *     chunk-data     = 1*OCTET ; a sequence of chunk-size octets
+ * 
+ */ +@SdkInternalApi +public final class ChunkedEncodedInputStream extends InputStream { + private static final Logger LOG = Logger.loggerFor(ChunkedEncodedInputStream.class); + private static final byte[] CRLF = {'\r', '\n'}; + private static final byte[] END = {}; + + private final InputStream inputStream; + private final int chunkSize; + + private final ChunkHeaderProvider header; + private final List extensions = new ArrayList<>(); + private final List trailers = new ArrayList<>(); + + private Chunk currentChunk; + private boolean isFinished = false; + + private ChunkedEncodedInputStream(Builder builder) { + this.inputStream = Validate.notNull(builder.inputStream, "Input-Stream cannot be null!"); + this.chunkSize = Validate.isPositive(builder.chunkSize, "Chunk-size must be greater than 0!"); + this.header = Validate.notNull(builder.header, "Header cannot be null!"); + this.extensions.addAll(Validate.notNull(builder.extensions, "Extensions cannot be null!")); + this.trailers.addAll(Validate.notNull(builder.trailers, "Trailers cannot be null!")); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public int read() throws IOException { + if (currentChunk == null || !currentChunk.hasRemaining() && !isFinished) { + currentChunk = getChunk(inputStream); + } + + return currentChunk.stream().read(); + } + + /** + * Get an encoded chunk from the input-stream, or the final chunk if we've reached the end of the input-stream. + */ + private Chunk getChunk(InputStream stream) throws IOException { + LOG.debug(() -> "Reading next chunk."); + if (currentChunk != null) { + currentChunk.close(); + } + // we *have* to read from the backing stream in order to figure out if it's the end or not + // TODO(sra-identity-and-auth): We can likely optimize this by not copying the entire chunk of data into memory + byte[] chunkData = new byte[chunkSize]; + int read = read(stream, chunkData, chunkSize); + + if (read > 0) { + // set the current chunk to the newly written chunk + return getNextChunk(Arrays.copyOf(chunkData, read)); + } + + LOG.debug(() -> "End of backing stream reached. Reading final chunk."); + isFinished = true; + // set the current chunk to the written final chunk + return getFinalChunk(); + } + + /** + * Read from an input-stream, up to a max number of bytes, storing them in a byte-array. The actual number of bytes can be + * less than the max in the event that we reach the end of the stream. + *

+ * This method is necessary because we cannot assume the backing stream uses the default implementation of + * {@code read(byte b[], int off, int len)} + */ + private int read(InputStream inputStream, byte[] buf, int maxBytesToRead) throws IOException { + int read; + int offset = 0; + do { + read = inputStream.read(buf, offset, maxBytesToRead - offset); + assert read != 0; + if (read > 0) { + offset += read; + } + } while (read > 0 && offset < maxBytesToRead); + + return offset; + } + + /** + * Create a chunk from a byte-array, which includes the header, the extensions, and the chunk data. The input array should be + * correctly sized, i.e. the number of bytes should equal its length. + */ + private Chunk getNextChunk(byte[] data) throws IOException { + ByteArrayOutputStream chunkStream = new ByteArrayOutputStream(); + writeChunk(data, chunkStream); + chunkStream.write(CRLF); + byte[] newChunkData = chunkStream.toByteArray(); + + return Chunk.create(new ByteArrayInputStream(newChunkData), newChunkData.length); + } + + /** + * Create the final chunk, which includes the header, the extensions, the chunk (if applicable), and the trailer + */ + private Chunk getFinalChunk() throws IOException { + ByteArrayOutputStream chunkStream = new ByteArrayOutputStream(); + writeChunk(END, chunkStream); + writeTrailers(chunkStream); + chunkStream.write(CRLF); + byte[] newChunkData = chunkStream.toByteArray(); + + return Chunk.create(new ByteArrayInputStream(newChunkData), newChunkData.length); + } + + private void writeChunk(byte[] chunk, ByteArrayOutputStream outputStream) throws IOException { + writeHeader(chunk, outputStream); + writeExtensions(chunk, outputStream); + outputStream.write(CRLF); + outputStream.write(chunk); + } + + private void writeHeader(byte[] chunk, ByteArrayOutputStream outputStream) throws IOException { + byte[] hdr = header.get(chunk); + outputStream.write(hdr); + } + + private void writeExtensions(byte[] chunk, ByteArrayOutputStream outputStream) throws IOException { + for (ChunkExtensionProvider chunkExtensionProvider : extensions) { + Pair ext = chunkExtensionProvider.get(chunk); + outputStream.write((byte) ';'); + outputStream.write(ext.left()); + outputStream.write((byte) '='); + outputStream.write(ext.right()); + } + } + + private void writeTrailers(ByteArrayOutputStream outputStream) throws IOException { + for (TrailerProvider trailer : trailers) { + Pair> tlr = trailer.get(); + outputStream.write(tlr.left().getBytes(StandardCharsets.UTF_8)); + outputStream.write((byte) ':'); + outputStream.write(String.join(",", tlr.right()).getBytes(StandardCharsets.UTF_8)); + outputStream.write(CRLF); + } + } + + @Override + public void reset() throws IOException { + trailers.forEach(TrailerProvider::reset); + extensions.forEach(ChunkExtensionProvider::reset); + header.reset(); + inputStream.reset(); + isFinished = false; + currentChunk = null; + } + + @Override + public void close() throws IOException { + inputStream.close(); + } + + public static class Builder { + private final List extensions = new ArrayList<>(); + private final List trailers = new ArrayList<>(); + private InputStream inputStream; + private int chunkSize; + private ChunkHeaderProvider header = chunk -> Integer.toHexString(chunk.length).getBytes(StandardCharsets.UTF_8); + + public InputStream inputStream() { + return this.inputStream; + } + + public Builder inputStream(InputStream inputStream) { + this.inputStream = inputStream; + return this; + } + + public Builder chunkSize(int chunkSize) { + this.chunkSize = chunkSize; + return this; + } + + public Builder header(ChunkHeaderProvider header) { + this.header = header; + return this; + } + + public Builder extensions(List extensions) { + this.extensions.clear(); + extensions.forEach(this::addExtension); + return this; + } + + public Builder addExtension(ChunkExtensionProvider extension) { + this.extensions.add(Validate.notNull(extension, "ExtensionProvider cannot be null!")); + return this; + } + + public List trailers() { + return new ArrayList<>(trailers); + } + + public Builder trailers(List trailers) { + this.trailers.clear(); + trailers.forEach(this::addTrailer); + return this; + } + + public Builder addTrailer(TrailerProvider trailer) { + this.trailers.add(Validate.notNull(trailer, "TrailerProvider cannot be null!")); + return this; + } + + public ChunkedEncodedInputStream build() { + return new ChunkedEncodedInputStream(this); + } + } +} + diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/DefaultChunk.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/DefaultChunk.java new file mode 100644 index 000000000000..aaea0fae5ca8 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/DefaultChunk.java @@ -0,0 +1,48 @@ +/* + * 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.http.auth.aws.internal.signer.chunkedencoding; + +import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; + +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * An implementation of a chunk, backed by a {@link ChunkInputStream}. This allows it to have awareness of its length and + * determine the endedness of the chunk. + */ +@SdkInternalApi +final class DefaultChunk implements Chunk { + private final ChunkInputStream data; + + DefaultChunk(ChunkInputStream data) { + this.data = data; + } + + @Override + public boolean hasRemaining() { + return data.remaining() > 0; + } + + @Override + public ChunkInputStream stream() { + return data; + } + + @Override + public void close() { + invokeSafely(data::close); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/Resettable.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/Resettable.java new file mode 100644 index 000000000000..76b57f0804d7 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/Resettable.java @@ -0,0 +1,24 @@ +/* + * 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.http.auth.aws.internal.signer.chunkedencoding; + +import software.amazon.awssdk.annotations.SdkInternalApi; + +@SdkInternalApi +public interface Resettable { + default void reset() { + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/SigV4ChunkExtensionProvider.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/SigV4ChunkExtensionProvider.java new file mode 100644 index 000000000000..02e02fb99546 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/SigV4ChunkExtensionProvider.java @@ -0,0 +1,65 @@ +/* + * 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.http.auth.aws.internal.signer.chunkedencoding; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.hash; +import static software.amazon.awssdk.utils.BinaryUtils.toHex; + +import java.nio.charset.StandardCharsets; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.http.auth.aws.internal.signer.RollingSigner; +import software.amazon.awssdk.utils.Pair; + +@SdkInternalApi +public class SigV4ChunkExtensionProvider implements ChunkExtensionProvider { + + private static final String EMPTY_HASH = toHex(hash("")); + + private final RollingSigner signer; + private final CredentialScope credentialScope; + + public SigV4ChunkExtensionProvider(RollingSigner signer, CredentialScope credentialScope) { + this.signer = signer; + this.credentialScope = credentialScope; + } + + @Override + public void reset() { + signer.reset(); + } + + private String getStringToSign(String previousSignature, byte[] chunk) { + // build the string-to-sign template for the rolling-signer to sign + return String.join("\n", + "AWS4-HMAC-SHA256-PAYLOAD", + credentialScope.getDatetime(), + credentialScope.scope(), + previousSignature, + EMPTY_HASH, + toHex(hash(chunk)) + ); + } + + @Override + public Pair get(byte[] chunk) { + String chunkSig = signer.sign(previousSig -> getStringToSign(previousSig, chunk)); + return Pair.of( + "chunk-signature".getBytes(StandardCharsets.UTF_8), + chunkSig.getBytes(StandardCharsets.UTF_8) + ); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/SigV4TrailerProvider.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/SigV4TrailerProvider.java new file mode 100644 index 000000000000..3d27cc1768be --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/SigV4TrailerProvider.java @@ -0,0 +1,83 @@ +/* + * 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.http.auth.aws.internal.signer.chunkedencoding; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.V4CanonicalRequest.getCanonicalHeaders; +import static software.amazon.awssdk.http.auth.aws.internal.signer.V4CanonicalRequest.getCanonicalHeadersString; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.hash; +import static software.amazon.awssdk.utils.BinaryUtils.toHex; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.http.auth.aws.internal.signer.RollingSigner; +import software.amazon.awssdk.utils.Pair; + +@SdkInternalApi +public class SigV4TrailerProvider implements TrailerProvider { + + private final List trailerProviders = new ArrayList<>(); + private final RollingSigner signer; + private final CredentialScope credentialScope; + + public SigV4TrailerProvider(List trailerProviders, RollingSigner signer, CredentialScope credentialScope) { + this.trailerProviders.addAll(trailerProviders); + this.signer = signer; + this.credentialScope = credentialScope; + } + + @Override + public void reset() { + trailerProviders.forEach(TrailerProvider::reset); + signer.reset(); + } + + @Override + public Pair> get() { + String trailerSig = signer.sign(this::getTrailersStringToSign); + return Pair.of( + "x-amz-trailer-signature", + Collections.singletonList(trailerSig) + ); + } + + private String getTrailersStringToSign(String previousSignature) { + // Get the headers by calling get() on each of the trailers + Map> headers = + trailerProviders.stream().map(TrailerProvider::get).collect( + Collectors.toMap( + Pair::left, + Pair::right + ) + ); + + String canonicalHeadersString = getCanonicalHeadersString(getCanonicalHeaders(headers)); + String canonicalHashHex = toHex(hash(canonicalHeadersString)); + + // build the string-to-sign template for the rolling-signer to sign + return String.join("\n", + "AWS4-HMAC-SHA256-TRAILER", + credentialScope.getDatetime(), + credentialScope.scope(), + previousSignature, + canonicalHashHex + ); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/TrailerProvider.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/TrailerProvider.java new file mode 100644 index 000000000000..3cb61aba55ec --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/TrailerProvider.java @@ -0,0 +1,37 @@ +/* + * 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.http.auth.aws.internal.signer.chunkedencoding; + +import java.util.List; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.utils.Pair; + +/** + * A functional interface for defining a trailer, where the trailer is a header pair. + *

+ * A trailer usually depends on the chunk-data itself (checksum, signature, etc.), but is not required to. Per RFC-7230, the chunked trailer section is defined as: + *

+ *     trailer-part   = *( header-field CRLF )
+ * 
+ * An implementation of this interface is specifically an element of the {@code trailer-part}. Therefore, all occurrences of + * {@code TrailerProvider}'s make up the {@code trailer-part}. + */ +@FunctionalInterface +@SdkInternalApi +public interface TrailerProvider extends Resettable { + Pair> get(); +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumInputStream.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumInputStream.java new file mode 100644 index 000000000000..bef90d2776cd --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumInputStream.java @@ -0,0 +1,60 @@ +/* + * 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.http.auth.aws.internal.signer.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.zip.Checksum; +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * An input-stream that takes a collection of checksums, and updates each checksum when it reads data. + */ +@SdkInternalApi +public class ChecksumInputStream extends FilterInputStream { + + private final Collection checksums = new ArrayList<>(); + + public ChecksumInputStream(InputStream stream, Collection checksums) { + super(stream); + this.checksums.addAll(checksums); + } + + @Override + public int read() throws IOException { + byte[] b = new byte[1]; + int read = read(b, 0, 1); + if (read > 0) { + checksums.forEach(checksum -> checksum.update(b, 0, 1)); + } + + return read; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int read = in.read(b, off, len); + if (read > 0) { + checksums.forEach(checksum -> checksum.update(b, off, read)); + } + + return read; + } +} + diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java new file mode 100644 index 000000000000..a625a4b02d82 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java @@ -0,0 +1,95 @@ +/* + * 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.http.auth.aws.internal.signer.io; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.zip.Checksum; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * A subscriber that takes a collection of checksums, and updates each checksum when it receives data. + */ +@SdkInternalApi +public final class ChecksumSubscriber implements Subscriber { + private final CompletableFuture checksumming = new CompletableFuture<>(); + private final Collection checksums = new ArrayList<>(); + private volatile boolean canceled = false; + private volatile Subscription subscription; + + public ChecksumSubscriber(Collection consumers) { + this.checksums.addAll(consumers); + + checksumming.whenComplete((r, t) -> { + if (t instanceof CancellationException) { + synchronized (this) { + canceled = true; + if (subscription != null) { + subscription.cancel(); + } + } + } + }); + } + + @Override + public void onSubscribe(Subscription subscription) { + synchronized (this) { + if (!canceled && this.subscription == null) { + this.subscription = subscription; + subscription.request(Long.MAX_VALUE); + } else { + subscription.cancel(); + } + } + } + + @Override + public void onNext(ByteBuffer byteBuffer) { + if (!canceled) { + byte[] buf; + + if (byteBuffer.hasArray()) { + buf = byteBuffer.array(); + } else { + buf = new byte[byteBuffer.remaining()]; + byteBuffer.get(buf); + } + + // We have to use a byte[], since update() is java 9+ + checksums.forEach(checksum -> checksum.update(buf, 0, buf.length)); + } + } + + @Override + public void onError(Throwable throwable) { + checksumming.completeExceptionally(throwable); + } + + @Override + public void onComplete() { + checksumming.complete(null); + } + + public CompletableFuture checksum() { + return checksumming; + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/Releasable.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/Releasable.java new file mode 100644 index 000000000000..147be9d9fc6d --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/Releasable.java @@ -0,0 +1,70 @@ +/* + * 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.http.auth.aws.internal.signer.io; + +import static software.amazon.awssdk.utils.IoUtils.closeQuietly; + +import java.io.Closeable; +import org.slf4j.Logger; +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * Used for releasing a resource. + *

+ * For example, the creation of a {@code ResettableInputStream} would entail physically opening a file. If the opened file is + * meant to be closed only (in a finally block) by the very same code block that created it, then it is necessary that the release + * method must not be called while the execution is made in other stack frames. + *

+ * In such case, as other stack frames may inadvertently or indirectly call the close method of the stream, the creator of the + * stream would need to explicitly disable the accidental closing via {@code ResettableInputStream#disableClose()}, so that the + * release method becomes the only way to truly close the opened file. + */ +@SdkInternalApi +public interface Releasable { + /** + * Releases the given {@link Closeable} especially if it was an instance of {@link Releasable}. + *

+ * For example, the creation of a {@code ResettableInputStream} would entail physically opening a file. If the opened file is + * meant to be closed only (in a finally block) by the very same code block that created it, then it is necessary that the + * release method must not be called while the execution is made in other stack frames. + *

+ * In such case, as other stack frames may inadvertently or indirectly call the close method of the stream, the creator of the + * stream would need to explicitly disable the accidental closing via {@code ResettableInputStream#disableClose()}, so that + * the release method becomes the only way to truly close the opened file. + */ + static void release(Closeable is, Logger log) { + closeQuietly(is, log); + if (is instanceof Releasable) { + Releasable r = (Releasable) is; + r.release(); + } + } + + /** + * Releases the allocated resource. This method should not be called except by the caller who allocated the resource at the + * very top of the call stack. This allows, typically, a {@link Closeable} resource to be not unintentionally released owing + * to the calling of the {@link Closeable#close()} methods by implementation deep down in the call stack. + *

+ * For example, the creation of a {@code ResettableInputStream} would entail physically opening a file. If the opened file is + * meant to be closed only (in a finally block) by the very same code block that created it, then it is necessary that the + * release method must not be called while the execution is made in other stack frames. + *

+ * In such case, as other stack frames may inadvertently or indirectly call the close method of the stream, the creator of the + * stream would need to explicitly disable the accidental closing via {@code ResettableInputStream#disableClose()}, so that + * the release method becomes the only way to truly close the opened file. + */ + void release(); +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ResettableContentStreamProvider.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ResettableContentStreamProvider.java new file mode 100644 index 000000000000..2afa9420c62b --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ResettableContentStreamProvider.java @@ -0,0 +1,50 @@ +/* + * 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.http.auth.aws.internal.signer.io; + +import java.io.IOException; +import java.io.InputStream; +import java.util.function.Supplier; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.ContentStreamProvider; + +@SdkInternalApi +public class ResettableContentStreamProvider implements ContentStreamProvider { + private final Supplier streamSupplier; + private InputStream currentStream; + + public ResettableContentStreamProvider(Supplier streamSupplier) { + this.streamSupplier = streamSupplier; + } + + @Override + public InputStream newStream() { + try { + reset(); + } catch (IOException e) { + throw new RuntimeException("Could not create new stream: ", e); + } + return currentStream; + } + + private void reset() throws IOException { + if (currentStream != null) { + currentStream.reset(); + } else { + currentStream = streamSupplier.get(); + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/SdkLengthAwareInputStream.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/SdkLengthAwareInputStream.java new file mode 100644 index 000000000000..b7ec9ad4442b --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/SdkLengthAwareInputStream.java @@ -0,0 +1,108 @@ +/* + * 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.http.auth.aws.internal.signer.io; + +import static software.amazon.awssdk.utils.NumericUtils.saturatedCast; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.utils.Logger; +import software.amazon.awssdk.utils.Validate; + +/** + * An {@code InputStream} that is aware of its length. The main purpose of this class is to support truncating streams to a length + * that is shorter than the total length of the stream. + */ +@SdkInternalApi +public class SdkLengthAwareInputStream extends FilterInputStream { + private static final Logger LOG = Logger.loggerFor(SdkLengthAwareInputStream.class); + private long length; + private long remaining; + + public SdkLengthAwareInputStream(InputStream in, long length) { + super(in); + this.length = Validate.isNotNegative(length, "length"); + this.remaining = this.length; + } + + @Override + public int read() throws IOException { + if (!hasMoreBytes()) { + LOG.debug(() -> String.format("Specified InputStream length of %d has been reached. Returning EOF.", length)); + return -1; + } + + int read = super.read(); + if (read != -1) { + remaining--; + } + return read; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (!hasMoreBytes()) { + LOG.debug(() -> String.format("Specified InputStream length of %d has been reached. Returning EOF.", length)); + return -1; + } + + len = Math.min(len, saturatedCast(remaining)); + int read = super.read(b, off, len); + if (read > 0) { + remaining -= read; + } + + return read; + } + + @Override + public long skip(long requestedBytesToSkip) throws IOException { + requestedBytesToSkip = Math.min(requestedBytesToSkip, remaining); + long skippedActual = super.skip(requestedBytesToSkip); + remaining -= skippedActual; + return skippedActual; + } + + @Override + public int available() throws IOException { + int streamAvailable = super.available(); + return Math.min(streamAvailable, saturatedCast(remaining)); + } + + @Override + public void mark(int readlimit) { + super.mark(readlimit); + // mark() causes reset() to change the stream's position back to the current position. Therefore, when reset() is called, + // the new length of the stream will be equal to the current value of 'remaining'. + length = remaining; + } + + @Override + public void reset() throws IOException { + super.reset(); + remaining = length; + } + + public long remaining() { + return remaining; + } + + private boolean hasMoreBytes() { + return remaining > 0; + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/BoundedLinkedHashMap.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/BoundedLinkedHashMap.java new file mode 100644 index 000000000000..720c3f51d163 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/BoundedLinkedHashMap.java @@ -0,0 +1,50 @@ +/* + * 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.http.auth.aws.internal.signer.util; + +import java.util.LinkedHashMap; +import java.util.Map; +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * A bounded linked hash map that would remove the eldest entry when the map size exceeds a configurable maximum. + */ +@SdkInternalApi +final class BoundedLinkedHashMap extends LinkedHashMap { + private static final long serialVersionUID = 1L; + private final int maxSize; + + BoundedLinkedHashMap(int maxSize) { + this.maxSize = maxSize; + } + + /** + * {@inheritDoc} + *

+ * Returns true if the size of this map exceeds the maximum. + */ + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > maxSize; + } + + /** + * Returns the maximum size of this map beyond which the eldest entry will get removed. + */ + int getMaxSize() { + return maxSize; + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/ChecksumUtil.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/ChecksumUtil.java new file mode 100644 index 000000000000..a5299e04849b --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/ChecksumUtil.java @@ -0,0 +1,113 @@ +/* + * 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.http.auth.aws.internal.signer.util; + +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.CRC32; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.CRC32C; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.MD5; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.SHA1; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.SHA256; + +import java.io.InputStream; +import java.util.Locale; +import java.util.Map; +import java.util.function.Supplier; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.ConstantChecksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Crc32CChecksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Crc32Checksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Md5Checksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.SdkChecksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Sha1Checksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Sha256Checksum; +import software.amazon.awssdk.utils.ImmutableMap; + +@SdkInternalApi +public final class ChecksumUtil { + + private static final String CONSTANT_CHECKSUM = "CONSTANT"; + + private static final Map> CHECKSUM_MAP = ImmutableMap.of( + SHA256.algorithmId(), Sha256Checksum::new, + SHA1.algorithmId(), Sha1Checksum::new, + CRC32.algorithmId(), Crc32Checksum::new, + CRC32C.algorithmId(), Crc32CChecksum::new, + MD5.algorithmId(), Md5Checksum::new + ); + + private ChecksumUtil() { + } + + /** + * Get the correct checksum header name based on the checksum-algorithm. This is required to be of the form + * {@code x-amz-checksum-*}, where '*' is alphanumeric checksum-algorithm-id in lower-case form. Examples include: + *

+ * x-amz-checksum-sha256, x-amz-checksum-sha1, x-amz-checksum-crc32, x-amz-checksum-crc32c, x-amz-checksum-md5 + *

+ */ + public static String checksumHeaderName(ChecksumAlgorithm checksumAlgorithm) { + return "x-amz-checksum-" + checksumAlgorithm.algorithmId().toLowerCase(Locale.US); + } + + /** + * Gets the SdkChecksum object based on the given ChecksumAlgorithm. + */ + public static SdkChecksum fromChecksumAlgorithm(ChecksumAlgorithm checksumAlgorithm) { + if (CHECKSUM_MAP.containsKey(checksumAlgorithm.algorithmId())) { + return CHECKSUM_MAP.get(checksumAlgorithm.algorithmId()).get(); + } + + if (CONSTANT_CHECKSUM.equals(checksumAlgorithm.algorithmId())) { + return new ConstantChecksum(((ConstantChecksumAlgorithm) checksumAlgorithm).value); + } + + throw new UnsupportedOperationException("Checksum not supported for " + checksumAlgorithm.algorithmId()); + } + + /** + * Read the entirety of an input-stream - this is useful when the stream has side-effects (such as calculating a checksum) + * when it gets read. + */ + public static void readAll(InputStream inputStream) { + try { + byte[] buffer = new byte[4096]; + while (inputStream.read(buffer) > -1) { + } + } catch (Exception e) { + throw new RuntimeException("Could not finish reading stream: ", e); + } + } + + /** + * An implementation of a {@link ChecksumAlgorithm} that will map to {@link ConstantChecksum}, which provides a constant + * checksum. This isn't super useful, but is needed in cases such as signing, where the content-hash (a + * cryptographically-secure "checksum") can be a set of pre-defined values. + */ + public static class ConstantChecksumAlgorithm implements ChecksumAlgorithm { + + private final String value; + + public ConstantChecksumAlgorithm(String value) { + this.value = value; + } + + @Override + public String algorithmId() { + return CONSTANT_CHECKSUM; + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/CredentialUtils.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/CredentialUtils.java new file mode 100644 index 000000000000..9a3da271619a --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/CredentialUtils.java @@ -0,0 +1,58 @@ +/* + * 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.http.auth.aws.internal.signer.util; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; +import software.amazon.awssdk.utils.StringUtils; + +@SdkInternalApi +public final class CredentialUtils { + + private CredentialUtils() { + } + + /** + * Determine whether the provided credentials are anonymous credentials, indicating that the customer is not attempting to + * authenticate themselves. + */ + public static boolean isAnonymous(AwsCredentialsIdentity credentials) { + return credentials.secretAccessKey() == null && credentials.accessKeyId() == null; + } + + /** + * Sanitize given credentials by trimming whitespace + */ + public static AwsCredentialsIdentity sanitizeCredentials(AwsCredentialsIdentity credentials) { + String accessKeyId = StringUtils.trim(credentials.accessKeyId()); + String secretKey = StringUtils.trim(credentials.secretAccessKey()); + + if (credentials instanceof AwsSessionCredentialsIdentity) { + AwsSessionCredentialsIdentity sessionCredentials = (AwsSessionCredentialsIdentity) credentials; + return AwsSessionCredentialsIdentity.create(accessKeyId, + secretKey, + StringUtils.trim(sessionCredentials.sessionToken())); + } + + // given credentials are anonymous, so don't create new instance + if (accessKeyId == null && secretKey == null) { + return credentials; + } + + return AwsCredentialsIdentity.create(accessKeyId, secretKey); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/FifoCache.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/FifoCache.java new file mode 100644 index 000000000000..4ca9ff526f16 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/FifoCache.java @@ -0,0 +1,102 @@ +/* + * 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.http.auth.aws.internal.signer.util; + +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.annotations.ThreadSafe; + +/** + * A bounded cache that has a FIFO eviction policy when the cache is full. + * + * @param value type + */ +@ThreadSafe +@SdkInternalApi +public final class FifoCache { + private final BoundedLinkedHashMap map; + private final ReadLock rlock; + private final WriteLock wlock; + + /** + * @param maxSize the maximum number of entries of the cache + */ + public FifoCache(int maxSize) { + if (maxSize < 1) { + throw new IllegalArgumentException("maxSize " + maxSize + + " must be at least 1"); + } + map = new BoundedLinkedHashMap<>(maxSize); + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + rlock = lock.readLock(); + wlock = lock.writeLock(); + } + + /** + * Adds an entry to the cache, evicting the earliest entry if necessary. + */ + public T add(String key, T value) { + wlock.lock(); + try { + return map.put(key, value); + } finally { + wlock.unlock(); + } + } + + /** + * Returns the value of the given key; or null of no such entry exists. + */ + public T get(String key) { + rlock.lock(); + try { + return map.get(key); + } finally { + rlock.unlock(); + } + } + + /** + * Returns the current size of the cache. + */ + public int size() { + rlock.lock(); + try { + return map.size(); + } finally { + rlock.unlock(); + } + } + + /** + * Returns the maximum size of the cache. + */ + public int getMaxSize() { + return map.getMaxSize(); + } + + @Override + public String toString() { + rlock.lock(); + try { + return map.toString(); + } finally { + rlock.unlock(); + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/OptionalDependencyLoaderUtil.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/OptionalDependencyLoaderUtil.java new file mode 100644 index 000000000000..b0acc3b039aa --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/OptionalDependencyLoaderUtil.java @@ -0,0 +1,80 @@ +/* + * 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.http.auth.aws.internal.signer.util; + +import java.time.Clock; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.crt.internal.signer.DefaultAwsCrtV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.eventstream.internal.signer.EventStreamV4PayloadSigner; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.ClassLoaderHelper; +import software.amazon.awssdk.utils.Logger; + +/** + * Utilities for loading of classes and objects which have optional dependencies, and therefore need to be safely checked at + * runtime in order to use. + */ +@SdkInternalApi +public final class OptionalDependencyLoaderUtil { + private static final Logger LOG = Logger.loggerFor(OptionalDependencyLoaderUtil.class); + + private static final String HTTP_AUTH_AWS_CRT_PATH = + "software.amazon.awssdk.http.auth.aws.crt.HttpAuthAwsCrt"; + private static final String HTTP_AUTH_AWS_CRT_MODULE = "software.amazon.awssdk:http-auth-aws-crt"; + private static final String HTTP_AUTH_AWS_EVENT_STREAM_PATH = + "software.amazon.awssdk.http.auth.aws.eventstream.HttpAuthAwsEventStream"; + private static final String HTTP_AUTH_AWS_EVENT_STREAM_MODULE = "software.amazon.awssdk:http-auth-aws-eventstream"; + + private OptionalDependencyLoaderUtil() { + } + + /** + * A helpful method that checks that some class is available on the class-path. If it fails to load, it will throw an + * exception based on why it failed to load. This should be used in cases where certain dependencies are optional, but the + * dependency is used at compile-time for strong typing (i.e. {@link EventStreamV4PayloadSigner}). + */ + private static void requireClass(String classPath, String module, String feature) { + try { + ClassLoaderHelper.loadClass(classPath, false); + } catch (ClassNotFoundException e) { + LOG.debug(() -> "Cannot find the " + classPath + " class: ", e); + String msg = String.format("Could not load class. You must add a dependency on the '%s' module to enable the %s " + + "feature: ", module, feature); + throw new RuntimeException(msg, e); + } catch (Exception e) { + throw new RuntimeException(String.format("Could not load class (%s): ", classPath), e); + } + } + + public static DefaultAwsCrtV4aHttpSigner getDefaultAwsCrtV4aHttpSigner() { + requireClass(HTTP_AUTH_AWS_CRT_PATH, HTTP_AUTH_AWS_CRT_MODULE, "CRT-V4a signing"); + return new DefaultAwsCrtV4aHttpSigner(); + } + + public static EventStreamV4PayloadSigner getEventStreamV4PayloadSigner( + AwsCredentialsIdentity credentials, + CredentialScope credentialScope, + Clock signingClock) { + + requireClass(HTTP_AUTH_AWS_EVENT_STREAM_PATH, HTTP_AUTH_AWS_EVENT_STREAM_MODULE, "Event-stream signing"); + return EventStreamV4PayloadSigner.builder() + .credentials(credentials) + .credentialScope(credentialScope) + .signingClock(signingClock) + .build(); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerConstant.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerConstant.java new file mode 100644 index 000000000000..771fab2b4db6 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerConstant.java @@ -0,0 +1,81 @@ +/* + * 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.http.auth.aws.internal.signer.util; + +import java.time.Duration; +import software.amazon.awssdk.annotations.SdkInternalApi; + +@SdkInternalApi +public final class SignerConstant { + + public static final String AWS4_TERMINATOR = "aws4_request"; + + public static final String AWS4_SIGNING_ALGORITHM = "AWS4-HMAC-SHA256"; + + public static final String X_AMZ_CONTENT_SHA256 = "x-amz-content-sha256"; + + public static final String AUTHORIZATION = "Authorization"; + + public static final String CONTENT_ENCODING = "Content-Encoding"; + + public static final String X_AMZ_SECURITY_TOKEN = "X-Amz-Security-Token"; + + public static final String X_AMZ_CREDENTIAL = "X-Amz-Credential"; + + public static final String X_AMZ_DATE = "X-Amz-Date"; + + public static final String X_AMZ_EXPIRES = "X-Amz-Expires"; + + public static final String X_AMZ_SIGNED_HEADERS = "X-Amz-SignedHeaders"; + + public static final String X_AMZ_SIGNATURE = "X-Amz-Signature"; + + public static final String X_AMZ_ALGORITHM = "X-Amz-Algorithm"; + + public static final String X_AMZ_DECODED_CONTENT_LENGTH = "x-amz-decoded-content-length"; + + public static final String X_AMZ_TRAILER = "x-amz-trailer"; + + public static final String AWS_CHUNKED = "aws-chunked"; + + public static final String HOST = "Host"; + + public static final String LINE_SEPARATOR = "\n"; + + public static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"; + + public static final String STREAMING_EVENTS_PAYLOAD = "STREAMING-AWS4-HMAC-SHA256-EVENTS"; + + public static final String STREAMING_UNSIGNED_PAYLOAD_TRAILER = "STREAMING-UNSIGNED-PAYLOAD-TRAILER"; + + public static final String STREAMING_ECDSA_SIGNED_PAYLOAD = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD"; + + public static final String STREAMING_ECDSA_SIGNED_PAYLOAD_TRAILER = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER"; + + public static final String STREAMING_SIGNED_PAYLOAD = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"; + + public static final String STREAMING_SIGNED_PAYLOAD_TRAILER = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER"; + + + /** + * Seconds in a week, which is the max expiration time Sig-v4 accepts. + */ + public static final Duration PRESIGN_URL_MAX_EXPIRATION_DURATION = Duration.ofDays(7); + + + private SignerConstant() { + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerKey.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerKey.java new file mode 100644 index 000000000000..44474909f007 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerKey.java @@ -0,0 +1,57 @@ +/* + * 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.http.auth.aws.internal.signer.util; + +import java.time.Instant; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.utils.DateUtils; + +/** + * Holds the signing key and the number of days since epoch for the date for which the signing key was generated. + */ +@Immutable +@SdkInternalApi +public final class SignerKey { + + private final long daysSinceEpoch; + + private final byte[] signingKey; + + public SignerKey(Instant date, byte[] signingKey) { + if (date == null) { + throw new IllegalArgumentException( + "Not able to cache signing key. Signing date to be is null"); + } + if (signingKey == null) { + throw new IllegalArgumentException( + "Not able to cache signing key. Signing Key to be cached are null"); + } + this.daysSinceEpoch = DateUtils.numberOfDaysSinceEpoch(date.toEpochMilli()); + this.signingKey = signingKey.clone(); + } + + public boolean isValidForDate(Instant other) { + return daysSinceEpoch == DateUtils.numberOfDaysSinceEpoch(other.toEpochMilli()); + } + + /** + * Returns a copy of the signing key. + */ + public byte[] getSigningKey() { + return signingKey.clone(); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerUtils.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerUtils.java new file mode 100644 index 000000000000..c85052da3d3a --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerUtils.java @@ -0,0 +1,285 @@ +/* + * 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.http.auth.aws.internal.signer.util; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_CONTENT_SHA256; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_DECODED_CONTENT_LENGTH; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.Header; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.BinaryUtils; +import software.amazon.awssdk.utils.Logger; +import software.amazon.awssdk.utils.http.SdkHttpUtils; + +/** + * Utility methods to be used by various AWS Signer implementations. This class is protected and subject to change. + */ +@SdkInternalApi +public final class SignerUtils { + + private static final Logger LOG = Logger.loggerFor(SignerUtils.class); + + private static final FifoCache SIGNER_CACHE = + new FifoCache<>(300); + + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter + .ofPattern("yyyyMMdd").withZone(ZoneId.of("UTC")); + + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter + .ofPattern("yyyyMMdd'T'HHmmss'Z'").withZone(ZoneId.of("UTC")); + + private SignerUtils() { + } + + /** + * Returns a string representation of the given datetime in yyyyMMdd format. The date returned is in the UTC zone. + *

+ * For example, given an Instant with millis-value of 1416863450581, this method returns "20141124" + */ + public static String formatDate(Instant instant) { + return DATE_FORMATTER.format(instant); + } + + /** + * Returns a string representation of the given datetime in yyyyMMdd'T'HHmmss'Z' format. The date returned is in the UTC + * zone. + *

+ * For example, given an Instant with millis-value of 1416863450581, this method returns "20141124T211050Z" + */ + public static String formatDateTime(Instant instant) { + return TIME_FORMATTER.format(instant); + } + + /** + * Create a hash of the canonical request string + *

+ * Step 2 of the AWS Signature version 4 calculation. Refer to + * https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html#create-canonical-request-hash. + */ + public static String hashCanonicalRequest(String canonicalRequestString) { + return BinaryUtils.toHex( + hash(canonicalRequestString) + ); + } + + /** + * Get the signing key based on the given credentials and a credential-scope + */ + public static byte[] deriveSigningKey(AwsCredentialsIdentity credentials, CredentialScope credentialScope) { + String cacheKey = createSigningCacheKeyName(credentials, credentialScope.getRegion(), credentialScope.getService()); + SignerKey signerKey = SIGNER_CACHE.get(cacheKey); + + if (signerKey != null && signerKey.isValidForDate(credentialScope.getInstant())) { + return signerKey.getSigningKey(); + } + + LOG.trace(() -> "Generating a new signing key as the signing key not available in the cache for the date: " + + credentialScope.getInstant().toEpochMilli()); + byte[] signingKey = newSigningKey(credentials, + credentialScope.getDate(), + credentialScope.getRegion(), + credentialScope.getService()); + SIGNER_CACHE.add(cacheKey, new SignerKey(credentialScope.getInstant(), signingKey)); + return signingKey; + } + + private static String createSigningCacheKeyName(AwsCredentialsIdentity credentials, + String regionName, + String serviceName) { + return credentials.secretAccessKey() + "-" + regionName + "-" + serviceName; + } + + private static byte[] newSigningKey(AwsCredentialsIdentity credentials, + String dateStamp, String regionName, String serviceName) { + byte[] kSecret = ("AWS4" + credentials.secretAccessKey()) + .getBytes(StandardCharsets.UTF_8); + byte[] kDate = sign(dateStamp, kSecret); + byte[] kRegion = sign(regionName, kDate); + byte[] kService = sign(serviceName, kRegion + ); + return sign(SignerConstant.AWS4_TERMINATOR, kService); + } + + /** + * Sign given data using a key. + */ + public static byte[] sign(String stringData, byte[] key) { + try { + byte[] data = stringData.getBytes(StandardCharsets.UTF_8); + return sign(data, key, SigningAlgorithm.HMAC_SHA256); + } catch (Exception e) { + throw new RuntimeException("Unable to calculate a request signature: ", e); + } + } + + /** + * Sign given data using a key and a specific algorithm + */ + public static byte[] sign(byte[] data, byte[] key, SigningAlgorithm algorithm) { + try { + Mac mac = algorithm.getMac(); + mac.init(new SecretKeySpec(key, algorithm.toString())); + return mac.doFinal(data); + } catch (Exception e) { + throw new RuntimeException("Unable to calculate a request signature: ", e); + } + } + + /** + * Compute the signature of a string using a signing key. + *

+ * Step 4 of the AWS Signature version 4 calculation. Refer to + * https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html#calculate-signature. + */ + public static byte[] computeSignature(String stringToSign, byte[] signingKey) { + return sign(stringToSign.getBytes(StandardCharsets.UTF_8), signingKey, + SigningAlgorithm.HMAC_SHA256); + } + + /** + * Add the host header based on parameters of a request + */ + public static void addHostHeader(SdkHttpRequest.Builder requestBuilder) { + // AWS4 requires that we sign the Host header, so we + // have to have it in the request by the time we sign. + + StringBuilder hostHeaderBuilder = new StringBuilder(requestBuilder.host()); + if (!SdkHttpUtils.isUsingStandardPort(requestBuilder.protocol(), requestBuilder.port())) { + hostHeaderBuilder.append(":").append(requestBuilder.port()); + } + + requestBuilder.putHeader(SignerConstant.HOST, hostHeaderBuilder.toString()); + } + + /** + * Add a date header using a datetime string + */ + public static void addDateHeader(SdkHttpRequest.Builder requestBuilder, String dateTime) { + requestBuilder.putHeader(SignerConstant.X_AMZ_DATE, dateTime); + } + + /** + * Move `Content-Length` to `x-amz-decoded-content-length` if not already present. If `Content-Length` is not present, then + * the payload is read in its entirety to calculate the length. + */ + public static long moveContentLength(SdkHttpRequest.Builder request, InputStream payload) { + if (!request.firstMatchingHeader(X_AMZ_DECODED_CONTENT_LENGTH).isPresent()) { + // if the decoded length isn't present, content-length must be there + String contentLength = request.firstMatchingHeader(Header.CONTENT_LENGTH).orElseGet( + () -> String.valueOf(readAll(payload)) + ); + + request.putHeader(X_AMZ_DECODED_CONTENT_LENGTH, contentLength) + .removeHeader(Header.CONTENT_LENGTH); + return Long.parseLong(contentLength); + } + + // decoded header is already there, so remove content-length just to be sure it's gone + request.removeHeader(Header.CONTENT_LENGTH); + return Long.parseLong(request.firstMatchingHeader(X_AMZ_DECODED_CONTENT_LENGTH).get()); + } + + private static MessageDigest getMessageDigestInstance() { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Unable to get SHA256 Function: ", e); + } + } + + public static InputStream getBinaryRequestPayloadStream(ContentStreamProvider streamProvider) { + try { + if (streamProvider == null) { + return new ByteArrayInputStream(new byte[0]); + } + return streamProvider.newStream(); + } catch (Exception e) { + throw new RuntimeException("Unable to read request payload to sign request: ", e); + } + } + + public static byte[] hash(InputStream input) { + try { + // TODO(sra-identity-and-auth): Performance testing to verify if we should cache message digest instances + // (thread-local) + MessageDigest md = getMessageDigestInstance(); + byte[] buf = new byte[4096]; + int read = 0; + while (read >= 0) { + read = input.read(buf); + md.update(buf, 0, read); + } + return md.digest(); + } catch (Exception e) { + throw new RuntimeException("Unable to compute hash while signing request: ", e); + } + } + + public static byte[] hash(byte[] data) { + try { + MessageDigest md = getMessageDigestInstance(); + md.update(data); + return md.digest(); + } catch (Exception e) { + throw new RuntimeException("Unable to compute hash while signing request: ", e); + } + } + + public static byte[] hash(String text) { + return hash(text.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Consume entire stream and return the number of bytes - the stream will NOT be reset upon completion, so if it needs to + * be read again, the caller MUST reset the stream. + */ + private static int readAll(InputStream inputStream) { + try { + byte[] buffer = new byte[4096]; + int read = 0; + int offset = 0; + while (read >= 0) { + read = inputStream.read(buffer); + if (read >= 0) { + offset += read; + } + } + return offset; + } catch (Exception e) { + throw new RuntimeException("Could not finish reading stream: ", e); + } + } + + public static String getContentHash(SdkHttpRequest.Builder requestBuilder) { + return requestBuilder.firstMatchingHeader(X_AMZ_CONTENT_SHA256).orElseThrow( + () -> new IllegalArgumentException("Content hash must be present in the '" + X_AMZ_CONTENT_SHA256 + "' header!") + ); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SigningAlgorithm.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SigningAlgorithm.java new file mode 100644 index 000000000000..390876b9084c --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SigningAlgorithm.java @@ -0,0 +1,63 @@ +/* + * 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.http.auth.aws.internal.signer.util; + +import java.security.NoSuchAlgorithmException; +import javax.crypto.Mac; +import software.amazon.awssdk.annotations.SdkInternalApi; + +@SdkInternalApi +public enum SigningAlgorithm { + + HMAC_SHA256("HmacSHA256"); + + private final String algorithmName; + private final ThreadLocal macReference; + + SigningAlgorithm(String algorithmName) { + this.algorithmName = algorithmName; + macReference = new MacThreadLocal(algorithmName); + } + + public String getAlgorithmName() { + return algorithmName; + } + + /** + * Returns the thread local reference for the crypto algorithm + */ + public Mac getMac() { + return macReference.get(); + } + + private static class MacThreadLocal extends ThreadLocal { + private final String algorithmName; + + MacThreadLocal(String algorithmName) { + this.algorithmName = algorithmName; + } + + @Override + protected Mac initialValue() { + try { + return Mac.getInstance(algorithmName); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Unable to fetch Mac instance for Algorithm " + + algorithmName + ": " + e.getMessage()); + } + } + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/scheme/AwsV4AuthScheme.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/scheme/AwsV4AuthScheme.java new file mode 100644 index 000000000000..698a9ed6a025 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/scheme/AwsV4AuthScheme.java @@ -0,0 +1,56 @@ +/* + * 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.http.auth.aws.scheme; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.aws.internal.scheme.DefaultAwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; + +/** + * The aws.auth#sigv4 auth scheme, which uses a + * {@link AwsCredentialsIdentity} and {@link AwsV4HttpSigner}. + */ +@SdkPublicApi +public interface AwsV4AuthScheme extends AuthScheme { + + /** + * The scheme ID for this interface. + */ + String SCHEME_ID = "aws.auth#sigv4"; + + /** + * Get a default implementation of a {@link AwsV4AuthScheme} + */ + static AwsV4AuthScheme create() { + return DefaultAwsV4AuthScheme.create(); + } + + /** + * Retrieve the {@link AwsCredentialsIdentity} based {@link IdentityProvider} associated with this authentication scheme. + */ + @Override + IdentityProvider identityProvider(IdentityProviders providers); + + /** + * Retrieve the {@link AwsV4HttpSigner} associated with this authentication scheme. + */ + @Override + AwsV4HttpSigner signer(); +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/scheme/AwsV4aAuthScheme.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/scheme/AwsV4aAuthScheme.java new file mode 100644 index 000000000000..5e2421afa781 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/scheme/AwsV4aAuthScheme.java @@ -0,0 +1,56 @@ +/* + * 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.http.auth.aws.scheme; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.aws.internal.scheme.DefaultAwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; + +/** + * The aws.auth#sigv4a auth scheme, which uses a + * {@link AwsCredentialsIdentity} and AwsV4aHttpSigner. + */ +@SdkPublicApi +public interface AwsV4aAuthScheme extends AuthScheme { + + /** + * The scheme ID for this interface. + */ + String SCHEME_ID = "aws.auth#sigv4a"; + + /** + * Get a default implementation of a {@link AwsV4aAuthScheme} + */ + static AwsV4aAuthScheme create() { + return DefaultAwsV4aAuthScheme.create(); + } + + /** + * Retrieve the {@link AwsCredentialsIdentity} based {@link IdentityProvider} associated with this authentication scheme. + */ + @Override + IdentityProvider identityProvider(IdentityProviders providers); + + /** + * Retrieve the {@link AwsV4aHttpSigner} associated with this authentication scheme. + */ + @Override + AwsV4aHttpSigner signer(); +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4FamilyHttpSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4FamilyHttpSigner.java new file mode 100644 index 000000000000..dd6ebdf72760 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4FamilyHttpSigner.java @@ -0,0 +1,98 @@ +/* + * 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.http.auth.aws.signer; + +import java.time.Duration; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.Identity; + +/** + * An interface shared by {@link AwsV4HttpSigner} and {@link AwsV4aHttpSigner} for defining signer properties that are common + * across both signers. + */ +@SdkPublicApi +public interface AwsV4FamilyHttpSigner extends HttpSigner { + /** + * The name of the AWS service. This property is required. + */ + SignerProperty SERVICE_SIGNING_NAME = + SignerProperty.create(AwsV4FamilyHttpSigner.class, "ServiceSigningName"); + + /** + * A boolean to indicate whether to double url-encode the resource path when constructing the canonical request. This property + * defaults to true. + */ + SignerProperty DOUBLE_URL_ENCODE = + SignerProperty.create(AwsV4FamilyHttpSigner.class, "DoubleUrlEncode"); + + /** + * A boolean to indicate whether the resource path should be "normalized" according to RFC3986 when constructing the canonical + * request. This property defaults to true. + */ + SignerProperty NORMALIZE_PATH = + SignerProperty.create(AwsV4FamilyHttpSigner.class, "NormalizePath"); + + /** + * The location where auth-related data is inserted, as a result of signing. This property defaults to HEADER. + */ + SignerProperty AUTH_LOCATION = + SignerProperty.create(AwsV4FamilyHttpSigner.class, "AuthLocation"); + + /** + * The duration for the request to be valid. This property defaults to null. This can be set to presign the request for + * later use. The maximum allowed value for this property is 7 days. This is only supported when AuthLocation=QUERY. + */ + SignerProperty EXPIRATION_DURATION = + SignerProperty.create(AwsV4FamilyHttpSigner.class, "ExpirationDuration"); + + /** + * Whether to indicate that a payload is signed or not. This property defaults to true. This can be set false to disable + * payload signing. + */ + SignerProperty PAYLOAD_SIGNING_ENABLED = + SignerProperty.create(AwsV4FamilyHttpSigner.class, "PayloadSigningEnabled"); + + /** + * Whether to indicate that a payload is chunk-encoded or not. This property defaults to false. This can be set true to + * enable the `aws-chunk` content-encoding + */ + SignerProperty CHUNK_ENCODING_ENABLED = + SignerProperty.create(AwsV4FamilyHttpSigner.class, "ChunkEncodingEnabled"); + + /** + * The algorithm to use for calculating a "flexible" checksum. This property is optional. + */ + SignerProperty CHECKSUM_ALGORITHM = + SignerProperty.create(AwsV4FamilyHttpSigner.class, "ChecksumAlgorithm"); + + /** + * This enum represents where auth-related data is inserted, as a result of signing. + */ + enum AuthLocation { + /** + * Indicates auth-related data is inserted in HTTP headers. + */ + HEADER, + + /** + * Indicates auth-related data is inserted in HTTP query-parameters. + */ + QUERY_STRING + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4HttpSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4HttpSigner.java new file mode 100644 index 000000000000..4b85c017c0f5 --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4HttpSigner.java @@ -0,0 +1,44 @@ +/* + * 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.http.auth.aws.signer; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.aws.internal.signer.DefaultAwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; + +/** + * An {@link HttpSigner} that will sign a request using an AWS credentials {@link AwsCredentialsIdentity}). + *

+ * The process for signing requests to send to AWS services is documented + * here. + */ +@SdkPublicApi +public interface AwsV4HttpSigner extends AwsV4FamilyHttpSigner { + /** + * The AWS region name to be used for computing the signature. This property is required. + */ + SignerProperty REGION_NAME = + SignerProperty.create(AwsV4HttpSigner.class, "RegionName"); + + /** + * Get a default implementation of a {@link AwsV4HttpSigner} + */ + static AwsV4HttpSigner create() { + return new DefaultAwsV4HttpSigner(); + } +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4aHttpSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4aHttpSigner.java new file mode 100644 index 000000000000..686ac70645aa --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4aHttpSigner.java @@ -0,0 +1,45 @@ +/* + * 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.http.auth.aws.signer; + +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.OptionalDependencyLoaderUtil.getDefaultAwsCrtV4aHttpSigner; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; + +/** + * An {@link HttpSigner} that will sign a request using an AWS credentials {@link AwsCredentialsIdentity}). + *

+ * The process for signing requests to send to AWS services is documented + * here. + */ +@SdkPublicApi +public interface AwsV4aHttpSigner extends AwsV4FamilyHttpSigner { + /** + * The AWS region-set to be used for computing the signature. This property is required. + */ + SignerProperty REGION_SET = SignerProperty.create(AwsV4aHttpSigner.class, "RegionSet"); + + /** + * Get a default implementation of a {@link AwsV4aHttpSigner} + */ + static AwsV4aHttpSigner create() { + return getDefaultAwsCrtV4aHttpSigner(); + } + +} diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/RegionSet.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/RegionSet.java new file mode 100644 index 000000000000..d687b97e363d --- /dev/null +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/RegionSet.java @@ -0,0 +1,117 @@ +/* + * 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.http.auth.aws.signer; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.utils.Validate; + +/** + * This class represents the concept of a set of regions. + *

+ * A region-set can contain one or more comma-separated AWS regions, or a single wildcard to represent all regions ("global"). + * Whitespace is trimmed from entries of the set. + *

+ * Examples of region-sets: + *

    + *
  • '*' - Represents all regions, global
  • + *
  • 'eu-west-1' - Represents a single region, eu-west-1
  • + *
  • 'us-west-2,us-east-1' - Represents 2 regions, us-west-2 and us-east-1
  • + *
+ */ +@SdkPublicApi +@Immutable +public final class RegionSet { + + /** + * The "Global" region, which is represented with a single wildcard character: "*". + */ + public static final RegionSet GLOBAL; + + static { + GLOBAL = create(Collections.singleton("*")); + } + + private final Set regionSet; + private final String regionSetString; + + private RegionSet(Collection regions) { + this.regionSet = Collections.unmodifiableSet(new HashSet<>(regions)); + this.regionSetString = String.join(",", regionSet); + } + + /** + * Gets the string representation of this RegionSet. + */ + public String asString() { + return regionSetString; + } + + /** + * Gets the set of strings that represent this RegionSet. + */ + public Set asSet() { + return regionSet; + } + + /** + * Creates a RegionSet with the supplied region-set string. + * + * @param value See class documentation {@link RegionSet} for the expected format. + */ + public static RegionSet create(String value) { + Validate.notBlank(value, "value must not be blank!"); + return create(Arrays.asList(value.trim().split(","))); + } + + /** + * Creates a RegionSet from the supplied collection. + * + * @param regions A collection of regions. + */ + public static RegionSet create(Collection regions) { + Validate.notEmpty(regions, "regions must not be empty!"); + return new RegionSet( + regions.stream().map(s -> Validate.notBlank(s, "region must not be empty!").trim()).collect(Collectors.toList()) + ); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + RegionSet that = (RegionSet) o; + + return regionSet.equals(that.regionSet); + } + + @Override + public int hashCode() { + return Objects.hashCode(regionSet); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/AwsV4aHttpSignerTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/AwsV4aHttpSignerTest.java new file mode 100644 index 000000000000..5c58f4b6183a --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/AwsV4aHttpSignerTest.java @@ -0,0 +1,40 @@ +/* + * 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.http.auth.aws; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.assertj.core.api.AssertionsForClassTypes; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.utils.ClassLoaderHelper; + +public class AwsV4aHttpSignerTest { + + @Test + public void create_WithoutHttpAuthAwsCrtModule_throws() { + try (MockedStatic utilities = Mockito.mockStatic(ClassLoaderHelper.class)) { + utilities.when(() -> ClassLoaderHelper.loadClass( + "software.amazon.awssdk.http.auth.aws.crt.HttpAuthAwsCrt", + false) + ).thenThrow(new ClassNotFoundException("boom!")); + Exception e = assertThrows(RuntimeException.class, AwsV4aHttpSigner::create); + AssertionsForClassTypes.assertThat(e).hasMessageContaining("http-auth-aws-crt"); + } + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/RegionSetTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/RegionSetTest.java new file mode 100644 index 000000000000..aaa2b70a87fe --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/RegionSetTest.java @@ -0,0 +1,150 @@ +/* + * 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.http.auth.aws; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; +import static software.amazon.awssdk.http.auth.aws.RegionSetTest.Case.tc; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; + +public class RegionSetTest { + + private static final List> stringCaseList = Arrays.asList( + tc("*", "*", Arrays.asList("*")), + tc("us-west-2", "us-west-2", Arrays.asList("us-west-2")), + tc("us-east-1,us-west-2", "us-east-1,us-west-2", Arrays.asList("us-east-1", "us-west-2")), + tc(" us-west-2 ", "us-west-2", Arrays.asList("us-west-2")), + tc(" us-east-1, us-west-2 ", "us-east-1,us-west-2", Arrays.asList("us-east-1", "us-west-2")), + tc(" a,b ,c ,d ,e ,f , g ", "a,b,c,d,e,f,g", Arrays.asList("a", "b", "c", "d", "e", "f", "g")) + ); + + private static final List stringFailList = Arrays.asList( + null, + "", + " ", + ", ,", + ",,," + ); + + private static final List>> collectionCaseList = Arrays.asList( + tc(Arrays.asList("*"), "*", Arrays.asList("*")), + tc(Arrays.asList("us-west-2"), "us-west-2", Arrays.asList("us-west-2")), + tc(Arrays.asList("us-east-1", "us-west-2"), "us-east-1,us-west-2", Arrays.asList("us-east-1", "us-west-2")), + tc(Arrays.asList(" us-west-2 "), "us-west-2", Arrays.asList("us-west-2")), + tc(Arrays.asList(" us-east-1", " us-west-2 "), "us-east-1,us-west-2", Arrays.asList("us-east-1", "us-west-2")), + tc(Arrays.asList(" a", "b ", "c ", "d ", "e ", "f ", " g "), + "a,b,c,d,e,f,g", + Arrays.asList("a", "b", "c", "d", "e", "f", "g") + ) + ); + + private static final List> collectionFailList = Arrays.asList( + null, + Arrays.asList(), + Arrays.asList(""), + Arrays.asList(" "), + Arrays.asList(" ", ""), + Arrays.asList("", "", "") + ); + + private static List> stringCases() { + return stringCaseList; + } + + private static List stringFailures() { + return stringFailList; + } + + private static List>> collectionCases() { + return collectionCaseList; + } + + private static List> collectionFailures() { + return collectionFailList; + } + + @ParameterizedTest + @MethodSource("stringCases") + public void create_withStringInput_succeeds(Case stringCase) { + RegionSet regionSet = RegionSet.create(stringCase.input); + assertEquals(stringCase.asString, regionSet.asString()); + assertIterableEquals(stringCase.asSet, regionSet.asSet()); + } + + @ParameterizedTest + @MethodSource("stringFailures") + public void create_withInvalidStringInput_throws(String input) { + Exception ex = assertThrows(Exception.class, () -> RegionSet.create(input)); + if (ex instanceof NullPointerException) { + assertThat(ex.getMessage()).contains("must not be"); + } else if (ex instanceof IllegalArgumentException) { + assertThat(ex.getMessage()).contains("must not be"); + } else { + fail(); + } + } + + @ParameterizedTest + @MethodSource("collectionCases") + public void create_withCollectionInput_succeeds(Case> collectionCase) { + RegionSet regionSet = RegionSet.create(collectionCase.input); + assertEquals(collectionCase.asString, regionSet.asString()); + assertIterableEquals(collectionCase.asSet, regionSet.asSet()); + } + + @ParameterizedTest + @MethodSource("collectionFailures") + public void create_withInvalidCollectionInput_throws(Collection input) { + Exception ex = assertThrows(Exception.class, () -> RegionSet.create(input)); + if (ex instanceof NullPointerException) { + assertThat(ex.getMessage()).contains("must not be"); + } else if (ex instanceof IllegalArgumentException) { + assertThat(ex.getMessage()).contains("must not be"); + } else { + fail(); + } + } + + static final class Case { + final T input; + final String asString; + final Collection asSet; + + private Case(T input, String asString, Collection asSet) { + this.input = input; + this.asString = asString; + this.asSet = asSet; + } + + static Case tc(T input, String asString, Collection asSet) { + return new Case<>(input, asString, asSet); + } + + @Override + public String toString() { + return String.format("%s => %s :: %s", input, asString, asSet); + } + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/TestUtils.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/TestUtils.java new file mode 100644 index 000000000000..bf4d9d8f7860 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/TestUtils.java @@ -0,0 +1,119 @@ +package software.amazon.awssdk.http.auth.aws; + +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner.REGION_NAME; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner.SERVICE_SIGNING_NAME; +import static software.amazon.awssdk.http.auth.spi.signer.HttpSigner.SIGNING_CLOCK; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.nio.ByteBuffer; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.function.Consumer; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.async.SimplePublisher; + +public final class TestUtils { + + private TestUtils() { + } + + // Helpers for generating test requests + public static SignRequest generateBasicRequest( + T credentials, + Consumer requestOverrides, + Consumer> signRequestOverrides + ) { + return SignRequest.builder(credentials) + .request(SdkHttpRequest.builder() + .method(SdkHttpMethod.POST) + .putHeader("Host", "demo.us-east-1.amazonaws.com") + .putHeader("x-amz-archive-description", "test test") + .encodedPath("/") + .uri(URI.create("https://demo.us-east-1.amazonaws.com")) + .build() + .copy(requestOverrides)) + .payload(() -> new ByteArrayInputStream("{\"TableName\": \"foo\"}".getBytes())) + .putProperty(REGION_NAME, "us-east-1") + .putProperty(SERVICE_SIGNING_NAME, "demo") + .putProperty(SIGNING_CLOCK, + new TickingClock(Instant.ofEpochMilli(351153000968L))) + .build() + .copy(signRequestOverrides); + } + + public static AsyncSignRequest generateBasicAsyncRequest( + T credentials, + Consumer requestOverrides, + Consumer> signRequestOverrides + ) { + SimplePublisher publisher = new SimplePublisher<>(); + + publisher.send(ByteBuffer.wrap("{\"TableName\": \"foo\"}".getBytes())); + publisher.complete(); + + return AsyncSignRequest.builder(credentials) + .request(SdkHttpRequest.builder() + .protocol("https") + .method(SdkHttpMethod.POST) + .putHeader("Host", "demo.us-east-1.amazonaws.com") + .putHeader("x-amz-archive-description", "test test") + .encodedPath("/") + .uri(URI.create("https://demo.us-east-1.amazonaws.com")) + .build() + .copy(requestOverrides)) + .payload(publisher) + .putProperty(REGION_NAME, "us-east-1") + .putProperty(SERVICE_SIGNING_NAME, "demo") + .putProperty(SIGNING_CLOCK, + new TickingClock(Instant.ofEpochMilli(351153000968L))) + .build() + .copy(signRequestOverrides); + } + + public static class AnonymousCredentialsIdentity implements AwsCredentialsIdentity { + + @Override + public String accessKeyId() { + return null; + } + + @Override + public String secretAccessKey() { + return null; + } + } + + public static class TickingClock extends Clock { + private final Duration tick = Duration.ofSeconds(1); + private Instant time; + + public TickingClock(Instant time) { + this.time = time; + } + + @Override + public ZoneId getZone() { + return ZoneOffset.UTC; + } + + @Override + public Clock withZone(ZoneId zone) { + throw new UnsupportedOperationException(); + } + + @Override + public Instant instant() { + Instant time = this.time; + this.time = time.plus(tick); + return time; + } + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/crt/TestUtils.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/crt/TestUtils.java new file mode 100644 index 000000000000..71ee4c5d48fa --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/crt/TestUtils.java @@ -0,0 +1,95 @@ +/* + * 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.http.auth.aws.crt; + +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner.REGION_SET; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner.SERVICE_SIGNING_NAME; +import static software.amazon.awssdk.http.auth.aws.TestUtils.TickingClock; +import static software.amazon.awssdk.http.auth.aws.crt.internal.util.CrtHttpRequestConverter.toRequest; +import static software.amazon.awssdk.http.auth.aws.crt.internal.util.CrtUtils.sanitizeRequest; +import static software.amazon.awssdk.http.auth.aws.crt.internal.util.CrtUtils.toCredentials; +import static software.amazon.awssdk.http.auth.spi.signer.HttpSigner.SIGNING_CLOCK; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.time.Instant; +import java.util.function.Consumer; +import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig; +import software.amazon.awssdk.crt.auth.signing.AwsSigningUtils; +import software.amazon.awssdk.crt.http.HttpRequest; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; + +public final class TestUtils { + + private static final String TEST_VERIFICATION_PUB_X = "b6618f6a65740a99e650b33b6b4b5bd0d43b176d721a3edfea7e7d2d56d936b1"; + private static final String TEST_VERIFICATION_PUB_Y = "865ed22a7eadc9c5cb9d2cbaca1b3699139fedc5043dc6661864218330c8e518"; + + private TestUtils() { + } + + // Helpers for generating test requests + public static SignRequest generateBasicRequest( + T credentials, + Consumer requestOverrides, + Consumer> signRequestOverrides + ) { + return SignRequest.builder(credentials) + .request(SdkHttpRequest.builder() + .method(SdkHttpMethod.POST) + .putHeader("x-amz-archive-description", "test test") + .putHeader("Host", "demo.us-east-1.amazonaws.com") + .encodedPath("/") + .uri(URI.create("https://demo.us-east-1.amazonaws.com")) + .build() + .copy(requestOverrides)) + .payload(() -> new ByteArrayInputStream("{\"TableName\": \"foo\"}".getBytes())) + .putProperty(REGION_SET, RegionSet.create("aws-global")) + .putProperty(SERVICE_SIGNING_NAME, "demo") + .putProperty(SIGNING_CLOCK, new TickingClock(Instant.ofEpochMilli(1596476903000L))) + .build() + .copy(signRequestOverrides); + } + + public static AwsSigningConfig generateBasicSigningConfig(AwsCredentialsIdentity credentials) { + try (AwsSigningConfig signingConfig = new AwsSigningConfig()) { + signingConfig.setCredentials(toCredentials(credentials)); + signingConfig.setService("demo"); + signingConfig.setRegion("aws-global"); + signingConfig.setAlgorithm(AwsSigningConfig.AwsSigningAlgorithm.SIGV4_ASYMMETRIC); + signingConfig.setTime(1596476903000L); + signingConfig.setUseDoubleUriEncode(true); + signingConfig.setShouldNormalizeUriPath(true); + return signingConfig; + } + } + + public static boolean verifyEcdsaSignature(SdkHttpRequest request, + ContentStreamProvider payload, + String expectedCanonicalRequest, + AwsSigningConfig signingConfig, + String signatureValue) { + HttpRequest crtRequest = toRequest(sanitizeRequest(request), payload); + + return AwsSigningUtils.verifySigv4aEcdsaSignature(crtRequest, expectedCanonicalRequest, signingConfig, + signatureValue.getBytes(), TEST_VERIFICATION_PUB_X, + TEST_VERIFICATION_PUB_Y); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/AwsChunkedV4aPayloadSignerTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/AwsChunkedV4aPayloadSignerTest.java new file mode 100644 index 000000000000..a46ae3c181d3 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/AwsChunkedV4aPayloadSignerTest.java @@ -0,0 +1,407 @@ +/* + * 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.http.auth.aws.crt.internal.signer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.CRC32; +import static software.amazon.awssdk.http.auth.aws.crt.internal.util.CrtUtils.toCredentials; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_ECDSA_SIGNED_PAYLOAD; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_ECDSA_SIGNED_PAYLOAD_TRAILER; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.STREAMING_UNSIGNED_PAYLOAD_TRAILER; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.Header; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; + +/** + * Test the delegation of signing to the correct implementations. + */ +public class AwsChunkedV4aPayloadSignerTest { + + private static final int CHUNK_SIZE = 4; + + private static final CredentialScope CREDENTIAL_SCOPE = new CredentialScope("us-east-1", "s3", Instant.EPOCH); + + private static final byte[] DATA = "{\"TableName\": \"foo\"}".getBytes(); + + private static final ContentStreamProvider PAYLOAD = () -> new ByteArrayInputStream(DATA); + + private SdkHttpRequest.Builder requestBuilder; + + @BeforeEach + public void setUp() { + requestBuilder = SdkHttpRequest + .builder() + .method(SdkHttpMethod.POST) + .putHeader("Host", "demo.us-east-1.amazonaws.com") + .putHeader("x-amz-archive-description", "test test") + .putHeader(Header.CONTENT_LENGTH, Integer.toString(DATA.length)) + .encodedPath("/") + .uri(URI.create("http://demo.us-east-1.amazonaws.com")); + } + + @Test + public void sign_withSignedPayload_shouldChunkEncodeWithSigV4aExt() throws IOException { + AwsSigningConfig signingConfig = basicSigningConfig(); + signingConfig.setSignedBodyValue(STREAMING_ECDSA_SIGNED_PAYLOAD); + V4aRequestSigningResult result = new V4aRequestSigningResult( + requestBuilder, + "sig".getBytes(StandardCharsets.UTF_8), + signingConfig + ); + AwsChunkedV4aPayloadSigner signer = AwsChunkedV4aPayloadSigner.builder() + .credentialScope(CREDENTIAL_SCOPE) + .chunkSize(CHUNK_SIZE) + .build(); + + signer.beforeSigning(requestBuilder, PAYLOAD, signingConfig.getSignedBodyValue()); + ContentStreamProvider signedPayload = signer.sign(PAYLOAD, result); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(DATA.length)); + + byte[] tmp = new byte[2048]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + + int expectedBytes = expectedByteCount(DATA, CHUNK_SIZE); + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedBytes, actualBytes); + } + + @Test + public void sign_withSignedPayloadAndChecksum_shouldChunkEncodeWithSigV4aExtAndSigV4aTrailer() throws IOException { + AwsSigningConfig signingConfig = basicSigningConfig(); + signingConfig.setSignedBodyValue(STREAMING_ECDSA_SIGNED_PAYLOAD_TRAILER); + V4aRequestSigningResult result = new V4aRequestSigningResult( + requestBuilder, + "sig".getBytes(StandardCharsets.UTF_8), + signingConfig + ); + AwsChunkedV4aPayloadSigner signer = AwsChunkedV4aPayloadSigner.builder() + .credentialScope(CREDENTIAL_SCOPE) + .chunkSize(CHUNK_SIZE) + .checksumAlgorithm(CRC32) + .build(); + + signer.beforeSigning(requestBuilder, PAYLOAD, signingConfig.getSignedBodyValue()); + ContentStreamProvider signedPayload = signer.sign(PAYLOAD, result); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(DATA.length)); + assertThat(requestBuilder.firstMatchingHeader("x-amz-trailer")).hasValue("x-amz-checksum-crc32"); + + byte[] tmp = new byte[2048]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + int expectedBytes = expectedByteCount(DATA, CHUNK_SIZE); + // include trailer bytes in the count: + // (checksum-header + checksum-value + \r\n + trailer-sig-header + trailer-sig + \r\n) + expectedBytes += 21 + 8 + 2 + 24 + 144 + 2; + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedBytes, actualBytes); + } + + @Test + public void sign_withChecksum_shouldChunkEncodeWithChecksumTrailer() throws IOException { + AwsSigningConfig signingConfig = basicSigningConfig(); + signingConfig.setSignedBodyValue(STREAMING_UNSIGNED_PAYLOAD_TRAILER); + V4aRequestSigningResult result = new V4aRequestSigningResult( + requestBuilder, + "sig".getBytes(StandardCharsets.UTF_8), + signingConfig + ); + AwsChunkedV4aPayloadSigner signer = AwsChunkedV4aPayloadSigner.builder() + .credentialScope(CREDENTIAL_SCOPE) + .chunkSize(CHUNK_SIZE) + .checksumAlgorithm(CRC32) + .build(); + + signer.beforeSigning(requestBuilder, PAYLOAD, signingConfig.getSignedBodyValue()); + ContentStreamProvider signedPayload = signer.sign(PAYLOAD, result); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(DATA.length)); + assertThat(requestBuilder.firstMatchingHeader("x-amz-trailer")).hasValue("x-amz-checksum-crc32"); + + byte[] tmp = new byte[2048]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + int expectedBytes = expectedByteCountUnsigned(DATA, CHUNK_SIZE); + // include trailer bytes in the count: + // (checksum-header + checksum-value + \r\n) + expectedBytes += 21 + 8 + 2; + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedBytes, actualBytes); + } + + @Test + public void sign_withPreExistingTrailers_shouldChunkEncodeWithExistingTrailers() throws IOException { + AwsSigningConfig signingConfig = basicSigningConfig(); + signingConfig.setSignedBodyValue(STREAMING_UNSIGNED_PAYLOAD_TRAILER); + V4aRequestSigningResult result = new V4aRequestSigningResult( + requestBuilder + .putHeader("x-amz-trailer", "aTrailer") + .putHeader("aTrailer", "aValue"), + "sig".getBytes(StandardCharsets.UTF_8), + signingConfig + ); + AwsChunkedV4aPayloadSigner signer = AwsChunkedV4aPayloadSigner.builder() + .credentialScope(CREDENTIAL_SCOPE) + .chunkSize(CHUNK_SIZE) + .build(); + + signer.beforeSigning(requestBuilder, PAYLOAD, signingConfig.getSignedBodyValue()); + ContentStreamProvider signedPayload = signer.sign(PAYLOAD, result); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(DATA.length)); + assertThat(requestBuilder.firstMatchingHeader("aTrailer")).isNotPresent(); + assertThat(requestBuilder.firstMatchingHeader("x-amz-trailer")).hasValue("aTrailer"); + + byte[] tmp = new byte[2048]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + int expectedBytes = expectedByteCountUnsigned(DATA, CHUNK_SIZE); + // include trailer bytes in the count: + // (aTrailer: + aValue + \r\n) + expectedBytes += 9 + 6 + 2; + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedBytes, actualBytes); + } + + @Test + public void sign_withPreExistingTrailersAndChecksum_shouldChunkEncodeWithTrailers() throws IOException { + AwsSigningConfig signingConfig = basicSigningConfig(); + signingConfig.setSignedBodyValue(STREAMING_UNSIGNED_PAYLOAD_TRAILER); + V4aRequestSigningResult result = new V4aRequestSigningResult( + requestBuilder + .putHeader("x-amz-trailer", "aTrailer") + .putHeader("aTrailer", "aValue"), + "sig".getBytes(StandardCharsets.UTF_8), + signingConfig + ); + AwsChunkedV4aPayloadSigner signer = AwsChunkedV4aPayloadSigner.builder() + .credentialScope(CREDENTIAL_SCOPE) + .chunkSize(CHUNK_SIZE) + .checksumAlgorithm(CRC32) + .build(); + + signer.beforeSigning(requestBuilder, PAYLOAD, signingConfig.getSignedBodyValue()); + ContentStreamProvider signedPayload = signer.sign(PAYLOAD, result); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(DATA.length)); + assertThat(requestBuilder.firstMatchingHeader("aTrailer")).isNotPresent(); + assertThat(requestBuilder.matchingHeaders("x-amz-trailer")).contains("aTrailer", "x-amz-checksum-crc32"); + + byte[] tmp = new byte[2048]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + int expectedBytes = expectedByteCountUnsigned(DATA, CHUNK_SIZE); + // include trailer bytes in the count: + // (aTrailer: + aValue + \r\n + checksum-header + checksum-value + \r\n) + expectedBytes += 9 + 6 + 2 + 21 + 8 + 2; + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedBytes, actualBytes); + } + + @Test + public void sign_withPreExistingTrailersAndChecksumAndSignedPayload_shouldAwsChunkEncode() throws IOException { + AwsSigningConfig signingConfig = basicSigningConfig(); + signingConfig.setSignedBodyValue(STREAMING_ECDSA_SIGNED_PAYLOAD_TRAILER); + V4aRequestSigningResult result = new V4aRequestSigningResult( + requestBuilder + .putHeader("x-amz-trailer", "aTrailer") + .putHeader("aTrailer", "aValue"), + "sig".getBytes(StandardCharsets.UTF_8), + signingConfig + ); + AwsChunkedV4aPayloadSigner signer = AwsChunkedV4aPayloadSigner.builder() + .credentialScope(CREDENTIAL_SCOPE) + .chunkSize(CHUNK_SIZE) + .checksumAlgorithm(CRC32) + .build(); + + signer.beforeSigning(requestBuilder, PAYLOAD, signingConfig.getSignedBodyValue()); + ContentStreamProvider signedPayload = signer.sign(PAYLOAD, result); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(DATA.length)); + assertThat(requestBuilder.firstMatchingHeader("aTrailer")).isNotPresent(); + assertThat(requestBuilder.matchingHeaders("x-amz-trailer")).contains("aTrailer", "x-amz-checksum-crc32"); + + byte[] tmp = new byte[2048]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + int expectedBytes = expectedByteCount(DATA, CHUNK_SIZE); + // include trailer bytes in the count: + // (aTrailer: + aValue + \r\n + checksum-header + checksum-value + \r\n + trailer-sig-header + trailer-sig + \r\n) + expectedBytes += 9 + 6 + 2 + 21 + 8 + 2 + 24 + 144 + 2; + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedBytes, actualBytes); + } + + @Test + public void sign_withoutContentLength_calculatesContentLengthFromPayload() throws IOException { + AwsSigningConfig signingConfig = basicSigningConfig(); + signingConfig.setSignedBodyValue(STREAMING_UNSIGNED_PAYLOAD_TRAILER); + V4aRequestSigningResult result = new V4aRequestSigningResult( + requestBuilder, + "sig".getBytes(StandardCharsets.UTF_8), + signingConfig + ); + AwsChunkedV4aPayloadSigner signer = AwsChunkedV4aPayloadSigner.builder() + .credentialScope(CREDENTIAL_SCOPE) + .chunkSize(CHUNK_SIZE) + .checksumAlgorithm(CRC32) + .build(); + + requestBuilder.removeHeader(Header.CONTENT_LENGTH); + signer.beforeSigning(requestBuilder, PAYLOAD, signingConfig.getSignedBodyValue()); + ContentStreamProvider signedPayload = signer.sign(PAYLOAD, result); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(DATA.length)); + assertThat(requestBuilder.firstMatchingHeader("x-amz-trailer")).hasValue("x-amz-checksum-crc32"); + + byte[] tmp = new byte[2048]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + int expectedBytes = expectedByteCountUnsigned(DATA, CHUNK_SIZE); + // include trailer bytes in the count: + // (checksum-header + checksum-value + \r\n) + expectedBytes += 21 + 8 + 2; + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedBytes, actualBytes); + } + + @Test + public void sign_shouldReturnResettableContentStreamProvider() throws IOException { + AwsSigningConfig signingConfig = basicSigningConfig(); + signingConfig.setSignedBodyValue(STREAMING_ECDSA_SIGNED_PAYLOAD); + V4aRequestSigningResult result = new V4aRequestSigningResult( + requestBuilder, + "sig".getBytes(StandardCharsets.UTF_8), + signingConfig + ); + AwsChunkedV4aPayloadSigner signer = AwsChunkedV4aPayloadSigner.builder() + .credentialScope(CREDENTIAL_SCOPE) + .chunkSize(CHUNK_SIZE) + .build(); + + signer.beforeSigning(requestBuilder, PAYLOAD, signingConfig.getSignedBodyValue()); + ContentStreamProvider signedPayload = signer.sign(PAYLOAD, result); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(DATA.length)); + + byte[] tmp = new byte[2048]; + int expectedBytes = expectedByteCount(DATA, CHUNK_SIZE); + + // successive calls to newStream() should return a stream with the same data every time - this makes sure that state + // isn't carried over to the new streams returned by newStream() + for (int i = 0; i < 2; i++) { + int actualBytes = readAll(signedPayload.newStream(), tmp); + assertEquals(expectedBytes, actualBytes); + } + } + + private int readAll(InputStream src, byte[] dst) throws IOException { + int read = 0; + int offset = 0; + while (read >= 0) { + read = src.read(); + if (read >= 0) { + dst[offset] = (byte) read; + offset += 1; + } + } + return offset; + } + + private AwsSigningConfig basicSigningConfig() { + AwsSigningConfig signingConfig = new AwsSigningConfig(); + + signingConfig.setCredentials(toCredentials(AwsCredentialsIdentity.create("key", "secret"))); + signingConfig.setService("s3"); + signingConfig.setRegion("aws-global"); + signingConfig.setAlgorithm(AwsSigningConfig.AwsSigningAlgorithm.SIGV4_ASYMMETRIC); + signingConfig.setTime(Instant.now().toEpochMilli()); + signingConfig.setSignatureType(AwsSigningConfig.AwsSignatureType.HTTP_REQUEST_CHUNK); + + return signingConfig; + } + + private int expectedByteCount(byte[] data, int chunkSize) { + int size = data.length; + int ecdsaSignatureLength = 144; + int chunkHeaderLength = 17 + Integer.toHexString(chunkSize).length(); + int numChunks = size / chunkSize; + int expectedBytes = 0; + + // normal chunks + // x;chunk-signature=\r\n\r\n + expectedBytes += numChunks * (chunkHeaderLength + ecdsaSignatureLength + chunkSize + 4); + + // remaining chunk + // n;chunk-signature=\r\n\\r\n + int remainingBytes = size % chunkSize; + if (remainingBytes > 0) { + int remainingChunkHeaderLength = 17 + Integer.toHexString(remainingBytes).length(); + expectedBytes += remainingChunkHeaderLength + ecdsaSignatureLength + remainingBytes + 4; + } + + // final chunk + // 0;chunk-signature=\r\n\r\n + int finalBytes = 0; + int finalChunkHeaderLength = 17 + Integer.toHexString(finalBytes).length(); + expectedBytes += finalChunkHeaderLength + ecdsaSignatureLength + 4; + + return expectedBytes; + } + + private int expectedByteCountUnsigned(byte[] data, int chunkSize) { + int size = data.length; + int chunkHeaderLength = Integer.toHexString(chunkSize).length(); + int numChunks = size / chunkSize; + int expectedBytes = 0; + + // normal chunks + // x\r\n\r\n + expectedBytes += numChunks * (chunkHeaderLength + chunkSize + 4); + + // remaining chunk + // n\r\n\\r\n + int remainingBytes = size % chunkSize; + if (remainingBytes > 0) { + int remainingChunkHeaderLength = Integer.toHexString(remainingBytes).length(); + expectedBytes += remainingChunkHeaderLength + remainingBytes + 4; + } + + // final chunk + // 0\r\n\r\n + int finalBytes = 0; + int finalChunkHeaderLength = Integer.toHexString(finalBytes).length(); + expectedBytes += finalChunkHeaderLength + 4; + + return expectedBytes; + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/DefaultAwsCrtV4aHttpSignerTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/DefaultAwsCrtV4aHttpSignerTest.java new file mode 100644 index 000000000000..9703dac8fd22 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/crt/internal/signer/DefaultAwsCrtV4aHttpSignerTest.java @@ -0,0 +1,295 @@ +/* + * 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.http.auth.aws.crt.internal.signer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.CRC32; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignatureType.HTTP_REQUEST_VIA_HEADERS; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignedBodyValue.STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignedBodyValue.STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD_TRAILER; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignedBodyValue.STREAMING_UNSIGNED_PAYLOAD_TRAILER; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignedBodyValue.UNSIGNED_PAYLOAD; +import static software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSigningAlgorithm.SIGV4_ASYMMETRIC; +import static software.amazon.awssdk.http.auth.aws.TestUtils.AnonymousCredentialsIdentity; +import static software.amazon.awssdk.http.auth.aws.crt.TestUtils.generateBasicRequest; +import static software.amazon.awssdk.http.auth.aws.crt.internal.util.CrtUtils.toCredentials; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.readAll; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner.CHECKSUM_ALGORITHM; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner.AUTH_LOCATION; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner.AuthLocation; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner.CHUNK_ENCODING_ENABLED; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner.EXPIRATION_DURATION; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner.PAYLOAD_SIGNING_ENABLED; + +import java.time.Duration; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig; +import software.amazon.awssdk.http.Header; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; + + +/** + * Functional tests for the Sigv4a signer. These tests call the CRT native signer code. + */ +public class DefaultAwsCrtV4aHttpSignerTest { + + DefaultAwsCrtV4aHttpSigner signer = new DefaultAwsCrtV4aHttpSigner(); + + @Test + public void sign_withBasicRequest_shouldSignWithHeaders() { + AwsCredentialsIdentity credentials = + AwsCredentialsIdentity.create("AKIDEXAMPLE", "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"); + SignRequest request = generateBasicRequest( + credentials, + httpRequest -> httpRequest.port(443), + signRequest -> { + } + ); + + AwsSigningConfig expectedSigningConfig = new AwsSigningConfig(); + expectedSigningConfig.setCredentials(toCredentials(request.identity())); + expectedSigningConfig.setService("demo"); + expectedSigningConfig.setRegion("aws-global"); + expectedSigningConfig.setAlgorithm(SIGV4_ASYMMETRIC); + expectedSigningConfig.setTime(1596476903000L); + expectedSigningConfig.setUseDoubleUriEncode(true); + expectedSigningConfig.setShouldNormalizeUriPath(true); + expectedSigningConfig.setSignatureType(HTTP_REQUEST_VIA_HEADERS); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("Host")).hasValue("demo.us-east-1.amazonaws.com"); + assertThat(signedRequest.request().firstMatchingHeader("X-Amz-Date")).hasValue("20200803T174823Z"); + assertThat(signedRequest.request().firstMatchingHeader("X-Amz-Region-Set")).hasValue("aws-global"); + assertThat(signedRequest.request().firstMatchingHeader("Authorization")).isPresent(); + + } + + @Test + public void sign_withQuery_shouldSignWithQueryParams() { + AwsCredentialsIdentity credentials = + AwsCredentialsIdentity.create("AKIDEXAMPLE", "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"); + SignRequest request = generateBasicRequest( + credentials, + httpRequest -> httpRequest.port(443), + signRequest -> + signRequest.putProperty(AUTH_LOCATION, AuthLocation.QUERY_STRING) + ); + + AwsSigningConfig expectedSigningConfig = new AwsSigningConfig(); + expectedSigningConfig.setCredentials(toCredentials(request.identity())); + expectedSigningConfig.setService("demo"); + expectedSigningConfig.setRegion("aws-global"); + expectedSigningConfig.setAlgorithm(SIGV4_ASYMMETRIC); + expectedSigningConfig.setTime(1596476903000L); + expectedSigningConfig.setUseDoubleUriEncode(true); + expectedSigningConfig.setShouldNormalizeUriPath(true); + expectedSigningConfig.setSignatureType(HTTP_REQUEST_VIA_QUERY_PARAMS); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Algorithm")) + .hasValue("AWS4-ECDSA-P256-SHA256"); + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Credential")) + .hasValue("AKIDEXAMPLE/20200803/demo/aws4_request"); + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Date")).hasValue("20200803T174823Z"); + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-SignedHeaders")) + .hasValue("host;x-amz-archive-description"); + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Region-Set")).hasValue("aws-global"); + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Signature")).isPresent(); + } + + @Test + public void sign_withQueryAndExpiration_shouldSignWithQueryParamsAndExpire() { + AwsCredentialsIdentity credentials = + AwsCredentialsIdentity.create("AKIDEXAMPLE", "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"); + SignRequest request = generateBasicRequest( + credentials, + httpRequest -> httpRequest.port(443), + signRequest -> signRequest + .putProperty(AUTH_LOCATION, AuthLocation.QUERY_STRING) + .putProperty(EXPIRATION_DURATION, Duration.ofSeconds(1)) + ); + + AwsSigningConfig expectedSigningConfig = new AwsSigningConfig(); + expectedSigningConfig.setCredentials(toCredentials(request.identity())); + expectedSigningConfig.setService("demo"); + expectedSigningConfig.setRegion("aws-global"); + expectedSigningConfig.setAlgorithm(SIGV4_ASYMMETRIC); + expectedSigningConfig.setTime(1596476903000L); + expectedSigningConfig.setUseDoubleUriEncode(true); + expectedSigningConfig.setShouldNormalizeUriPath(true); + expectedSigningConfig.setSignatureType(HTTP_REQUEST_VIA_QUERY_PARAMS); + expectedSigningConfig.setExpirationInSeconds(1); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Algorithm")) + .hasValue("AWS4-ECDSA-P256-SHA256"); + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Credential")) + .hasValue("AKIDEXAMPLE/20200803/demo/aws4_request"); + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Date")).hasValue("20200803T174823Z"); + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-SignedHeaders")) + .hasValue("host;x-amz-archive-description"); + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Region-Set")).hasValue("aws-global"); + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Signature")).isPresent(); + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Expires")).hasValue("1"); + } + + @Test + public void sign_withUnsignedPayload_shouldNotSignPayload() { + AwsCredentialsIdentity credentials = + AwsCredentialsIdentity.create("AKIDEXAMPLE", "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"); + SignRequest request = generateBasicRequest( + credentials, + httpRequest -> { + }, + signRequest -> signRequest + .putProperty(PAYLOAD_SIGNING_ENABLED, false) + ); + + AwsSigningConfig expectedSigningConfig = new AwsSigningConfig(); + expectedSigningConfig.setCredentials(toCredentials(request.identity())); + expectedSigningConfig.setService("demo"); + expectedSigningConfig.setRegion("aws-global"); + expectedSigningConfig.setAlgorithm(SIGV4_ASYMMETRIC); + expectedSigningConfig.setTime(1596476903000L); + expectedSigningConfig.setUseDoubleUriEncode(true); + expectedSigningConfig.setShouldNormalizeUriPath(true); + expectedSigningConfig.setSignatureType(HTTP_REQUEST_VIA_HEADERS); + expectedSigningConfig.setSignedBodyValue(UNSIGNED_PAYLOAD); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("Host")).hasValue("demo.us-east-1.amazonaws.com"); + assertThat(signedRequest.request().firstMatchingHeader("X-Amz-Date")).hasValue("20200803T174823Z"); + assertThat(signedRequest.request().firstMatchingHeader("X-Amz-Region-Set")).hasValue("aws-global"); + assertThat(signedRequest.request().firstMatchingHeader("Authorization")).isPresent(); + } + + @Test + public void sign_withAnonymousCredentials_shouldNotSign() { + AwsCredentialsIdentity credentials = new AnonymousCredentialsIdentity(); + SignRequest request = generateBasicRequest( + credentials, + httpRequest -> { + }, + signRequest -> { + } + ); + + SignedRequest signedRequest = signer.sign(request); + + assertNull(signedRequest.request().headers().get("Authorization")); + } + + @Test + public void signAsync_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, + () -> signer.signAsync((AsyncSignRequest) null) + ); + } + + @Test + public void sign_WithChunkEncodingTrue_DelegatesToAwsChunkedPayloadSigner() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest + .putHeader(Header.CONTENT_LENGTH, "20"), + signRequest -> signRequest + .putProperty(CHUNK_ENCODING_ENABLED, true) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")) + .hasValue(STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD); + assertThat(signedRequest.request().firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue("353"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-decoded-content-length")).hasValue("20"); + + // Ensures that CRT runs correctly and without throwing an exception + readAll(signedRequest.payload().get().newStream()); + } + + @Test + public void sign_WithChunkEncodingTrueAndChecksumAlgorithm_DelegatesToAwsChunkedPayloadSigner() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest + .putHeader(Header.CONTENT_LENGTH, "20"), + signRequest -> signRequest + .putProperty(CHUNK_ENCODING_ENABLED, true) + .putProperty(CHECKSUM_ALGORITHM, CRC32) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")) + .hasValue(STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD_TRAILER); + assertThat(signedRequest.request().firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue("554"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-decoded-content-length")).hasValue("20"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-trailer")).hasValue("x-amz-checksum-crc32"); + + // Ensures that CRT runs correctly and without throwing an exception + readAll(signedRequest.payload().get().newStream()); + } + + @Test + public void sign_WithPayloadSigningFalseAndChunkEncodingTrueAndTrailer_DelegatesToAwsChunkedPayloadSigner() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest + .putHeader(Header.CONTENT_LENGTH, "20"), + signRequest -> signRequest + .putProperty(PAYLOAD_SIGNING_ENABLED, false) + .putProperty(CHUNK_ENCODING_ENABLED, true) + .putProperty(CHECKSUM_ALGORITHM, CRC32) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")) + .hasValue(STREAMING_UNSIGNED_PAYLOAD_TRAILER); + assertThat(signedRequest.request().firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue("62"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-decoded-content-length")).hasValue("20"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-trailer")).hasValue("x-amz-checksum-crc32"); + + // Ensures that CRT runs correctly and without throwing an exception + readAll(signedRequest.payload().get().newStream()); + } + + @Test + public void sign_WithPayloadSigningFalseAndChunkEncodingTrueWithoutTrailer_DelegatesToUnsignedPayload() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest + .putHeader(Header.CONTENT_LENGTH, "20"), + signRequest -> signRequest + .putProperty(PAYLOAD_SIGNING_ENABLED, false) + .putProperty(CHUNK_ENCODING_ENABLED, true) + ); + + SignedRequest signedRequest = signer.sign(request); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue("UNSIGNED-PAYLOAD"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-decoded-content-length")).isNotPresent(); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/crt/internal/util/CrtHttpRequestConverterTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/crt/internal/util/CrtHttpRequestConverterTest.java new file mode 100644 index 000000000000..7fa5a85e96db --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/crt/internal/util/CrtHttpRequestConverterTest.java @@ -0,0 +1,136 @@ +/* + * 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.http.auth.aws.crt.internal.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.http.auth.aws.crt.internal.util.CrtHttpRequestConverter.toCrtStream; +import static software.amazon.awssdk.http.auth.aws.crt.internal.util.CrtHttpRequestConverter.toRequest; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.crt.http.HttpHeader; +import software.amazon.awssdk.crt.http.HttpRequest; +import software.amazon.awssdk.crt.http.HttpRequestBodyStream; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.utils.BinaryUtils; + +public class CrtHttpRequestConverterTest { + + @Test + public void request_withHeaders_isConvertedToCrtFormat() { + String data = "data"; + SdkHttpRequest request = SdkHttpFullRequest.builder() + .method(SdkHttpMethod.POST) + .putHeader("x-amz-archive-description", "test test") + .putHeader("Host", "demo.us-east-1.amazonaws.com") + .encodedPath("/") + .uri(URI.create("https://demo.us-east-1.amazonaws.com")) + .build(); + ContentStreamProvider payload = () -> new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)); + + HttpRequest crtHttpRequest = toRequest(request, payload); + + assertThat(crtHttpRequest.getMethod()).isEqualTo("POST"); + assertThat(crtHttpRequest.getEncodedPath()).isEqualTo("/"); + + List headers = crtHttpRequest.getHeaders(); + HttpHeader[] headersAsArray = crtHttpRequest.getHeadersAsArray(); + assertThat(headers.size()).isEqualTo(2); + assertThat(headersAsArray.length).isEqualTo(2); + assertThat(headers.get(0).getName()).isEqualTo("Host"); + assertThat(headers.get(0).getValue()).isEqualTo("demo.us-east-1.amazonaws.com"); + assertThat(headersAsArray[1].getName()).isEqualTo("x-amz-archive-description"); + assertThat(headersAsArray[1].getValue()).isEqualTo("test test"); + + assertStream(data, crtHttpRequest.getBodyStream()); + assertHttpRequestSame(request, crtHttpRequest); + } + + @Test + public void request_withQueryParams_isConvertedToCrtFormat() { + SdkHttpRequest request = SdkHttpFullRequest.builder() + .method(SdkHttpMethod.GET) + .putRawQueryParameter("param1", "value1") + .putRawQueryParameter("param2", Arrays.asList("value2-1", "value2-2")) + .putHeader("Host", "demo.us-east-1.amazonaws.com") + .encodedPath("/path") + .uri(URI.create("https://demo.us-east-1.amazonaws.com")) + .build(); + HttpRequest crtHttpRequest = toRequest(request, (ContentStreamProvider) null); + assertThat(crtHttpRequest.getMethod()).isEqualTo("GET"); + assertThat(crtHttpRequest.getEncodedPath()).isEqualTo("/path?param1=value1¶m2=value2-1¶m2=value2-2"); + assertHttpRequestSame(request, crtHttpRequest); + } + + @Test + public void request_withEmptyPath_isConvertedToCrtFormat() { + SdkHttpFullRequest request = SdkHttpFullRequest.builder() + .method(SdkHttpMethod.GET) + .putHeader("Host", "demo.us-east-1.amazonaws.com") + .encodedPath("") + .uri(URI.create("https://demo.us-east-1.amazonaws.com")) + .build(); + HttpRequest crtHttpRequest = toRequest(request, (ContentStreamProvider) null); + assertThat(crtHttpRequest.getEncodedPath()).isEqualTo("/"); + assertHttpRequestSame(request, crtHttpRequest); + } + + @Test + public void request_byteArray_isConvertedToCrtStream() { + byte[] data = new byte[144]; + Arrays.fill(data, (byte) 0x2A); + + HttpRequestBodyStream crtStream = toCrtStream(data); + + ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16 * 1024]); + crtStream.sendRequestBody(byteBuffer); + byteBuffer.flip(); + + String result = new String(BinaryUtils.copyBytesFrom(byteBuffer), StandardCharsets.UTF_8); + assertThat(result.length()).isEqualTo(144); + assertThat(result).containsPattern("^\\*+$"); + } + + private void assertHttpRequestSame(SdkHttpRequest originalRequest, HttpRequest crtRequest) { + SdkHttpRequest sdkRequest = toRequest(originalRequest, crtRequest); + + assertThat(sdkRequest.method()).isEqualTo(originalRequest.method()); + assertThat(sdkRequest.protocol()).isEqualTo(originalRequest.protocol()); + assertThat(sdkRequest.host()).isEqualTo(originalRequest.host()); + assertThat(sdkRequest.encodedPath()).isEqualTo(originalRequest.encodedPath()); + assertThat(sdkRequest.headers()).isEqualTo(originalRequest.headers()); + assertThat(sdkRequest.rawQueryParameters()).isEqualTo(originalRequest.rawQueryParameters()); + } + + private void assertStream(String expectedData, HttpRequestBodyStream crtStream) { + ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16 * 1024]); + crtStream.sendRequestBody(byteBuffer); + byteBuffer.flip(); + + String result = new String(BinaryUtils.copyBytesFrom(byteBuffer), StandardCharsets.UTF_8); + assertThat(result.length()).isEqualTo(expectedData.length()); + assertThat(result).isEqualTo(expectedData); + } + +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/io/SigV4DataFramePublisherTckTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/io/SigV4DataFramePublisherTckTest.java new file mode 100644 index 000000000000..7e4ee721ae52 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/io/SigV4DataFramePublisherTckTest.java @@ -0,0 +1,87 @@ +/* + * 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.http.auth.aws.eventstream.internal.io; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Instant; +import org.reactivestreams.Publisher; +import org.reactivestreams.tck.PublisherVerification; +import org.reactivestreams.tck.TestEnvironment; +import software.amazon.awssdk.http.auth.aws.TestUtils.TickingClock; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.async.SimplePublisher; + +public class SigV4DataFramePublisherTckTest extends PublisherVerification { + public SigV4DataFramePublisherTckTest() { + super(new TestEnvironment()); + } + + @Override + public Publisher createPublisher(long elements) { + Clock signingClock = new TickingClock(Instant.EPOCH); + Instant initialInstant = signingClock.instant(); + AwsCredentialsIdentity credentials = AwsCredentialsIdentity.create("access", "secret"); + CredentialScope credentialScope = new CredentialScope("us-east-1", "demo", initialInstant); + + SimplePublisher payload = new SimplePublisher<>(); + + Publisher sigV4Publisher = SigV4DataFramePublisher.builder() + .publisher(payload) + .credentials(credentials) + .credentialScope(credentialScope) + .signature("signature") + .signingClock(signingClock) + .build(); + + // since this publisher specifically appends an empty element to the end, we need to subtract 1 + // from the number of elements to expected to be "produced" before end-of-stream + long expectedElements = elements - 1; + for (int i = 0; i < expectedElements; i++) { + payload.send(ByteBuffer.wrap(Integer.toString(i).getBytes(StandardCharsets.UTF_8))); + } + payload.complete(); + + return sigV4Publisher; + } + + @Override + public Publisher createFailedPublisher() { + Clock signingClock = new TickingClock(Instant.EPOCH); + Instant initialInstant = signingClock.instant(); + AwsCredentialsIdentity credentials = AwsCredentialsIdentity.create("access", "secret"); + CredentialScope credentialScope = new CredentialScope("us-east-1", "demo", initialInstant); + + SimplePublisher payload = new SimplePublisher<>(); + + Publisher sigV4Publisher = SigV4DataFramePublisher.builder() + .publisher(payload) + .credentials(credentials) + .credentialScope(credentialScope) + .signature("signature") + .signingClock(signingClock) + .build(); + payload.error(new RuntimeException("boom!")); + return sigV4Publisher; + } + + @Override + public long maxElementsFromPublisher() { + return 256L; + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/io/SigV4DataFramePublisherTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/io/SigV4DataFramePublisherTest.java new file mode 100644 index 000000000000..ae492fdc6f4e --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/eventstream/internal/io/SigV4DataFramePublisherTest.java @@ -0,0 +1,274 @@ +/* + * 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.http.auth.aws.eventstream.internal.io; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import io.reactivex.Flowable; +import io.reactivex.functions.BiFunction; +import io.reactivex.functions.Function; +import io.reactivex.subscribers.TestSubscriber; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.assertj.core.util.Lists; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.BinaryUtils; +import software.amazon.eventstream.HeaderValue; +import software.amazon.eventstream.Message; +import software.amazon.eventstream.MessageDecoder; + +public class SigV4DataFramePublisherTest { + + private static final List SIGNING_INSTANTS = Stream.of( + // Note: This first Instant is used for signing the request not an event + OffsetDateTime.of(1981, 1, 16, 6, 30, 0, 0, ZoneOffset.UTC).toInstant(), + OffsetDateTime.of(1981, 1, 16, 6, 30, 1, 0, ZoneOffset.UTC).toInstant(), + OffsetDateTime.of(1981, 1, 16, 6, 30, 2, 0, ZoneOffset.UTC).toInstant(), + OffsetDateTime.of(1981, 1, 16, 6, 30, 3, 0, ZoneOffset.UTC).toInstant(), + OffsetDateTime.of(1981, 1, 16, 6, 30, 4, 0, ZoneOffset.UTC).toInstant() + ).collect(Collectors.toList()); + + /** + * @return A clock that returns the values from {@link #SIGNING_INSTANTS} in order. + * @throws IllegalStateException When there are no more instants to return. + */ + private static Clock signingClock() { + return new Clock() { + private final AtomicInteger timeIndex = new AtomicInteger(0); + + @Override + public Instant instant() { + int idx = timeIndex.getAndIncrement(); + // Note: we use an atomic because Clock must be threadsafe, + // though probably not necessary for our tests + if (idx >= SIGNING_INSTANTS.size()) { + throw new IllegalStateException("Clock ran out of Instants to return! " + idx); + } + return SIGNING_INSTANTS.get(idx); + } + + @Override + public ZoneId getZone() { + return ZoneOffset.UTC; + } + + @Override + public Clock withZone(ZoneId zone) { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Test that the sigv4 publisher adapts an existing publisher with sigv4 chunk-signing + */ + @Test + public void sigV4DataFramePublisher_shouldAdaptAndSignPublisher() { + TestVector testVector = generateTestVector(); + Clock signingClock = signingClock(); + Instant initialInstant = signingClock.instant(); + AwsCredentialsIdentity credentials = AwsCredentialsIdentity.create("access", "secret"); + CredentialScope credentialScope = new CredentialScope("us-east-1", "demo", initialInstant); + + Publisher sigV4Publisher = SigV4DataFramePublisher.builder() + .publisher(testVector.payload()) + .credentials(credentials) + .credentialScope(credentialScope) + .signature(testVector.signature()) + .signingClock(signingClock) + .build(); + + TestSubscriber testSubscriber = TestSubscriber.create(); + + Flowable.fromPublisher(sigV4Publisher) + .flatMap(new Function>() { + final Queue messages = new LinkedList<>(); + final MessageDecoder decoder = new MessageDecoder(message -> messages.offer(message)); + + @Override + public Publisher apply(ByteBuffer byteBuffer) { + decoder.feed(byteBuffer.array()); + List messageList = new ArrayList<>(); + while (!messages.isEmpty()) { + messageList.add(messages.poll()); + } + + return Flowable.fromIterable(messageList); + } + }) + .subscribe(testSubscriber); + + testSubscriber.assertNoErrors(); + testSubscriber.assertComplete(); + testSubscriber.assertValueSequence(testVector.expectedPublisher().blockingIterable()); + } + + /** + * Test that without demand from subscriber, trailing empty frame is not delivered + */ + @Test + public void testBackPressure() { + TestVector testVector = generateTestVector(); + Clock signingClock = signingClock(); + Instant initialInstant = signingClock.instant(); + AwsCredentialsIdentity credentials = AwsCredentialsIdentity.create("access", "secret"); + CredentialScope credentialScope = new CredentialScope("us-east-1", "demo", initialInstant); + + Publisher sigV4Publisher = SigV4DataFramePublisher.builder() + .publisher(testVector.payload()) + .credentials(credentials) + .credentialScope(credentialScope) + .signature(testVector.signature()) + .signingClock(signingClock) + .build(); + + Subscriber subscriber = Mockito.spy(new Subscriber() { + + @Override + public void onSubscribe(Subscription s) { + // Only request the size of request body (which should NOT include the empty frame) + s.request(testVector.content().size()); + } + + @Override + public void onNext(Object o) { + } + + @Override + public void onError(Throwable t) { + fail("onError should never been called"); + + } + + @Override + public void onComplete() { + fail("onComplete should never been called"); + + } + }); + + Flowable.fromPublisher(sigV4Publisher) + .flatMap(new Function>() { + final Queue messages = new LinkedList<>(); + final MessageDecoder decoder = new MessageDecoder(message -> messages.offer(message)); + + @Override + public Publisher apply(ByteBuffer byteBuffer) throws Exception { + decoder.feed(byteBuffer.array()); + List messageList = new ArrayList<>(); + while (!messages.isEmpty()) { + messageList.add(messages.poll()); + } + return Flowable.fromIterable(messageList); + } + }) + .subscribe(subscriber); + + + // The number of events equal to the size of request body (excluding trailing empty frame) + verify(subscriber, times(testVector.content().size())).onNext(any()); + // subscriber is not terminated (no onError/onComplete) since trailing empty frame is not delivered yet + verify(subscriber, never()).onError(any()); + verify(subscriber, never()).onComplete(); + } + + private TestVector generateTestVector() { + return new TestVector() { + final List content = Lists.newArrayList("A", "B", "C"); + + @Override + public List content() { + return content; + } + + @Override + public String signature() { + return "e1d8e8c8815e60969f2a34765c9a15945ffc0badbaa4b7e3b163ea19131e949b"; + } + + @Override + public Publisher payload() { + List bodyBytes = content.stream() + .map(s -> ByteBuffer.wrap(s.getBytes(StandardCharsets.UTF_8))) + .collect(Collectors.toList()); + + return Flowable.fromIterable(bodyBytes); + } + + @Override + public Flowable expectedPublisher() { + Flowable sigsHex = Flowable.just( + "7aabf85b765e6a4d0d500b6e968657b14726fa3e1eb7e839302728ffd77629a5", + "f72aa9642f571d24a6e1ae42f10f073ad9448d8a028b6bcd82da081335adda02", + "632af120435b57ec241d8bfbb12e496dfd5e2730a1a02ac0ab6eaa230ae02e9a", + "c6f679ddb3af68f5e82f0cf6761244cb2338cf11e7d01a24130aea1b7c17e53e"); + + // The Last data frame is empty + Flowable payloads = Flowable.fromIterable(content).concatWith(Flowable.just("")); + + return sigsHex.zipWith(payloads, new BiFunction() { + // The first Instant was used to sign the request + private int idx = 1; + + @Override + public Message apply(String sig, String payload) { + Map headers = new HashMap<>(); + headers.put(":date", HeaderValue.fromTimestamp(SIGNING_INSTANTS.get(idx))); + headers.put(":chunk-signature", + HeaderValue.fromByteArray(BinaryUtils.fromHex(sig))); + idx += 1; + return new Message(headers, payload.getBytes(StandardCharsets.UTF_8)); + } + } + ); + } + }; + } + + private interface TestVector { + String signature(); + + List content(); + + Publisher payload(); + + Flowable expectedPublisher(); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/AwsChunkedV4PayloadSignerTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/AwsChunkedV4PayloadSignerTest.java new file mode 100644 index 000000000000..01ad8b847151 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/AwsChunkedV4PayloadSignerTest.java @@ -0,0 +1,460 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.CRC32; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.SHA256; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.Header; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; + +/** + * Test the delegation of signing to the correct implementations. + */ +public class AwsChunkedV4PayloadSignerTest { + + int chunkSize = 4; + + CredentialScope credentialScope = new CredentialScope("us-east-1", "s3", Instant.EPOCH); + + byte[] data = "{\"TableName\": \"foo\"}".getBytes(); + + ContentStreamProvider payload = () -> new ByteArrayInputStream(data); + + SdkHttpRequest.Builder requestBuilder; + + @BeforeEach + public void setUp() { + requestBuilder = SdkHttpRequest + .builder() + .method(SdkHttpMethod.POST) + .putHeader("Host", "demo.us-east-1.amazonaws.com") + .putHeader("x-amz-archive-description", "test test") + .putHeader(Header.CONTENT_LENGTH, Integer.toString(data.length)) + .encodedPath("/") + .uri(URI.create("http://demo.us-east-1.amazonaws.com")); + } + + @Test + public void sign_withSignedPayload_shouldChunkEncodeWithSigV4Ext() throws IOException { + String expectedContent = + "4;chunk-signature=082f5b0e588893570e152b401a886161ee772ed066948f68c8f01aee11cca4f8\r\n{\"Ta\r\n" + + "4;chunk-signature=777b02ec61ce7934578b1efe6fbe08c21ae4a8cdf66a709d3b4fd320dddd2839\r\nbleN\r\n" + + "4;chunk-signature=84abdae650f64dee4d703d41c7d87c8bc251c22b8c493c75ce24431b60b73937\r\name\"\r\n" + + "4;chunk-signature=aff22ddad9d4388233fe9bc47e9c552a6e9ba9285af79555d2ce7fdaab726320\r\n: \"f\r\n" + + "4;chunk-signature=30e55f4e1c1fd444c06e9be42d9594b8fd7ead436bc67a58b5350ffd58b6aaa5\r\noo\"}\r\n" + + "0;chunk-signature=825ad80195cae47f54984835543ff2179c2c5a53c324059cd632e50259384ee3\r\n\r\n"; + + requestBuilder.putHeader("x-amz-content-sha256", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"); + V4CanonicalRequest canonicalRequest = new V4CanonicalRequest( + requestBuilder.build(), + "STREAMING-AWS4-HMAC-SHA256-PAYLOAD", + new V4CanonicalRequest.Options(true, true) + ); + V4RequestSigningResult requestSigningResult = new V4RequestSigningResult( + "STREAMING-AWS4-HMAC-SHA256-PAYLOAD", + "key".getBytes(StandardCharsets.UTF_8), + "sig", + canonicalRequest, + requestBuilder + ); + AwsChunkedV4PayloadSigner signer = AwsChunkedV4PayloadSigner.builder() + .credentialScope(credentialScope) + .chunkSize(chunkSize) + .build(); + + signer.beforeSigning(requestBuilder, null); + ContentStreamProvider signedPayload = signer.sign(payload, requestSigningResult); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(data.length)); + + byte[] tmp = new byte[1024]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedContent, new String(tmp, 0, actualBytes)); + } + + @Test + public void sign_withSignedPayloadAndChecksum_shouldChunkEncodeWithSigV4ExtAndSigV4Trailer() throws IOException { + String expectedContent = + "4;chunk-signature=082f5b0e588893570e152b401a886161ee772ed066948f68c8f01aee11cca4f8\r\n{\"Ta\r\n" + + "4;chunk-signature=777b02ec61ce7934578b1efe6fbe08c21ae4a8cdf66a709d3b4fd320dddd2839\r\nbleN\r\n" + + "4;chunk-signature=84abdae650f64dee4d703d41c7d87c8bc251c22b8c493c75ce24431b60b73937\r\name\"\r\n" + + "4;chunk-signature=aff22ddad9d4388233fe9bc47e9c552a6e9ba9285af79555d2ce7fdaab726320\r\n: \"f\r\n" + + "4;chunk-signature=30e55f4e1c1fd444c06e9be42d9594b8fd7ead436bc67a58b5350ffd58b6aaa5\r\noo\"}\r\n" + + "0;chunk-signature=825ad80195cae47f54984835543ff2179c2c5a53c324059cd632e50259384ee3\r\n" + + "x-amz-checksum-crc32:oL+a/g==\r\n" + + "x-amz-trailer-signature:23457d04f4a8e279780cb91e28d4fbd1c6a2dd678d419705461a80514cea206c\r\n\r\n"; + + requestBuilder.putHeader("x-amz-content-sha256", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER"); + V4CanonicalRequest canonicalRequest = new V4CanonicalRequest( + requestBuilder.build(), + "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER", + new V4CanonicalRequest.Options(true, true) + ); + V4RequestSigningResult requestSigningResult = new V4RequestSigningResult( + "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER", + "key".getBytes(StandardCharsets.UTF_8), + "sig", + canonicalRequest, + requestBuilder + ); + AwsChunkedV4PayloadSigner signer = AwsChunkedV4PayloadSigner.builder() + .credentialScope(credentialScope) + .chunkSize(chunkSize) + .checksumAlgorithm(CRC32) + .build(); + + signer.beforeSigning(requestBuilder, payload); + ContentStreamProvider signedPayload = signer.sign(payload, requestSigningResult); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(data.length)); + assertThat(requestBuilder.firstMatchingHeader("x-amz-trailer")).hasValue("x-amz-checksum-crc32"); + + byte[] tmp = new byte[1024]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedContent, new String(tmp, 0, actualBytes)); + } + + @Test + public void sign_withChecksum_shouldChunkEncodeWithChecksumTrailer() throws IOException { + String expectedContent = + "4\r\n{\"Ta\r\n" + + "4\r\nbleN\r\n" + + "4\r\name\"\r\n" + + "4\r\n: \"f\r\n" + + "4\r\noo\"}\r\n" + + "0\r\n" + + "x-amz-checksum-sha256:oVyCkrHRKru75BSGBfeHL732RWGP7lqw6AcqezTxVeI=\r\n\r\n"; + + requestBuilder.putHeader("x-amz-content-sha256", "STREAMING-UNSIGNED-PAYLOAD-TRAILER"); + V4CanonicalRequest canonicalRequest = new V4CanonicalRequest( + requestBuilder.build(), + "STREAMING-UNSIGNED-PAYLOAD-TRAILER", + new V4CanonicalRequest.Options(true, true) + ); + V4RequestSigningResult requestSigningResult = new V4RequestSigningResult( + "STREAMING-UNSIGNED-PAYLOAD-TRAILER", + "key".getBytes(StandardCharsets.UTF_8), + "sig", + canonicalRequest, + requestBuilder + ); + AwsChunkedV4PayloadSigner signer = AwsChunkedV4PayloadSigner.builder() + .credentialScope(credentialScope) + .chunkSize(chunkSize) + .checksumAlgorithm(SHA256) + .build(); + + signer.beforeSigning(requestBuilder, payload); + ContentStreamProvider signedPayload = signer.sign(payload, requestSigningResult); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(data.length)); + assertThat(requestBuilder.firstMatchingHeader("x-amz-trailer")).hasValue("x-amz-checksum-sha256"); + + byte[] tmp = new byte[1024]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedContent, new String(tmp, 0, actualBytes)); + } + + @Test + public void sign_withPreExistingTrailers_shouldChunkEncodeWithExistingTrailers() throws IOException { + String expectedContent = + "4\r\n{\"Ta\r\n" + + "4\r\nbleN\r\n" + + "4\r\name\"\r\n" + + "4\r\n: \"f\r\n" + + "4\r\noo\"}\r\n" + + "0\r\n" + + "PreExistingHeader1:someValue1,someValue2\r\n" + + "PreExistingHeader2:someValue3\r\n\r\n"; + + requestBuilder + .putHeader("x-amz-content-sha256", "STREAMING-UNSIGNED-PAYLOAD-TRAILER") + .appendHeader("PreExistingHeader1", "someValue1") + .appendHeader("PreExistingHeader1", "someValue2") + .appendHeader("PreExistingHeader2", "someValue3") + .appendHeader("x-amz-trailer", "PreExistingHeader1") + .appendHeader("x-amz-trailer", "PreExistingHeader2"); + + V4CanonicalRequest canonicalRequest = new V4CanonicalRequest( + requestBuilder.build(), + "STREAMING-UNSIGNED-PAYLOAD-TRAILER", + new V4CanonicalRequest.Options(true, true) + ); + V4RequestSigningResult requestSigningResult = new V4RequestSigningResult( + "STREAMING-UNSIGNED-PAYLOAD-TRAILER", + "key".getBytes(StandardCharsets.UTF_8), + "sig", + canonicalRequest, + requestBuilder + ); + AwsChunkedV4PayloadSigner signer = AwsChunkedV4PayloadSigner.builder() + .credentialScope(credentialScope) + .chunkSize(chunkSize) + .build(); + + signer.beforeSigning(requestBuilder, payload); + ContentStreamProvider signedPayload = signer.sign(payload, requestSigningResult); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(data.length)); + assertThat(requestBuilder.firstMatchingHeader("PreExistingHeader1")).isNotPresent(); + assertThat(requestBuilder.firstMatchingHeader("PreExistingHeader2")).isNotPresent(); + assertThat(requestBuilder.matchingHeaders("x-amz-trailer")).contains("PreExistingHeader1", "PreExistingHeader2"); + + byte[] tmp = new byte[1024]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedContent, new String(tmp, 0, actualBytes)); + } + + @Test + public void sign_withPreExistingTrailersAndChecksum_shouldChunkEncodeWithTrailers() throws IOException { + String expectedContent = + "4\r\n{\"Ta\r\n" + + "4\r\nbleN\r\n" + + "4\r\name\"\r\n" + + "4\r\n: \"f\r\n" + + "4\r\noo\"}\r\n" + + "0\r\n" + + "PreExistingHeader1:someValue1,someValue2\r\n" + + "PreExistingHeader2:someValue3\r\n" + + "x-amz-checksum-crc32:oL+a/g==\r\n\r\n"; + + requestBuilder + .putHeader("x-amz-content-sha256", "STREAMING-UNSIGNED-PAYLOAD-TRAILER") + .appendHeader("PreExistingHeader1", "someValue1") + .appendHeader("PreExistingHeader1", "someValue2") + .appendHeader("PreExistingHeader2", "someValue3") + .appendHeader("x-amz-trailer", "PreExistingHeader1") + .appendHeader("x-amz-trailer", "PreExistingHeader2"); + + V4CanonicalRequest canonicalRequest = new V4CanonicalRequest( + requestBuilder.build(), + "STREAMING-UNSIGNED-PAYLOAD-TRAILER", + new V4CanonicalRequest.Options(true, true) + ); + V4RequestSigningResult requestSigningResult = new V4RequestSigningResult( + "STREAMING-UNSIGNED-PAYLOAD-TRAILER", + "key".getBytes(StandardCharsets.UTF_8), + "sig", + canonicalRequest, + requestBuilder + ); + AwsChunkedV4PayloadSigner signer = AwsChunkedV4PayloadSigner.builder() + .credentialScope(credentialScope) + .chunkSize(chunkSize) + .checksumAlgorithm(CRC32) + .build(); + + signer.beforeSigning(requestBuilder, payload); + ContentStreamProvider signedPayload = signer.sign(payload, requestSigningResult); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(data.length)); + assertThat(requestBuilder.firstMatchingHeader("PreExistingHeader1")).isNotPresent(); + assertThat(requestBuilder.firstMatchingHeader("PreExistingHeader2")).isNotPresent(); + assertThat(requestBuilder.matchingHeaders("x-amz-trailer")).contains( + "PreExistingHeader1", "PreExistingHeader2", "x-amz-checksum-crc32" + ); + + byte[] tmp = new byte[1024]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedContent, new String(tmp, 0, actualBytes)); + } + + @Test + public void sign_withPreExistingTrailersAndChecksumAndSignedPayload_shouldAwsChunkEncode() throws IOException { + String expectedContent = + "4;chunk-signature=082f5b0e588893570e152b401a886161ee772ed066948f68c8f01aee11cca4f8\r\n{\"Ta\r\n" + + "4;chunk-signature=777b02ec61ce7934578b1efe6fbe08c21ae4a8cdf66a709d3b4fd320dddd2839\r\nbleN\r\n" + + "4;chunk-signature=84abdae650f64dee4d703d41c7d87c8bc251c22b8c493c75ce24431b60b73937\r\name\"\r\n" + + "4;chunk-signature=aff22ddad9d4388233fe9bc47e9c552a6e9ba9285af79555d2ce7fdaab726320\r\n: \"f\r\n" + + "4;chunk-signature=30e55f4e1c1fd444c06e9be42d9594b8fd7ead436bc67a58b5350ffd58b6aaa5\r\noo\"}\r\n" + + "0;chunk-signature=825ad80195cae47f54984835543ff2179c2c5a53c324059cd632e50259384ee3\r\n" + + "zzz:123\r\n" + + "PreExistingHeader1:someValue1\r\n" + + "x-amz-checksum-crc32:oL+a/g==\r\n" + + "x-amz-trailer-signature:3f65ab57ede6a5fb7c77b14b35faf2d9dd2c6d89828bdae189a04f3677bc16f2\r\n\r\n"; + + requestBuilder + .putHeader("x-amz-content-sha256", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER") + .appendHeader("PreExistingHeader1", "someValue1") + .appendHeader("zzz", "123") + .appendHeader("x-amz-trailer", "zzz") + .appendHeader("x-amz-trailer", "PreExistingHeader1"); + + V4CanonicalRequest canonicalRequest = new V4CanonicalRequest( + requestBuilder.build(), + "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER", + new V4CanonicalRequest.Options(true, true) + ); + V4RequestSigningResult requestSigningResult = new V4RequestSigningResult( + "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER", + "key".getBytes(StandardCharsets.UTF_8), + "sig", + canonicalRequest, + requestBuilder + ); + AwsChunkedV4PayloadSigner signer = AwsChunkedV4PayloadSigner.builder() + .credentialScope(credentialScope) + .chunkSize(chunkSize) + .checksumAlgorithm(CRC32) + .build(); + + signer.beforeSigning(requestBuilder, payload); + ContentStreamProvider signedPayload = signer.sign(payload, requestSigningResult); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(data.length)); + assertThat(requestBuilder.firstMatchingHeader("PreExistingHeader1")).isNotPresent(); + assertThat(requestBuilder.matchingHeaders("x-amz-trailer")).contains("zzz", "PreExistingHeader1", "x-amz-checksum-crc32"); + + byte[] tmp = new byte[1024]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedContent, new String(tmp, 0, actualBytes)); + } + + + @Test + public void sign_withoutContentLength_calculatesContentLengthFromPayload() throws IOException { + String expectedContent = + "4\r\n{\"Ta\r\n" + + "4\r\nbleN\r\n" + + "4\r\name\"\r\n" + + "4\r\n: \"f\r\n" + + "4\r\noo\"}\r\n" + + "0\r\n" + + "x-amz-checksum-sha256:oVyCkrHRKru75BSGBfeHL732RWGP7lqw6AcqezTxVeI=\r\n\r\n"; + + requestBuilder.putHeader("x-amz-content-sha256", "STREAMING-UNSIGNED-PAYLOAD-TRAILER"); + requestBuilder.removeHeader(Header.CONTENT_LENGTH); + + V4CanonicalRequest canonicalRequest = new V4CanonicalRequest( + requestBuilder.build(), + "STREAMING-UNSIGNED-PAYLOAD-TRAILER", + new V4CanonicalRequest.Options(true, true) + ); + V4RequestSigningResult requestSigningResult = new V4RequestSigningResult( + "STREAMING-UNSIGNED-PAYLOAD-TRAILER", + "key".getBytes(StandardCharsets.UTF_8), + "sig", + canonicalRequest, + requestBuilder + ); + AwsChunkedV4PayloadSigner signer = AwsChunkedV4PayloadSigner.builder() + .credentialScope(credentialScope) + .chunkSize(chunkSize) + .checksumAlgorithm(SHA256) + .build(); + + signer.beforeSigning(requestBuilder, payload); + ContentStreamProvider signedPayload = signer.sign(payload, requestSigningResult); + + assertThat(requestBuilder.firstMatchingHeader("x-amz-decoded-content-length")).hasValue(Integer.toString(data.length)); + assertThat(requestBuilder.firstMatchingHeader("x-amz-trailer")).hasValue("x-amz-checksum-sha256"); + + byte[] tmp = new byte[1024]; + int actualBytes = readAll(signedPayload.newStream(), tmp); + + assertThat(requestBuilder.firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue(Integer.toString(actualBytes)); + assertEquals(expectedContent, new String(tmp, 0, actualBytes)); + } + + @Test + public void sign_shouldReturnResettableContentStreamProvider() throws IOException { + String expectedContent = + "4;chunk-signature=082f5b0e588893570e152b401a886161ee772ed066948f68c8f01aee11cca4f8\r\n{\"Ta\r\n" + + "4;chunk-signature=777b02ec61ce7934578b1efe6fbe08c21ae4a8cdf66a709d3b4fd320dddd2839\r\nbleN\r\n" + + "4;chunk-signature=84abdae650f64dee4d703d41c7d87c8bc251c22b8c493c75ce24431b60b73937\r\name\"\r\n" + + "4;chunk-signature=aff22ddad9d4388233fe9bc47e9c552a6e9ba9285af79555d2ce7fdaab726320\r\n: \"f\r\n" + + "4;chunk-signature=30e55f4e1c1fd444c06e9be42d9594b8fd7ead436bc67a58b5350ffd58b6aaa5\r\noo\"}\r\n" + + "0;chunk-signature=825ad80195cae47f54984835543ff2179c2c5a53c324059cd632e50259384ee3\r\n\r\n"; + + requestBuilder.putHeader("x-amz-content-sha256", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"); + V4CanonicalRequest canonicalRequest = new V4CanonicalRequest( + requestBuilder.build(), + "STREAMING-AWS4-HMAC-SHA256-PAYLOAD", + new V4CanonicalRequest.Options(true, true) + ); + V4RequestSigningResult requestSigningResult = new V4RequestSigningResult( + "STREAMING-AWS4-HMAC-SHA256-PAYLOAD", + "key".getBytes(StandardCharsets.UTF_8), + "sig", + canonicalRequest, + requestBuilder + ); + AwsChunkedV4PayloadSigner signer = AwsChunkedV4PayloadSigner.builder() + .credentialScope(credentialScope) + .chunkSize(chunkSize) + .build(); + + signer.beforeSigning(requestBuilder, payload); + ContentStreamProvider signedPayload = signer.sign(payload, requestSigningResult); + + // successive calls to newStream() should return a stream with the same data every time - this makes sure that state + // isn't carried over to the new streams returned by newStream() + byte[] tmp = new byte[1024]; + for (int i = 0; i < 2; i++) { + int actualBytes = readAll(signedPayload.newStream(), tmp); + assertEquals(expectedContent, new String(tmp, 0, actualBytes)); + } + } + + @Test + public void signAsync_throws() { + AwsChunkedV4PayloadSigner signer = AwsChunkedV4PayloadSigner.builder() + .credentialScope(credentialScope) + .chunkSize(chunkSize) + .build(); + + assertThrows(UnsupportedOperationException.class, () -> signer.signAsync(null, null)); + } + + private int readAll(InputStream src, byte[] dst) throws IOException { + int read = 0; + int offset = 0; + while (read >= 0) { + read = src.read(); + if (read >= 0) { + dst[offset] = (byte) read; + offset += 1; + } + } + return offset; + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultAwsV4HttpSignerTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultAwsV4HttpSignerTest.java new file mode 100644 index 000000000000..b2e5cdf93c8b --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultAwsV4HttpSignerTest.java @@ -0,0 +1,346 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.CRC32; +import static software.amazon.awssdk.http.auth.aws.TestUtils.generateBasicAsyncRequest; +import static software.amazon.awssdk.http.auth.aws.TestUtils.generateBasicRequest; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner.AUTH_LOCATION; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner.CHECKSUM_ALGORITHM; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner.CHUNK_ENCODING_ENABLED; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner.EXPIRATION_DURATION; +import static software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner.PAYLOAD_SIGNING_ENABLED; + +import java.net.URI; +import java.time.Duration; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import software.amazon.awssdk.http.Header; +import software.amazon.awssdk.http.auth.aws.TestUtils; +import software.amazon.awssdk.http.auth.aws.eventstream.internal.io.SigV4DataFramePublisher; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner.AuthLocation; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.ClassLoaderHelper; + +/** + * Test the delegation of signing to the correct implementations. + */ +// TODO(sra-identity-and-auth): missing tests for async code path +public class DefaultAwsV4HttpSignerTest { + + DefaultAwsV4HttpSigner signer = new DefaultAwsV4HttpSigner(); + + @Test + public void sign_WithNoAdditonalProperties_DelegatesToHeaderSigner() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> { + }, + signRequest -> { + } + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("Authorization")).isPresent(); + } + + @Test + public void sign_WithQueryAuthLocation_DelegatesToQuerySigner() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> { + }, + signRequest -> signRequest.putProperty(AUTH_LOCATION, AuthLocation.QUERY_STRING) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Signature")).isPresent(); + } + + @Test + public void sign_WithHeaderAuthLocationAndExpirationDuration_Throws() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> { + }, + signRequest -> signRequest + .putProperty(AUTH_LOCATION, AuthLocation.HEADER) + .putProperty(EXPIRATION_DURATION, Duration.ZERO) + ); + + assertThrows(UnsupportedOperationException.class, () -> signer.sign(request)); + } + + @Test + public void sign_withAnonymousCreds_shouldNotSign() { + SignRequest request = generateBasicRequest( + new TestUtils.AnonymousCredentialsIdentity(), + httpRequest -> { + }, + signRequest -> { + } + ); + + SignedRequest signedRequest = signer.sign(request); + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Signature")) + .isNotPresent(); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).isNotPresent(); + } + + @Test + public void sign_WithQueryAuthLocationAndExpiration_DelegatesToPresignedSigner() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> { + }, + signRequest -> signRequest + .putProperty(AUTH_LOCATION, AuthLocation.QUERY_STRING) + .putProperty(EXPIRATION_DURATION, Duration.ZERO) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingRawQueryParameter("X-Amz-Expires")).isPresent(); + } + + @Test + public void sign_WithPayloadSigningFalse_DelegatesToUnsignedPayloadSigner() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> { + }, + signRequest -> signRequest + .putProperty(PAYLOAD_SIGNING_ENABLED, false) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue("UNSIGNED-PAYLOAD"); + } + + @Test + public void sign_WithEventStreamContentTypeWithoutHttpAuthAwsEventStreamModule_throws() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest + .putHeader("Content-Type", "application/vnd.amazon.eventstream"), + signRequest -> { + } + ); + + try (MockedStatic utilities = Mockito.mockStatic(ClassLoaderHelper.class)) { + utilities.when(() ->ClassLoaderHelper.loadClass( + "software.amazon.awssdk.http.auth.aws.eventstream.HttpAuthAwsEventStream", + false) + ).thenThrow(new ClassNotFoundException("boom!")); + Exception e = assertThrows(RuntimeException.class, () -> signer.sign(request)); + assertThat(e).hasMessageContaining("http-auth-aws-eventstream"); + } + } + + @Test + public void sign_WithChunkEncodingTrue_DelegatesToAwsChunkedPayloadSigner() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest + .putHeader(Header.CONTENT_LENGTH, "20"), + signRequest -> signRequest + .putProperty(CHUNK_ENCODING_ENABLED, true) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")) + .hasValue("STREAMING-AWS4-HMAC-SHA256-PAYLOAD"); + Assertions.assertThat(signedRequest.request().firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue("193"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-decoded-content-length")).hasValue("20"); + } + + @Test + public void sign_WithChunkEncodingTrueAndChecksumAlgorithm_DelegatesToAwsChunkedPayloadSigner() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest + .putHeader(Header.CONTENT_LENGTH, "20"), + signRequest -> signRequest + .putProperty(CHUNK_ENCODING_ENABLED, true) + .putProperty(CHECKSUM_ALGORITHM, CRC32) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")) + .hasValue("STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER"); + Assertions.assertThat(signedRequest.request().firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue("314"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-decoded-content-length")).hasValue("20"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-trailer")).hasValue("x-amz-checksum-crc32"); + } + + @Test + public void sign_WithPayloadSigningFalseAndChunkEncodingTrueAndTrailer_DelegatesToAwsChunkedPayloadSigner() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest + .putHeader(Header.CONTENT_LENGTH, "20"), + signRequest -> signRequest + .putProperty(PAYLOAD_SIGNING_ENABLED, false) + .putProperty(CHUNK_ENCODING_ENABLED, true) + .putProperty(CHECKSUM_ALGORITHM, CRC32) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")) + .hasValue("STREAMING-UNSIGNED-PAYLOAD-TRAILER"); + Assertions.assertThat(signedRequest.request().firstMatchingHeader(Header.CONTENT_LENGTH)).hasValue("62"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-decoded-content-length")).hasValue("20"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-trailer")).hasValue("x-amz-checksum-crc32"); + } + + @Test + public void sign_WithPayloadSigningFalseAndChunkEncodingTrueWithoutTrailer_DelegatesToUnsignedPayload() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest + .putHeader(Header.CONTENT_LENGTH, "20"), + signRequest -> signRequest + .putProperty(PAYLOAD_SIGNING_ENABLED, false) + .putProperty(CHUNK_ENCODING_ENABLED, true) + ); + + SignedRequest signedRequest = signer.sign(request); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue("UNSIGNED-PAYLOAD"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-decoded-content-length")).isNotPresent(); + } + + @Test + public void sign_WithPayloadSigningFalseAndChunkEncodingTrueWithChecksumHeader_DelegatesToUnsignedPayload() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest + .putHeader(Header.CONTENT_LENGTH, "20") + .putHeader("x-amz-checksum-crc32", "bogus"), + signRequest -> signRequest + .putProperty(PAYLOAD_SIGNING_ENABLED, false) + .putProperty(CHUNK_ENCODING_ENABLED, true) + ); + + SignedRequest signedRequest = signer.sign(request); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue("UNSIGNED-PAYLOAD"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-checksum-crc32")).hasValue("bogus"); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-decoded-content-length")).isNotPresent(); + } + + @Test + public void sign_withChecksumAlgorithm_DelegatesToChecksummerWithThatChecksumAlgorithm() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> { + }, + signRequest -> signRequest.putProperty(CHECKSUM_ALGORITHM, CRC32) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("x-amz-checksum-crc32")).isPresent(); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).isPresent(); + } + + @Test + public void sign_withChecksumAlgorithmAndPayloadSigningFalse_DelegatesToChecksummerWithThatChecksumAlgorithm() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> { + }, + signRequest -> signRequest + .putProperty(CHECKSUM_ALGORITHM, CRC32) + .putProperty(PAYLOAD_SIGNING_ENABLED, false) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("x-amz-checksum-crc32")).isPresent(); + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue("UNSIGNED-PAYLOAD"); + } + + @Test + public void sign_WithEventStreamContentType_DelegatesToEventStreamPayloadSigner() { + AsyncSignRequest request = generateBasicAsyncRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest + .putHeader("Content-Type", "application/vnd.amazon.eventstream"), + signRequest -> { + } + ); + + AsyncSignedRequest signedRequest = signer.signAsync(request).join(); + + assertThat(signedRequest.payload().get()).isInstanceOf(SigV4DataFramePublisher.class); + } + + @Test + public void sign_WithEventStreamContentTypeAndUnsignedPayload_Throws() { + AsyncSignRequest request = generateBasicAsyncRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest + .putHeader("Content-Type", "application/vnd.amazon.eventstream"), + signRequest -> signRequest + .putProperty(PAYLOAD_SIGNING_ENABLED, false) + ); + + assertThrows(UnsupportedOperationException.class, () -> signer.signAsync(request)); + } + + @Test + public void sign_WithPayloadSigningFalseAndHttp_FallsBackToPayloadSigning() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest.uri(URI.create("http://demo.us-east-1.amazonaws.com")), + signRequest -> signRequest + .putProperty(PAYLOAD_SIGNING_ENABLED, false) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")) + .hasValue("a15c8292b1d12abbbbe4148605f7872fbdf645618fee5ab0e8072a7b34f155e2"); + } + + @Test + public void sign_WithPayloadSigningNullAndHttp_FallsBackToPayloadSigning() { + SignRequest request = generateBasicRequest( + AwsCredentialsIdentity.create("access", "secret"), + httpRequest -> httpRequest.uri(URI.create("http://demo.us-east-1.amazonaws.com")), + signRequest -> signRequest + .putProperty(PAYLOAD_SIGNING_ENABLED, null) + ); + + SignedRequest signedRequest = signer.sign(request); + + assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")) + .hasValue("a15c8292b1d12abbbbe4148605f7872fbdf645618fee5ab0e8072a7b34f155e2"); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultRequestSignerTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultRequestSignerTest.java new file mode 100644 index 000000000000..dfff39536aba --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/DefaultRequestSignerTest.java @@ -0,0 +1,67 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static java.time.ZoneOffset.UTC; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.awssdk.utils.BinaryUtils.toHex; + +import java.net.URI; +import java.time.Clock; +import java.time.Instant; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; + +public class DefaultRequestSignerTest { + + V4Properties v4Properties = V4Properties.builder() + .credentials(AwsCredentialsIdentity.create("foo", "bar")) + .credentialScope(new CredentialScope("baz", "qux", Instant.EPOCH)) + .signingClock(Clock.fixed(Instant.EPOCH, UTC)) + .doubleUrlEncode(true) + .normalizePath(true) + .build(); + + DefaultV4RequestSigner requestSigner = new DefaultV4RequestSigner(v4Properties, "quux"); + + @Test + public void requestSigner_sign_shouldReturnSignedResult_butNotAddAnyAuthInfoToRequest() { + SdkHttpRequest.Builder request = SdkHttpRequest + .builder() + .uri(URI.create("https://localhost")) + .method(SdkHttpMethod.GET) + .putHeader("Host", "localhost"); + String expectedContentHash = "quux"; + String expectedSigningKeyHex = "3d558b7a87b67996abc908071e0771a31b2a7977ab247144e60a6cba3356be1f"; + String expectedSignature = "6c1f4222e0888e6e68b20ded382bc80c7312465c69fb52cbd6d6ce2d073533bf"; + String expectedCanonicalRequestString = "GET\n/\n\n" + + "host:localhost\n\n" + + "host\nquux"; + String expectedHost = "localhost"; + + V4RequestSigningResult requestSigningResult = requestSigner.sign(request); + + assertEquals(expectedContentHash, requestSigningResult.getContentHash()); + assertEquals(expectedSigningKeyHex, toHex(requestSigningResult.getSigningKey())); + assertEquals(expectedSignature, requestSigningResult.getSignature()); + assertEquals(expectedCanonicalRequestString, requestSigningResult.getCanonicalRequest().getCanonicalRequestString()); + assertEquals(expectedHost, requestSigningResult.getSignedRequest().firstMatchingHeader("Host").orElse("")); + assertThat(requestSigningResult.getSignedRequest().build()).usingRecursiveComparison().isEqualTo(request.build()); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummerTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummerTest.java new file mode 100644 index 000000000000..cfb984f81c28 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummerTest.java @@ -0,0 +1,124 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.CRC32; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.SHA256; +import static software.amazon.awssdk.http.auth.aws.internal.signer.FlexibleChecksummer.option; + +import io.reactivex.Flowable; +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.utils.BinaryUtils; + +public class FlexibleChecksummerTest { + + ContentStreamProvider payload = () -> new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)); + Publisher payloadAsync = Flowable.just(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8))); + + + SdkHttpRequest.Builder request = SdkHttpRequest.builder() + .uri(URI.create("https://localhost")) + .method(SdkHttpMethod.GET); + + @Test + public void checksummer_withNoChecksums_shouldNotAddAnyChecksum() { + FlexibleChecksummer checksummer = new FlexibleChecksummer(); + SdkHttpRequest expectedRequest = request.build(); + + checksummer.checksum(payload, request); + + assertEquals(expectedRequest.headers(), request.build().headers()); + } + + @Test + public void checksummerAsync_withNoChecksums_shouldNotAddAnyChecksum() { + FlexibleChecksummer checksummer = new FlexibleChecksummer(); + SdkHttpRequest expectedRequest = request.build(); + + checksummer.checksum(payloadAsync, request); + + assertEquals(expectedRequest.headers(), request.build().headers()); + } + + @Test + public void checksummer_withOneChecksum_shouldAddOneChecksum() { + FlexibleChecksummer checksummer = new FlexibleChecksummer( + option().headerName("sha256").algorithm(SHA256).formatter(BinaryUtils::toHex).build() + ); + SdkHttpRequest expectedRequest = request + .putHeader("sha256", "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae") + .build(); + + checksummer.checksum(payload, request); + + assertEquals(expectedRequest.headers(), request.build().headers()); + } + + @Test + public void checksummerAsync_withOneChecksum_shouldAddOneChecksum() { + FlexibleChecksummer checksummer = new FlexibleChecksummer( + option().headerName("sha256").algorithm(SHA256).formatter(BinaryUtils::toBase64).build() + ); + SdkHttpRequest expectedRequest = request + .putHeader("sha256", "LCa0a2j/xo/5m0U8HTBBNBNCLXBkg7+g+YpeiGJm564=") + .build(); + + checksummer.checksum(payloadAsync, request); + + assertEquals(expectedRequest.headers(), request.build().headers()); + } + + @Test + public void checksummer_withTwoChecksums_shouldAddTwoChecksums() { + FlexibleChecksummer checksummer = new FlexibleChecksummer( + option().headerName("sha256").algorithm(SHA256).formatter(BinaryUtils::toHex).build(), + option().headerName("crc32").algorithm(CRC32).formatter(BinaryUtils::toBase64).build() + ); + SdkHttpRequest expectedRequest = request + .putHeader("sha256", "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae") + .putHeader("crc32", "jHNlIQ==") + .build(); + + checksummer.checksum(payload, request); + + assertEquals(expectedRequest.headers(), request.build().headers()); + } + + @Test + public void checksummerAsync_withTwoChecksums_shouldAddTwoChecksums() { + FlexibleChecksummer checksummer = new FlexibleChecksummer( + option().headerName("sha256").algorithm(SHA256).formatter(BinaryUtils::toBase64).build(), + option().headerName("crc32").algorithm(CRC32).formatter(BinaryUtils::toHex).build() + ); + SdkHttpRequest expectedRequest = request + .putHeader("sha256", "LCa0a2j/xo/5m0U8HTBBNBNCLXBkg7+g+YpeiGJm564=") + .putHeader("crc32", "8c736521") + .build(); + + checksummer.checksum(payloadAsync, request); + + assertEquals(expectedRequest.headers(), request.build().headers()); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/PrecomputedSha256ChecksummerTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/PrecomputedSha256ChecksummerTest.java new file mode 100644 index 000000000000..b91a176ff066 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/PrecomputedSha256ChecksummerTest.java @@ -0,0 +1,64 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.reactivex.Flowable; +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; + +public class PrecomputedSha256ChecksummerTest { + + ContentStreamProvider payload = () -> new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)); + Publisher payloadAsync = Flowable.just(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8))); + + + SdkHttpRequest.Builder request = SdkHttpRequest.builder() + .uri(URI.create("https://localhost")) + .method(SdkHttpMethod.GET); + + @Test + public void checksummer_shouldAddComputedValue() { + PrecomputedSha256Checksummer checksummer = new PrecomputedSha256Checksummer(() -> "something"); + SdkHttpRequest expectedRequest = request + .putHeader("x-amz-content-sha256", "something") + .build(); + + checksummer.checksum(payload, request); + + assertEquals(expectedRequest.toString(), request.build().toString()); + } + + @Test + public void checksummerAsync_shouldAddComputedValue() { + PrecomputedSha256Checksummer checksummer = new PrecomputedSha256Checksummer(() -> "something"); + SdkHttpRequest expectedRequest = request + .putHeader("x-amz-content-sha256", "something") + .build(); + + checksummer.checksum(payloadAsync, request); + + assertEquals(expectedRequest.toString(), request.build().toString()); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/RollingSignerTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/RollingSignerTest.java new file mode 100644 index 000000000000..37697c3df2e1 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/RollingSignerTest.java @@ -0,0 +1,53 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +public class RollingSignerTest { + + @Test + public void sign_shouldRollSignature() { + RollingSigner signer = new RollingSigner("key".getBytes(StandardCharsets.UTF_8), "seed"); + + String first = signer.sign(signature -> signature + "a"); + String second = signer.sign(signature -> signature + "b"); + String third = signer.sign(signature -> signature + "c"); + + assertEquals("878b44846ec83a43c3932578c5311fa8b289ae194ccc0c06244c366f3b949012", first); + assertEquals("08f47cc56d186cee34fa7662128d5c9187eb9483938df45282e922d902e85a27", second); + assertEquals("10da3ca4a393be5f8368823cb73a777426db24428bd8374fbf54a64a808ff00a", third); + } + + @Test + public void reset_shouldResetSeed() { + RollingSigner signer = new RollingSigner("key".getBytes(StandardCharsets.UTF_8), "seed"); + + String first = signer.sign(signature -> signature + "a"); + String second = signer.sign(signature -> signature + "b"); + + signer.reset(); + + String firstAgain = signer.sign(signature -> signature + "a"); + + assertEquals("878b44846ec83a43c3932578c5311fa8b289ae194ccc0c06244c366f3b949012", first); + assertEquals("08f47cc56d186cee34fa7662128d5c9187eb9483938df45282e922d902e85a27", second); + assertEquals("878b44846ec83a43c3932578c5311fa8b289ae194ccc0c06244c366f3b949012", firstAgain); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequestTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequestTest.java new file mode 100644 index 000000000000..2b24a8dfca25 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequestTest.java @@ -0,0 +1,189 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.utils.ToString; + +/** + * Tests how canonical resource paths are created including normalization + */ +public class V4CanonicalRequestTest { + public static Iterable data() { + return Arrays.asList( + // Handling slash + tc("Empty path -> (initial) slash added", "", "/"), + tc("Slash -> unchanged", "/", "/"), + tc("Single segment with initial slash -> unchanged", "/foo", "/foo"), + tc("Single segment no slash -> slash prepended", "foo", "/foo"), + tc("Multiple segments -> unchanged", "/foo/bar", "/foo/bar"), + tc("Multiple segments with trailing slash -> unchanged", "/foo/bar/", "/foo/bar/"), + + // Double URL encoding + tc("Multiple segments, urlEncoded slash -> encodes percent", "/foo%2Fbar", "/foo%252Fbar", true, true), + + // No double-url-encoding + normalization + tc("Single segment, dot -> should remove dot", "/.", "/"), + tc("Single segment, double dot -> unchanged", "/..", "/.."), + tc("Multiple segments with dot -> should remove dot", "/foo/./bar", "/foo/bar"), + tc("Multiple segments with ending dot -> should remove dot and trailing slash", "/foo/bar/.", "/foo/bar"), + tc("Multiple segments with dots -> should remove dots and preceding segment", "/foo/bar/../baz", "/foo/baz"), + tc("First segment has colon -> unchanged, url encoded first", "foo:/bar", "/foo%3A/bar", true, true), + + // Double-url-encoding + normalization + tc("Multiple segments, urlEncoded slash -> encodes percent", "/foo%2F.%2Fbar", "/foo%252F.%252Fbar", true, true), + + // Double-url-encoding + no normalization + tc("No url encode, Multiple segments with dot -> unchanged", "/foo/./bar", "/foo/./bar", false, false), + tc("Multiple segments with dots -> unchanged", "/foo/bar/../baz", "/foo/bar/../baz", false, false) + ); + } + + private static TestCase tc(String name, String path, String expectedPath) { + return new TestCase(name, path, expectedPath, false, true); + } + + private static TestCase tc(String name, String path, String expectedPath, boolean urlEncode, boolean normalizePath) { + return new TestCase(name, path, expectedPath, urlEncode, normalizePath); + } + + @ParameterizedTest + @MethodSource("data") + public void verifyNormalizedPath(TestCase tc) { + String canonicalRequest = tc.canonicalRequest.getCanonicalRequestString(); + String[] requestParts = canonicalRequest.split("\\n"); + String canonicalPath = requestParts[1]; + assertEquals(tc.expectedPath, canonicalPath); + } + + @Test + public void canonicalRequest_WithForbiddenHeaders_shouldExcludeForbidden() { + SdkHttpRequest request = SdkHttpRequest.builder() + .protocol("https") + .host("localhost") + .method(SdkHttpMethod.PUT) + .putHeader("foo", "bar") + .putHeader("x-amzn-trace-id", "wontBePresent") + .build(); + V4CanonicalRequest cr = new V4CanonicalRequest(request, "sha-256", + new V4CanonicalRequest.Options(true, + true)); + + assertEquals("PUT\n/\n\nfoo:bar\n\nfoo\nsha-256", cr.getCanonicalRequestString()); + } + + @Test + public void canonicalRequest_WithMultiValueHeaders_shouldCommaSeparateValues() { + SdkHttpRequest request = SdkHttpRequest.builder() + .protocol("https") + .host("localhost") + .method(SdkHttpMethod.PUT) + .appendHeader("foo", "bar") + .appendHeader("foo", "baz") + .build(); + V4CanonicalRequest cr = new V4CanonicalRequest(request, "sha-256", + new V4CanonicalRequest.Options(true, + true)); + + assertEquals("PUT\n/\n\nfoo:bar,baz\n\nfoo\nsha-256", cr.getCanonicalRequestString()); + } + + @Test + public void canonicalRequest_withSpacedHeaders_shouldStripWhitespace() { + SdkHttpRequest request = SdkHttpRequest.builder() + .protocol("https") + .host("localhost") + .method(SdkHttpMethod.PUT) + .putHeader("foo", " bar baz ") + .build(); + V4CanonicalRequest cr = new V4CanonicalRequest(request, "sha-256", + new V4CanonicalRequest.Options(true, + true)); + + assertEquals("PUT\n/\n\nfoo:bar baz\n\nfoo\nsha-256", cr.getCanonicalRequestString()); + } + + @Test + public void canonicalRequest_WithNullParamValue_shouldIncludeEquals() { + SdkHttpRequest request = SdkHttpRequest.builder() + .protocol("https") + .host("localhost") + .method(SdkHttpMethod.PUT) + .putRawQueryParameter("foo", (String) null) + .build(); + V4CanonicalRequest cr = new V4CanonicalRequest(request, "sha-256", + new V4CanonicalRequest.Options(true, + true)); + + assertEquals("PUT\n/\nfoo=\n\n\nsha-256", cr.getCanonicalRequestString()); + } + + @Test + public void canonicalRequest_WithEmptyParamKey_shouldExcludeParam() { + SdkHttpRequest request = SdkHttpRequest.builder() + .protocol("https") + .host("localhost") + .method(SdkHttpMethod.PUT) + .putRawQueryParameter("", "") + .build(); + V4CanonicalRequest cr = new V4CanonicalRequest(request, "sha-256", + new V4CanonicalRequest.Options(true, + true)); + + assertEquals("PUT\n/\n\n\n\nsha-256", cr.getCanonicalRequestString()); + } + + private static class TestCase { + private final String name; + private final String path; + private final String expectedPath; + private final V4CanonicalRequest canonicalRequest; + + private TestCase(String name, + String path, + String expectedPath, + boolean doubleUrlEncode, + boolean normalizePath) { + SdkHttpRequest request = SdkHttpRequest.builder() + .protocol("https") + .host("localhost") + .encodedPath(path) + .method(SdkHttpMethod.PUT) + .build(); + this.name = name; + this.path = path; + this.expectedPath = expectedPath; + this.canonicalRequest = new V4CanonicalRequest(request, "sha-256", + new V4CanonicalRequest.Options(doubleUrlEncode, normalizePath)); + } + + @Override + public String toString() { + return ToString.builder("TestCase") + .add("name", name) + .add("path", path) + .add("expectedPath", expectedPath) + .build(); + } + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4RequestSignerTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4RequestSignerTest.java new file mode 100644 index 000000000000..536631c5d18f --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4RequestSignerTest.java @@ -0,0 +1,163 @@ +/* + * 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.http.auth.aws.internal.signer; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static software.amazon.awssdk.http.auth.aws.TestUtils.TickingClock; +import static software.amazon.awssdk.http.auth.aws.internal.signer.V4RequestSigner.header; +import static software.amazon.awssdk.http.auth.aws.internal.signer.V4RequestSigner.presigned; +import static software.amazon.awssdk.http.auth.aws.internal.signer.V4RequestSigner.query; + +import java.net.URI; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; + + +public class V4RequestSignerTest { + + private static final AwsCredentialsIdentity creds = + AwsCredentialsIdentity.create("access", "secret"); + private static final AwsSessionCredentialsIdentity sessionCreds = + AwsSessionCredentialsIdentity.create("access", "secret", "token"); + + @Test + public void sign_computesSigningResult() { + String expectedSignature = "4c7049543386ba32bc85e7a7d7b892e7da1c412abf3508ca84775ed099790acf"; + String expectedContentHash = "abc123"; + String expectedCanonicalRequestString = "GET\n" + + "/./foo\n\n" + + "x-amz-archive-description:test test\n" + + "x-amz-content-sha256:checksum\n\n" + + "x-amz-archive-description;x-amz-content-sha256\n" + + "abc123"; + V4RequestSigningResult result = V4RequestSigner.create(getProperties(creds), "abc123").sign(getRequest()); + + assertEquals(expectedSignature, result.getSignature()); + assertEquals(expectedContentHash, result.getContentHash()); + assertEquals(expectedCanonicalRequestString, result.getCanonicalRequest().getCanonicalRequestString()); + } + + @Test + public void sign_withHeader_addsAuthHeaders() { + String expectedAuthorization = "AWS4-HMAC-SHA256 Credential=access/19700101/us-east-1/demo/aws4_request, " + + "SignedHeaders=host;x-amz-archive-description;x-amz-content-sha256;x-amz-date, " + + "Signature=0fafd04465eb6201e868a80f72d15d50731512298f554684ce6627c0619f429a"; + V4RequestSigningResult result = header(getProperties(creds)).sign(getRequest()); + + assertThat(result.getSignedRequest().firstMatchingHeader("X-Amz-Date")).hasValue("19700101T000000Z"); + assertThat(result.getSignedRequest().firstMatchingHeader("Authorization")).hasValue(expectedAuthorization); + } + + @Test + public void sign_withHeaderAndSessionCredentials_addsAuthHeadersAndTokenHeader() { + String expectedAuthorization = "AWS4-HMAC-SHA256 Credential=access/19700101/us-east-1/demo/aws4_request, " + + "SignedHeaders=host;x-amz-archive-description;x-amz-content-sha256;x-amz-date;" + + "x-amz-security-token, " + + "Signature=cda79272f6d258c2cb2f04ac84a5f9515440e0158bf39e212c3dcf88b3a477a9"; + V4RequestSigningResult result = header(getProperties(sessionCreds)).sign(getRequest()); + + assertThat(result.getSignedRequest().firstMatchingHeader("X-Amz-Date")).hasValue("19700101T000000Z"); + assertThat(result.getSignedRequest().firstMatchingHeader("Authorization")).hasValue(expectedAuthorization); + assertThat(result.getSignedRequest().firstMatchingHeader("X-Amz-Security-Token")).hasValue("token"); + } + + @Test + public void sign_withQuery_addsAuthQueryParams() { + V4RequestSigningResult result = query(getProperties(creds)).sign(getRequest()); + + assertEquals("AWS4-HMAC-SHA256", result.getSignedRequest().rawQueryParameters().get("X-Amz-Algorithm").get(0)); + assertEquals("19700101T000000Z", result.getSignedRequest().rawQueryParameters().get("X-Amz-Date").get(0)); + assertEquals("host;x-amz-archive-description;x-amz-content-sha256", + result.getSignedRequest().rawQueryParameters().get("X-Amz-SignedHeaders").get(0)); + assertEquals("access/19700101/us-east-1/demo/aws4_request", result.getSignedRequest().rawQueryParameters().get( + "X-Amz-Credential").get(0)); + assertEquals("bb3ddb98bc32b85c8aa484bfaf321171a22ad802baa03ee9d5fcda9842b769c9", + result.getSignedRequest().rawQueryParameters().get("X-Amz-Signature").get(0)); + } + + @Test + public void sign_withQueryAndSessionCredentials_addsAuthQueryParamsAndTokenParam() { + V4RequestSigningResult result = query(getProperties(sessionCreds)).sign(getRequest()); + + assertEquals("AWS4-HMAC-SHA256", result.getSignedRequest().rawQueryParameters().get("X-Amz-Algorithm").get(0)); + assertEquals("19700101T000000Z", result.getSignedRequest().rawQueryParameters().get("X-Amz-Date").get(0)); + assertEquals("host;x-amz-archive-description;x-amz-content-sha256", + result.getSignedRequest().rawQueryParameters().get("X-Amz-SignedHeaders").get(0)); + assertEquals( + "access/19700101/us-east-1/demo/aws4_request", + result.getSignedRequest().rawQueryParameters().get("X-Amz-Credential").get(0)); + assertEquals("2ffe9562fefd57e14f43bf1937b6b85cc0f0180d63789254bddec25498e14a29", + result.getSignedRequest().rawQueryParameters().get("X-Amz-Signature").get(0)); + assertEquals("token", result.getSignedRequest().rawQueryParameters().get("X-Amz-Security-Token").get(0)); + } + + @Test + public void sign_withPresigned_addsExpirationParam() { + V4RequestSigningResult result = presigned(getProperties(creds), Duration.ZERO).sign(getRequest()); + + assertEquals("0", result.getSignedRequest().rawQueryParameters().get("X-Amz-Expires").get(0)); + assertEquals("host;x-amz-archive-description", + result.getSignedRequest().rawQueryParameters().get("X-Amz-SignedHeaders").get(0)); + assertEquals("691f39caa2064fe4fb897976dfb4b09df54749c825a5fcd1e2f0b3fcd1bcc600", result.getSignature()); + } + + @Test + public void sign_withHeader_withNoContentHashHeader_throws() { + SdkHttpRequest.Builder request = getRequest().removeHeader("x-amz-content-sha256"); + + assertThrows(IllegalArgumentException.class, + () -> header(getProperties(sessionCreds)).sign(request) + ); + } + + @Test + public void sign_withQuery_withNoContentHashHeader_throws() { + SdkHttpRequest.Builder request = getRequest().removeHeader("x-amz-content-sha256"); + + assertThrows(IllegalArgumentException.class, + () -> query(getProperties(sessionCreds)).sign(request) + ); + } + + private V4Properties getProperties(AwsCredentialsIdentity creds) { + Clock clock = new TickingClock(Instant.EPOCH); + return V4Properties.builder() + .credentials(creds) + .credentialScope(new CredentialScope("us-east-1", "demo", clock.instant())) + .signingClock(clock) + .doubleUrlEncode(false) + .normalizePath(false) + .build(); + } + + private SdkHttpRequest.Builder getRequest() { + URI target = URI.create("https://test.com/./foo"); + return SdkHttpRequest.builder() + .method(SdkHttpMethod.GET) + .uri(target) + .encodedPath(target.getPath()) + .putHeader("x-amz-content-sha256", "checksum") + .putHeader("x-amz-archive-description", "test test"); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkedEncodedInputStreamTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkedEncodedInputStreamTest.java new file mode 100644 index 000000000000..9c207feada8c --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkedEncodedInputStreamTest.java @@ -0,0 +1,327 @@ +/* + * 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.http.auth.aws.internal.signer.chunkedencoding; + +import static java.util.Arrays.copyOf; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.awssdk.http.auth.aws.internal.signer.V4CanonicalRequest.getCanonicalHeadersString; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.deriveSigningKey; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.hash; +import static software.amazon.awssdk.utils.BinaryUtils.toHex; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.http.auth.aws.internal.signer.RollingSigner; +import software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.Pair; + +public class ChunkedEncodedInputStreamTest { + + @Test + public void ChunkEncodedInputStream_withBasicParams_returnsEncodedChunks() throws IOException { + byte[] data = "abcdefghij".getBytes(); + InputStream payload = new ByteArrayInputStream(data); + int chunkSize = 3; + + ChunkedEncodedInputStream inputStream = ChunkedEncodedInputStream + .builder() + .inputStream(payload) + .chunkSize(chunkSize) + .header(chunk -> Integer.toHexString(chunk.length).getBytes()) + .build(); + + byte[] tmp = new byte[64]; + int bytesRead = readAll(inputStream, tmp); + + int expectedBytesRead = 35; + byte[] expected = new byte[expectedBytesRead]; + System.arraycopy( + "3\r\nabc\r\n3\r\ndef\r\n3\r\nghi\r\n1\r\nj\r\n0\r\n\r\n".getBytes(), + 0, + expected, + 0, + expectedBytesRead + ); + byte[] actual = copyOf(tmp, bytesRead); + + assertEquals(expectedBytesRead, bytesRead); + assertArrayEquals(expected, actual); + } + + @Test + public void ChunkEncodedInputStream_withExtensions_returnsEncodedExtendedChunks() throws IOException { + byte[] data = "abcdefghij".getBytes(); + InputStream payload = new ByteArrayInputStream(data); + int chunkSize = 3; + + ChunkExtensionProvider helloWorldExt = chunk -> Pair.of( + "hello".getBytes(StandardCharsets.UTF_8), + "world!".getBytes(StandardCharsets.UTF_8) + ); + + ChunkedEncodedInputStream inputStream = ChunkedEncodedInputStream + .builder() + .inputStream(payload) + .chunkSize(chunkSize) + .header(chunk -> Integer.toHexString(chunk.length).getBytes()) + .extensions(Collections.singletonList(helloWorldExt)) + .build(); + + byte[] tmp = new byte[128]; + int bytesRead = readAll(inputStream, tmp); + + int expectedBytesRead = 100; + byte[] expected = new byte[expectedBytesRead]; + System.arraycopy( + ("3;hello=world!\r\nabc\r\n3;hello=world!\r\ndef\r\n3;hello=world!\r\nghi\r\n" + + "1;hello=world!\r\nj\r\n0;hello=world!\r\n\r\n").getBytes(), + 0, + expected, + 0, + expectedBytesRead + ); + byte[] actual = copyOf(tmp, expected.length); + + assertEquals(expectedBytesRead, bytesRead); + assertArrayEquals(expected, actual); + } + + @Test + public void ChunkEncodedInputStream_withTrailers_returnsEncodedChunksAndTrailerChunk() throws IOException { + byte[] data = "abcdefghij".getBytes(); + InputStream payload = new ByteArrayInputStream(data); + int chunkSize = 3; + + TrailerProvider helloWorldTrailer = () -> Pair.of( + "hello", + Collections.singletonList("world!") + ); + + ChunkedEncodedInputStream inputStream = ChunkedEncodedInputStream + .builder() + .inputStream(payload) + .chunkSize(chunkSize) + .header(chunk -> Integer.toHexString(chunk.length).getBytes()) + .trailers(Collections.singletonList(helloWorldTrailer)) + .build(); + + byte[] tmp = new byte[64]; + int bytesRead = readAll(inputStream, tmp); + + int expectedBytesRead = 49; + byte[] expected = new byte[expectedBytesRead]; + System.arraycopy( + "3\r\nabc\r\n3\r\ndef\r\n3\r\nghi\r\n1\r\nj\r\n0\r\nhello:world!\r\n\r\n".getBytes(), + 0, + expected, + 0, + expectedBytesRead + ); + byte[] actual = copyOf(tmp, expected.length); + + assertEquals(expectedBytesRead, bytesRead); + assertArrayEquals(expected, actual); + } + + @Test + public void ChunkEncodedInputStream_withExtensionsAndTrailers_EncodedExtendedChunksAndTrailerChunk() throws IOException { + byte[] data = "abcdefghij".getBytes(); + InputStream payload = new ByteArrayInputStream(data); + int chunkSize = 3; + + ChunkExtensionProvider aExt = chunk -> Pair.of("a".getBytes(StandardCharsets.UTF_8), + "1".getBytes(StandardCharsets.UTF_8)); + ChunkExtensionProvider bExt = chunk -> Pair.of("b".getBytes(StandardCharsets.UTF_8), + "2".getBytes(StandardCharsets.UTF_8)); + + TrailerProvider aTrailer = () -> Pair.of("a", Collections.singletonList("1")); + TrailerProvider bTrailer = () -> Pair.of("b", Collections.singletonList("2")); + + ChunkedEncodedInputStream inputStream = ChunkedEncodedInputStream + .builder() + .inputStream(payload) + .chunkSize(chunkSize) + .header(chunk -> Integer.toHexString(chunk.length).getBytes()) + .addExtension(aExt) + .addExtension(bExt) + .addTrailer(aTrailer) + .addTrailer(bTrailer) + .build(); + + byte[] tmp = new byte[128]; + int bytesRead = readAll(inputStream, tmp); + + int expectedBytesRead = 85; + byte[] expected = new byte[expectedBytesRead]; + System.arraycopy( + "3;a=1;b=2\r\nabc\r\n3;a=1;b=2\r\ndef\r\n3;a=1;b=2\r\nghi\r\n1;a=1;b=2\r\nj\r\n0;a=1;b=2\r\na:1\r\nb:2\r\n\r\n".getBytes(), + 0, + expected, + 0, + expectedBytesRead + ); + byte[] actual = copyOf(tmp, expected.length); + + assertEquals(expectedBytesRead, bytesRead); + assertArrayEquals(expected, actual); + } + + @Test + public void ChunkEncodedInputStream_withAwsParams_returnsAwsSignedAndEncodedChunks() throws IOException { + byte[] data = new byte[65 * 1024]; + Arrays.fill(data, (byte) 'a'); + String seedSignature = "106e2a8a18243abcf37539882f36619c00e2dfc72633413f02d3b74544bfeb8e"; + CredentialScope credentialScope = + new CredentialScope("us-east-1", "s3", Instant.parse("2013-05-24T00:00:00Z")); + AwsCredentialsIdentity credentials = + AwsCredentialsIdentity.create("AKIAIOSFODNN7EXAMPLE", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"); + byte[] signingKey = deriveSigningKey(credentials, credentialScope); + InputStream payload = new ByteArrayInputStream(data); + int chunkSize = 64 * 1024; + + RollingSigner signer = new RollingSigner(signingKey, seedSignature); + + ChunkExtensionProvider ext = chunk -> Pair.of( + "chunk-signature".getBytes(StandardCharsets.UTF_8), + signer.sign(previousSignature -> + "AWS4-HMAC-SHA256-PAYLOAD" + SignerConstant.LINE_SEPARATOR + + credentialScope.getDatetime() + SignerConstant.LINE_SEPARATOR + + credentialScope.scope() + SignerConstant.LINE_SEPARATOR + + previousSignature + SignerConstant.LINE_SEPARATOR + + toHex(hash("")) + SignerConstant.LINE_SEPARATOR + + toHex(hash(chunk))) + .getBytes(StandardCharsets.UTF_8) + ); + + TrailerProvider checksumTrailer = () -> Pair.of( + "x-amz-checksum-crc32c", + Collections.singletonList("wdBDMA==") + ); + + List>> trailers = Collections.singletonList(checksumTrailer.get()); + Function template = + previousSignature -> + "AWS4-HMAC-SHA256-TRAILER" + SignerConstant.LINE_SEPARATOR + + credentialScope.getDatetime() + SignerConstant.LINE_SEPARATOR + + credentialScope.scope() + SignerConstant.LINE_SEPARATOR + + previousSignature + SignerConstant.LINE_SEPARATOR + + toHex(hash(getCanonicalHeadersString(trailers))); + + TrailerProvider signatureTrailer = () -> Pair.of( + "x-amz-trailer-signature", + Collections.singletonList(signer.sign(template)) + ); + + ChunkedEncodedInputStream inputStream = ChunkedEncodedInputStream + .builder() + .inputStream(payload) + .chunkSize(chunkSize) + .header(chunk -> Integer.toHexString(chunk.length).getBytes()) + .extensions(Collections.singletonList(ext)) + .trailers(Arrays.asList(checksumTrailer, signatureTrailer)) + .build(); + + byte[] tmp = new byte[chunkSize * 4]; + int bytesRead = readAll(inputStream, tmp); + + int expectedBytesRead = 66946; + byte[] actualBytes = copyOf(tmp, expectedBytesRead); + ByteArrayOutputStream expected = new ByteArrayOutputStream(); + expected.write( + "10000;chunk-signature=b474d8862b1487a5145d686f57f013e54db672cee1c953b3010fb58501ef5aa2\r\n".getBytes( + StandardCharsets.UTF_8) + ); + expected.write(data, 0, chunkSize); + expected.write( + "\r\n400;chunk-signature=1c1344b170168f8e65b41376b44b20fe354e373826ccbbe2c1d40a8cae51e5c7\r\n".getBytes( + StandardCharsets.UTF_8) + ); + expected.write(data, chunkSize, 1024); + expected.write( + "\r\n0;chunk-signature=2ca2aba2005185cf7159c6277faf83795951dd77a3a99e6e65d5c9f85863f992\r\n".getBytes( + StandardCharsets.UTF_8) + ); + expected.write(( + "x-amz-checksum-crc32c:wdBDMA==\r\n" + + "x-amz-trailer-signature:ce306fa4cdf73aa89071b78358f0d22ea79c43117314c8ed68017f7d6f91048e\r\n" + + "\r\n").getBytes(StandardCharsets.UTF_8) + ); + + assertEquals(expectedBytesRead, bytesRead); + assertArrayEquals(expected.toByteArray(), actualBytes); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 5, 8, 13, 21, 24, 45, 69, 104}) + void ChunkEncodedInputStream_withVariableChunkSize_shouldCorrectlyChunkData(int chunkSize) throws IOException { + int size = 100; + byte[] data = new byte[size]; + Arrays.fill(data, (byte) 'a'); + + ChunkedEncodedInputStream inputStream = ChunkedEncodedInputStream + .builder() + .inputStream(new ByteArrayInputStream(data)) + .header(chunk -> new byte[] {'0'}) + .chunkSize(chunkSize) + .build(); + + int expectedBytesRead = 0; + int numChunks = size / chunkSize; + + // 0\r\n\r\n + expectedBytesRead += numChunks * (5 + chunkSize); + + if (size % chunkSize != 0) { + // 0\r\n\\r\n + expectedBytesRead += 5 + (size % chunkSize); + } + + // 0\r\n\r\n + expectedBytesRead += 5; + + byte[] tmp = new byte[expectedBytesRead]; + int bytesRead = readAll(inputStream, tmp); + + assertEquals(expectedBytesRead, bytesRead); + } + + private int readAll(InputStream src, byte[] dst) throws IOException { + int read = 0; + int offset = 0; + while (read >= 0) { + read = src.read(); + if (read >= 0) { + dst[offset] = (byte) read; + offset += 1; + } + } + return offset; + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumInputStreamTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumInputStreamTest.java new file mode 100644 index 000000000000..1aa9157213d3 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumInputStreamTest.java @@ -0,0 +1,66 @@ +/* + * 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.http.auth.aws.internal.signer.io; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.readAll; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Crc32Checksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.SdkChecksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Sha256Checksum; +import software.amazon.awssdk.utils.BinaryUtils; + +public class ChecksumInputStreamTest { + + @Test + public void read_computesCorrectSha256() { + String testString = "AWS SDK for Java"; + String expectedDigest = "004c6bbd87e7fe70109b3bc23c8b1ab8f18a8bede0ed38c9233f6cdfd4f7b5d6"; + + ByteArrayInputStream backingStream = new ByteArrayInputStream(testString.getBytes(StandardCharsets.UTF_8)); + SdkChecksum checksum = new Sha256Checksum(); + ChecksumInputStream inputStream = new ChecksumInputStream(backingStream, Collections.singleton(checksum)); + + readAll(inputStream); + String computedDigest = BinaryUtils.toHex(checksum.getChecksumBytes()); + + assertThat(computedDigest).isEqualTo(expectedDigest); + } + + @Test + public void read_withMultipleChecksums_shouldComputeCorrectChecksums() { + String testString = "AWS SDK for Java"; + String expectedSha256Digest = "004c6bbd87e7fe70109b3bc23c8b1ab8f18a8bede0ed38c9233f6cdfd4f7b5d6"; + String expectedCrc32Digest = "4ac37ece"; + + ByteArrayInputStream backingStream = new ByteArrayInputStream(testString.getBytes(StandardCharsets.UTF_8)); + SdkChecksum sha256Checksum = new Sha256Checksum(); + SdkChecksum crc32Checksum = new Crc32Checksum(); + ChecksumInputStream inputStream = new ChecksumInputStream(backingStream, Arrays.asList(sha256Checksum, crc32Checksum)); + + readAll(inputStream); + String computedSha256Digest = BinaryUtils.toHex(sha256Checksum.getChecksumBytes()); + String computedCrc32Digest = BinaryUtils.toHex(crc32Checksum.getChecksumBytes()); + + assertThat(computedSha256Digest).isEqualTo(expectedSha256Digest); + assertThat(computedCrc32Digest).isEqualTo(expectedCrc32Digest); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriberTckTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriberTckTest.java new file mode 100644 index 000000000000..35ebf85e03f3 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriberTckTest.java @@ -0,0 +1,43 @@ +/* + * 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.http.auth.aws.internal.signer.io; + +import java.nio.ByteBuffer; +import java.util.Collections; +import org.reactivestreams.Subscriber; +import org.reactivestreams.tck.TestEnvironment; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Sha256Checksum; + +/** + * TCK verifiation test for {@link ChecksumSubscriber}. + */ +public class ChecksumSubscriberTckTest extends org.reactivestreams.tck.SubscriberBlackboxVerification { + + public ChecksumSubscriberTckTest() { + super(new TestEnvironment()); + } + + @Override + public Subscriber createSubscriber() { + return new ChecksumSubscriber(Collections.singleton(new Sha256Checksum())); + } + + @Override + public ByteBuffer createElement(int i) { + return ByteBuffer.wrap(String.valueOf(i).getBytes()); + } + +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriberTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriberTest.java new file mode 100644 index 000000000000..a0a0ccd52338 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriberTest.java @@ -0,0 +1,102 @@ +/* + * 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.http.auth.aws.internal.signer.io; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static software.amazon.awssdk.utils.CompletableFutureUtils.joinLikeSync; + +import io.reactivex.Flowable; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Subscription; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Crc32Checksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.SdkChecksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Sha256Checksum; +import software.amazon.awssdk.utils.BinaryUtils; + +public class ChecksumSubscriberTest { + + @Test + public void checksum_computesCorrectSha256() { + String testString = "AWS SDK for Java"; + String expectedDigest = "004c6bbd87e7fe70109b3bc23c8b1ab8f18a8bede0ed38c9233f6cdfd4f7b5d6"; + + SdkChecksum checksum = new Sha256Checksum(); + ChecksumSubscriber subscriber = new ChecksumSubscriber(Collections.singleton(checksum)); + Flowable publisher = Flowable.just(ByteBuffer.wrap(testString.getBytes(StandardCharsets.UTF_8))); + publisher.subscribe(subscriber); + + joinLikeSync(subscriber.checksum()); + String computedDigest = BinaryUtils.toHex(checksum.getChecksumBytes()); + + assertThat(computedDigest).isEqualTo(expectedDigest); + } + + @Test + public void checksum_withMultipleChecksums_shouldComputeCorrectChecksums() { + String testString = "AWS SDK for Java"; + String expectedSha256Digest = "004c6bbd87e7fe70109b3bc23c8b1ab8f18a8bede0ed38c9233f6cdfd4f7b5d6"; + String expectedCrc32Digest = "4ac37ece"; + + SdkChecksum sha256Checksum = new Sha256Checksum(); + SdkChecksum crc32Checksum = new Crc32Checksum(); + ChecksumSubscriber subscriber = new ChecksumSubscriber(Arrays.asList(sha256Checksum, crc32Checksum)); + Flowable publisher = Flowable.just(ByteBuffer.wrap(testString.getBytes(StandardCharsets.UTF_8))); + publisher.subscribe(subscriber); + + joinLikeSync(subscriber.checksum()); + String computedSha256Digest = BinaryUtils.toHex(sha256Checksum.getChecksumBytes()); + String computedCrc32Digest = BinaryUtils.toHex(crc32Checksum.getChecksumBytes()); + + assertThat(computedSha256Digest).isEqualTo(expectedSha256Digest); + assertThat(computedCrc32Digest).isEqualTo(expectedCrc32Digest); + } + + @Test + public void checksum_futureCancelledBeforeSubscribe_cancelsSubscription() { + Subscription mockSubscription = mock(Subscription.class); + + ChecksumSubscriber subscriber = new ChecksumSubscriber(Collections.emptyList()); + + subscriber.checksum().cancel(true); + + subscriber.onSubscribe(mockSubscription); + + verify(mockSubscription).cancel(); + verify(mockSubscription, times(0)).request(anyLong()); + } + + @Test + public void checksum_publisherCallsOnError_errorPropagatedToFuture() { + Subscription mockSubscription = mock(Subscription.class); + + ChecksumSubscriber subscriber = new ChecksumSubscriber(Collections.emptyList()); + subscriber.onSubscribe(mockSubscription); + + RuntimeException error = new RuntimeException("error"); + subscriber.onError(error); + + assertThatThrownBy(subscriber.checksum()::join).hasCause(error); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChunkInputStreamTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChunkInputStreamTest.java new file mode 100644 index 000000000000..2bd199af21b3 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChunkInputStreamTest.java @@ -0,0 +1,37 @@ +/* + * 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.http.auth.aws.internal.signer.io; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.http.auth.aws.internal.signer.chunkedencoding.ChunkInputStream; + +public class ChunkInputStreamTest { + + @Test + public void close_shouldDrainChunk() throws IOException { + ByteArrayInputStream backingStream = new ByteArrayInputStream(new byte[] {'a', 'b', 'c', 'd', 'e', 'f', 'g'}); + ChunkInputStream in = new ChunkInputStream(backingStream, 5); + + in.close(); + + assertEquals(0, in.remaining()); + assertEquals(2, backingStream.available()); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/ChecksumUtilTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/ChecksumUtilTest.java new file mode 100644 index 000000000000..a607902ac977 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/ChecksumUtilTest.java @@ -0,0 +1,66 @@ +/* + * 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.http.auth.aws.internal.signer.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.CRC32; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.CRC32C; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.MD5; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.SHA1; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.SHA256; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.checksumHeaderName; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.fromChecksumAlgorithm; +import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.readAll; + +import java.io.ByteArrayInputStream; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Crc32CChecksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Crc32Checksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Md5Checksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Sha1Checksum; +import software.amazon.awssdk.http.auth.aws.internal.signer.checksums.Sha256Checksum; + +public class ChecksumUtilTest { + + @Test + public void checksumHeaderName_shouldFormatName() { + assertEquals("x-amz-checksum-sha256", checksumHeaderName(SHA256)); + assertEquals("x-amz-checksum-sha1", checksumHeaderName(SHA1)); + assertEquals("x-amz-checksum-crc32", checksumHeaderName(CRC32)); + assertEquals("x-amz-checksum-crc32c", checksumHeaderName(CRC32C)); + assertEquals("x-amz-checksum-md5", checksumHeaderName(MD5)); + } + + @Test + public void fromChecksumAlgorithm_mapsToCorrectSdkChecksum() { + assertEquals(Sha256Checksum.class, fromChecksumAlgorithm(SHA256).getClass()); + assertEquals(Sha1Checksum.class, fromChecksumAlgorithm(SHA1).getClass()); + assertEquals(Crc32Checksum.class, fromChecksumAlgorithm(CRC32).getClass()); + assertEquals(Crc32CChecksum.class, fromChecksumAlgorithm(CRC32C).getClass()); + assertEquals(Md5Checksum.class, fromChecksumAlgorithm(MD5).getClass()); + } + + @Test + public void readAll_consumesEntireStream() { + int bytes = 4096 * 4 + 1; + byte[] buf = new byte[bytes]; + ByteArrayInputStream inputStream = new ByteArrayInputStream(buf); + + readAll(inputStream); + + assertEquals(0, inputStream.available()); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/FifoCacheTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/FifoCacheTest.java new file mode 100644 index 000000000000..e33e53f37cf8 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/FifoCacheTest.java @@ -0,0 +1,76 @@ +/* + * 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.http.auth.aws.internal.signer.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class FifoCacheTest { + + @Test + public void test() { + FifoCache cache = new FifoCache<>(3); + assertEquals(0, cache.size()); + cache.add("k1", "v1"); + assertEquals(1, cache.size()); + cache.add("k1", "v11"); + assertEquals(1, cache.size()); + cache.add("k2", "v2"); + assertEquals(2, cache.size()); + cache.add("k3", "v3"); + assertEquals(3, cache.size()); + assertEquals("v11", cache.get("k1")); + assertEquals("v2", cache.get("k2")); + assertEquals("v3", cache.get("k3")); + cache.add("k4", "v4"); + assertEquals(3, cache.size()); + assertNull(cache.get("k1")); + } + + @Test + public void testZeroSize() { + assertThrows(IllegalArgumentException.class, () -> new FifoCache<>(0)); + } + + @Test + public void testIllegalArgument() { + assertThrows(IllegalArgumentException.class, () -> new FifoCache<>(0)); + } + + @Test + public void testSingleEntry() { + FifoCache cache = new FifoCache(1); + assertEquals(0, cache.size()); + cache.add("k1", "v1"); + assertEquals(1, cache.size()); + cache.add("k1", "v11"); + assertEquals(1, cache.size()); + assertEquals("v11", cache.get("k1")); + + cache.add("k2", "v2"); + assertEquals(1, cache.size()); + assertEquals("v2", cache.get("k2")); + assertNull(cache.get("k1")); + + cache.add("k3", "v3"); + assertEquals(1, cache.size()); + assertEquals("v3", cache.get("k3")); + assertNull(cache.get("k2")); + } +} diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerKeyTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerKeyTest.java new file mode 100644 index 000000000000..8932af97d126 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/util/SignerKeyTest.java @@ -0,0 +1,51 @@ +/* + * 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.http.auth.aws.internal.signer.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Instant; +import org.junit.jupiter.api.Test; + +public class SignerKeyTest { + + @Test + public void isValidForDate_dayBefore_false() { + Instant signerDate = Instant.parse("2020-03-03T23:59:59Z"); + SignerKey key = new SignerKey(signerDate, new byte[0]); + Instant dayBefore = Instant.parse("2020-03-02T23:59:59Z"); + + assertThat(key.isValidForDate(dayBefore)).isFalse(); + } + + @Test + public void isValidForDate_sameDay_true() { + Instant signerDate = Instant.parse("2020-03-03T23:59:59Z"); + SignerKey key = new SignerKey(signerDate, new byte[0]); + Instant sameDay = Instant.parse("2020-03-03T01:02:03Z"); + + assertThat(key.isValidForDate(sameDay)).isTrue(); + } + + @Test + public void isValidForDate_dayAfter_false() { + Instant signerDate = Instant.parse("2020-03-03T23:59:59Z"); + SignerKey key = new SignerKey(signerDate, new byte[0]); + Instant dayAfter = Instant.parse("2020-03-04T00:00:00Z"); + + assertThat(key.isValidForDate(dayAfter)).isFalse(); + } +} diff --git a/core/http-auth-aws/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/core/http-auth-aws/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000000..1f0955d450f0 --- /dev/null +++ b/core/http-auth-aws/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/core/http-auth-spi/pom.xml b/core/http-auth-spi/pom.xml new file mode 100644 index 000000000000..cb66c98e9643 --- /dev/null +++ b/core/http-auth-spi/pom.xml @@ -0,0 +1,101 @@ + + + + + 4.0.0 + + + software.amazon.awssdk + core + 2.21.0-SNAPSHOT + + + http-auth-spi + AWS Java SDK :: HTTP Auth SPI + + The AWS SDK for Java - HTTP Auth SPI module contains the interfaces for authentication that are used by other + modules in the library. + + https://aws.amazon.com/sdkforjava + + + + software.amazon.awssdk + annotations + ${awsjavasdk.version} + + + software.amazon.awssdk + utils + ${awsjavasdk.version} + + + software.amazon.awssdk + http-client-spi + ${awsjavasdk.version} + + + org.reactivestreams + reactive-streams + ${reactive-streams.version} + + + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + + + + org.junit.jupiter + junit-jupiter + test + + + nl.jqno.equalsverifier + equalsverifier + test + + + org.mockito + mockito-core + test + + + org.assertj + assertj-core + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + software.amazon.awssdk.http.auth.spi + + + + + + + + diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/scheme/DefaultAuthSchemeOption.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/scheme/DefaultAuthSchemeOption.java new file mode 100644 index 000000000000..46a706c38af6 --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/scheme/DefaultAuthSchemeOption.java @@ -0,0 +1,145 @@ +/* + * 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.http.auth.spi.internal.scheme; + +import java.util.HashMap; +import java.util.Map; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.IdentityProperty; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +@SdkInternalApi +@Immutable +public final class DefaultAuthSchemeOption implements AuthSchemeOption { + + private final String schemeId; + private final Map, Object> identityProperties; + private final Map, Object> signerProperties; + + DefaultAuthSchemeOption(BuilderImpl builder) { + this.schemeId = Validate.paramNotBlank(builder.schemeId, "schemeId"); + this.identityProperties = new HashMap<>(builder.identityProperties); + this.signerProperties = new HashMap<>(builder.signerProperties); + } + + public static Builder builder() { + return new BuilderImpl(); + } + + @Override + public String schemeId() { + return schemeId; + } + + @SuppressWarnings("unchecked") // Safe because of the implementation of putIdentityProperty + @Override + public T identityProperty(IdentityProperty property) { + return (T) identityProperties.get(property); + } + + @SuppressWarnings("unchecked") // Safe because of the implementation of putSignerProperty + @Override + public T signerProperty(SignerProperty property) { + return (T) signerProperties.get(property); + } + + @Override + public void forEachIdentityProperty(IdentityPropertyConsumer consumer) { + identityProperties.keySet().forEach(property -> consumeProperty(property, consumer)); + } + + private void consumeProperty(IdentityProperty property, IdentityPropertyConsumer consumer) { + consumer.accept(property, this.identityProperty(property)); + } + + @Override + public void forEachSignerProperty(SignerPropertyConsumer consumer) { + signerProperties.keySet().forEach(property -> consumeProperty(property, consumer)); + } + + private void consumeProperty(SignerProperty property, SignerPropertyConsumer consumer) { + consumer.accept(property, this.signerProperty(property)); + } + + @Override + public Builder toBuilder() { + return new BuilderImpl(this); + } + + @Override + public String toString() { + return ToString.builder("AuthSchemeOption") + .add("schemeId", schemeId) + .add("identityProperties", identityProperties) + .add("signerProperties", signerProperties) + .build(); + } + + + public static final class BuilderImpl implements Builder { + private String schemeId; + private final Map, Object> identityProperties = new HashMap<>(); + private final Map, Object> signerProperties = new HashMap<>(); + + private BuilderImpl() { + } + + private BuilderImpl(DefaultAuthSchemeOption authSchemeOption) { + this.schemeId = authSchemeOption.schemeId; + this.identityProperties.putAll(authSchemeOption.identityProperties); + this.signerProperties.putAll(authSchemeOption.signerProperties); + } + + @Override + public Builder schemeId(String schemeId) { + this.schemeId = schemeId; + return this; + } + + @Override + public Builder putIdentityProperty(IdentityProperty key, T value) { + this.identityProperties.put(key, value); + return this; + } + + @Override + public Builder putIdentityPropertyIfAbsent(IdentityProperty key, T value) { + this.identityProperties.putIfAbsent(key, value); + return this; + } + + @Override + public Builder putSignerProperty(SignerProperty key, T value) { + this.signerProperties.put(key, value); + return this; + } + + @Override + public Builder putSignerPropertyIfAbsent(SignerProperty key, T value) { + this.signerProperties.putIfAbsent(key, value); + return this; + } + + @Override + public AuthSchemeOption build() { + return new DefaultAuthSchemeOption(this); + } + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultAsyncSignRequest.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultAsyncSignRequest.java new file mode 100644 index 000000000000..ee3cea86ace1 --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultAsyncSignRequest.java @@ -0,0 +1,81 @@ +/* + * 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.http.auth.spi.internal.signer; + +import java.nio.ByteBuffer; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.utils.ToString; + +@SdkInternalApi +public final class DefaultAsyncSignRequest + extends DefaultBaseSignRequest, IdentityT> implements AsyncSignRequest { + + private DefaultAsyncSignRequest(BuilderImpl builder) { + super(builder); + } + + public static AsyncSignRequest.Builder builder() { + return new BuilderImpl<>(); + } + + public static AsyncSignRequest.Builder builder(IdentityT identity) { + return new BuilderImpl<>(identity); + } + + @Override + public String toString() { + return ToString.builder("AsyncSignRequest") + .add("request", request) + .add("identity", identity) + .add("properties", properties) + .build(); + } + + @Override + public AsyncSignRequest.Builder toBuilder() { + return new BuilderImpl<>(this); + } + + @SdkInternalApi + public static final class BuilderImpl + extends DefaultBaseSignRequest.BuilderImpl, Publisher, IdentityT> + implements AsyncSignRequest.Builder { + + // Used to enable consumer builder pattern in HttpSigner.signAsync() + private BuilderImpl() { + } + + // Used by AsyncSignRequest#builder() where identity is passed as parameter, to avoid having to pass Class. + private BuilderImpl(IdentityT identity) { + super(identity); + } + + private BuilderImpl(DefaultAsyncSignRequest request) { + properties(request.properties); + identity(request.identity); + payload(request.payload); + request(request.request); + } + + @Override + public AsyncSignRequest build() { + return new DefaultAsyncSignRequest<>(this); + } + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultAsyncSignedRequest.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultAsyncSignedRequest.java new file mode 100644 index 000000000000..e762931d4573 --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultAsyncSignedRequest.java @@ -0,0 +1,49 @@ +/* + * 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.http.auth.spi.internal.signer; + +import java.nio.ByteBuffer; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.utils.ToString; + +@SdkInternalApi +public final class DefaultAsyncSignedRequest + extends DefaultBaseSignedRequest> implements AsyncSignedRequest { + + private DefaultAsyncSignedRequest(BuilderImpl builder) { + super(builder); + } + + @Override + public String toString() { + return ToString.builder("AsyncSignedRequest") + .add("request", request) + .build(); + } + + @SdkInternalApi + public static final class BuilderImpl + extends DefaultBaseSignedRequest.BuilderImpl> + implements AsyncSignedRequest.Builder { + + @Override + public AsyncSignedRequest build() { + return new DefaultAsyncSignedRequest(this); + } + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultBaseSignRequest.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultBaseSignRequest.java new file mode 100644 index 000000000000..d879cfdb06e0 --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultBaseSignRequest.java @@ -0,0 +1,113 @@ +/* + * 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.http.auth.spi.internal.signer; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.spi.signer.BaseSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.utils.Validate; + +@SdkInternalApi +abstract class DefaultBaseSignRequest implements BaseSignRequest { + + protected final SdkHttpRequest request; + protected final PayloadT payload; + protected final IdentityT identity; + protected final Map, Object> properties; + + protected DefaultBaseSignRequest(BuilderImpl builder) { + this.request = Validate.paramNotNull(builder.request, "request"); + this.payload = builder.payload; + this.identity = Validate.paramNotNull(builder.identity, "identity"); + this.properties = Collections.unmodifiableMap(new HashMap<>(builder.properties)); + } + + @Override + public SdkHttpRequest request() { + return request; + } + + @Override + public Optional payload() { + return Optional.ofNullable(payload); + } + + @Override + public IdentityT identity() { + return identity; + } + + @Override + public T property(SignerProperty property) { + return (T) properties.get(property); + } + + @SdkInternalApi + protected abstract static class BuilderImpl, PayloadT, + IdentityT extends Identity> implements Builder { + private final Map, Object> properties = new HashMap<>(); + private SdkHttpRequest request; + private PayloadT payload; + private IdentityT identity; + + protected BuilderImpl() { + } + + protected BuilderImpl(IdentityT identity) { + this.identity = identity; + } + + @Override + public B request(SdkHttpRequest request) { + this.request = request; + return thisBuilder(); + } + + @Override + public B payload(PayloadT payload) { + this.payload = payload; + return thisBuilder(); + } + + @Override + public B identity(IdentityT identity) { + this.identity = identity; + return thisBuilder(); + } + + @Override + public B putProperty(SignerProperty key, T value) { + this.properties.put(key, value); + return thisBuilder(); + } + + protected B properties(Map, Object> properties) { + this.properties.clear(); + this.properties.putAll(properties); + return thisBuilder(); + } + + private B thisBuilder() { + return (B) this; + } + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultBaseSignedRequest.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultBaseSignedRequest.java new file mode 100644 index 000000000000..55cdc3986995 --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultBaseSignedRequest.java @@ -0,0 +1,68 @@ +/* + * 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.http.auth.spi.internal.signer; + +import java.util.Optional; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.spi.signer.BaseSignedRequest; +import software.amazon.awssdk.utils.Validate; + +@SdkInternalApi +abstract class DefaultBaseSignedRequest implements BaseSignedRequest { + + protected final SdkHttpRequest request; + protected final PayloadT payload; + + protected DefaultBaseSignedRequest(BuilderImpl builder) { + this.request = Validate.paramNotNull(builder.request, "request"); + this.payload = builder.payload; + } + + @Override + public SdkHttpRequest request() { + return request; + } + + @Override + public Optional payload() { + return Optional.ofNullable(payload); + } + + protected abstract static class BuilderImpl, PayloadT> implements Builder { + private SdkHttpRequest request; + private PayloadT payload; + + protected BuilderImpl() { + } + + @Override + public B request(SdkHttpRequest request) { + this.request = request; + return thisBuilder(); + } + + @Override + public B payload(PayloadT payload) { + this.payload = payload; + return thisBuilder(); + } + + private B thisBuilder() { + return (B) this; + } + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultSignRequest.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultSignRequest.java new file mode 100644 index 000000000000..39ae5e287a1c --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultSignRequest.java @@ -0,0 +1,80 @@ +/* + * 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.http.auth.spi.internal.signer; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.utils.ToString; + +@SdkInternalApi +public final class DefaultSignRequest + extends DefaultBaseSignRequest implements SignRequest { + + private DefaultSignRequest(BuilderImpl builder) { + super(builder); + } + + public static SignRequest.Builder builder() { + return new BuilderImpl<>(); + } + + public static SignRequest.Builder builder(IdentityT identity) { + return new BuilderImpl<>(identity); + } + + @Override + public String toString() { + return ToString.builder("SignRequest") + .add("request", request) + .add("identity", identity) + .add("properties", properties) + .build(); + } + + @Override + public SignRequest.Builder toBuilder() { + return new BuilderImpl<>(this); + } + + @SdkInternalApi + public static final class BuilderImpl + extends DefaultBaseSignRequest.BuilderImpl, ContentStreamProvider, IdentityT> + implements SignRequest.Builder { + + // Used to enable consumer builder pattern in HttpSigner.sign() + private BuilderImpl() { + } + + // Used by SignRequest#builder() where identity is passed as parameter, to avoid having to pass Class. + private BuilderImpl(IdentityT identity) { + super(identity); + } + + private BuilderImpl(DefaultSignRequest request) { + properties(request.properties); + identity(request.identity); + payload(request.payload); + request(request.request); + } + + @Override + public SignRequest build() { + return new DefaultSignRequest<>(this); + } + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultSignedRequest.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultSignedRequest.java new file mode 100644 index 000000000000..f8aa206eb33d --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/internal/signer/DefaultSignedRequest.java @@ -0,0 +1,48 @@ +/* + * 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.http.auth.spi.internal.signer; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.utils.ToString; + +@SdkInternalApi +public final class DefaultSignedRequest + extends DefaultBaseSignedRequest implements SignedRequest { + + private DefaultSignedRequest(BuilderImpl builder) { + super(builder); + } + + @Override + public String toString() { + return ToString.builder("SyncSignedRequest") + .add("request", request) + .build(); + } + + @SdkInternalApi + public static final class BuilderImpl + extends DefaultBaseSignedRequest.BuilderImpl + implements SignedRequest.Builder { + + @Override + public SignedRequest build() { + return new DefaultSignedRequest(this); + } + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/scheme/AuthScheme.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/scheme/AuthScheme.java new file mode 100644 index 000000000000..6f76e7624a1a --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/scheme/AuthScheme.java @@ -0,0 +1,65 @@ +/* + * 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.http.auth.spi.scheme; + +import software.amazon.awssdk.annotations.SdkPublicApi; +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.TokenIdentity; + +/** + * An authentication scheme, composed of: + *
    + *
  1. A scheme ID - A unique identifier for the authentication scheme.
  2. + *
  3. An identity provider - An API that can be queried to acquire the customer's identity.
  4. + *
  5. A signer - An API that can be used to sign HTTP requests.
  6. + *
+ * + * See example auth schemes defined here. + * + * @param The type of the {@link Identity} used by this authentication scheme. + * @see IdentityProvider + * @see HttpSigner + */ +@SdkPublicApi +public interface AuthScheme { + + /** + * Retrieve the scheme ID, a unique identifier for the authentication scheme. + */ + String schemeId(); + + /** + * Retrieve the identity provider associated with this authentication scheme. The identity generated by this provider is + * guaranteed to be supported by the signer in this authentication scheme. + *

+ * For example, if the scheme ID is aws.auth#sigv4, the provider returns an {@link AwsCredentialsIdentity}, if the scheme ID + * is httpBearerAuth, the provider returns a {@link TokenIdentity}. + *

+ * Note, the returned identity provider may differ from the type of identity provider retrieved from the provided + * {@link IdentityProviders}. + */ + IdentityProvider identityProvider(IdentityProviders providers); + + /** + * Retrieve the signer associated with this authentication scheme. This signer is guaranteed to support the identity generated + * by the identity provider in this authentication scheme. + */ + HttpSigner signer(); +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/scheme/AuthSchemeOption.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/scheme/AuthSchemeOption.java new file mode 100644 index 000000000000..b53cbaaab38c --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/scheme/AuthSchemeOption.java @@ -0,0 +1,133 @@ +/* + * 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.http.auth.spi.scheme; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.spi.internal.scheme.DefaultAuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.IdentityProperty; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * An authentication scheme option, composed of the scheme ID and properties for use when resolving the identity and signing + * the request. + *

+ * This is used in the output from the auth scheme resolver. The resolver returns a list of these, in the order the auth scheme + * resolver wishes to use them. + * + * @see AuthScheme + */ +@SdkPublicApi +public interface AuthSchemeOption extends ToCopyableBuilder { + + /** + * Get a new builder for creating a {@link AuthSchemeOption}. + */ + static Builder builder() { + return DefaultAuthSchemeOption.builder(); + } + + /** + * Retrieve the scheme ID, a unique identifier for the authentication scheme (aws.auth#sigv4, smithy.api#httpBearerAuth). + */ + String schemeId(); + + /** + * Retrieve the value of an {@link IdentityProperty}. + * @param property The IdentityProperty to retrieve the value of. + * @param The type of the IdentityProperty. + */ + T identityProperty(IdentityProperty property); + + /** + * Retrieve the value of an {@link SignerProperty}. + * @param property The SignerProperty to retrieve the value of. + * @param The type of the SignerProperty. + */ + T signerProperty(SignerProperty property); + + /** + * A method to operate on all {@link IdentityProperty} values of this AuthSchemeOption. + * @param consumer The method to apply to each IdentityProperty. + */ + void forEachIdentityProperty(IdentityPropertyConsumer consumer); + + /** + * A method to operate on all {@link SignerProperty} values of this AuthSchemeOption. + * @param consumer The method to apply to each SignerProperty. + */ + void forEachSignerProperty(SignerPropertyConsumer consumer); + + /** + * Interface for operating on an {@link IdentityProperty} value. + */ + @FunctionalInterface + interface IdentityPropertyConsumer { + /** + * A method to operate on an {@link IdentityProperty} and it's value. + * @param propertyKey The IdentityProperty. + * @param propertyValue The value of the IdentityProperty. + * @param The type of the IdentityProperty. + */ + void accept(IdentityProperty propertyKey, T propertyValue); + } + + /** + * Interface for operating on an {@link SignerProperty} value. + */ + @FunctionalInterface + interface SignerPropertyConsumer { + /** + * A method to operate on a {@link SignerProperty} and it's value. + * @param propertyKey The SignerProperty. + * @param propertyValue The value of the SignerProperty. + * @param The type of the SignerProperty. + */ + void accept(SignerProperty propertyKey, T propertyValue); + } + + /** + * A builder for a {@link AuthSchemeOption}. + */ + interface Builder extends CopyableBuilder { + + /** + * Set the scheme ID. + */ + Builder schemeId(String schemeId); + + /** + * Update or add the provided property value. + */ + Builder putIdentityProperty(IdentityProperty key, T value); + + /** + * Add the provided property value if the property does not already exist. + */ + Builder putIdentityPropertyIfAbsent(IdentityProperty key, T value); + + /** + * Update or add the provided property value. + */ + Builder putSignerProperty(SignerProperty key, T value); + + /** + * Add the provided property value if the property does not already exist. + */ + Builder putSignerPropertyIfAbsent(SignerProperty key, T value); + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/scheme/AuthSchemeProvider.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/scheme/AuthSchemeProvider.java new file mode 100644 index 000000000000..d08bdbe520c1 --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/scheme/AuthSchemeProvider.java @@ -0,0 +1,26 @@ +/* + * 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.http.auth.spi.scheme; + +import software.amazon.awssdk.annotations.SdkPublicApi; + +/** + * A marker interface for an auth scheme provider. An auth scheme provider takes as input a set of service-specific parameters, + * and resolves a list of {@link AuthSchemeOption} based on the given parameters. + */ +@SdkPublicApi +public interface AuthSchemeProvider { +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/AsyncSignRequest.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/AsyncSignRequest.java new file mode 100644 index 000000000000..a501a11134ed --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/AsyncSignRequest.java @@ -0,0 +1,54 @@ +/* + * 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.http.auth.spi.signer; + +import java.nio.ByteBuffer; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.http.auth.spi.internal.signer.DefaultAsyncSignRequest; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * Input parameters to sign a request with async payload, using {@link HttpSigner}. + * + * @param The type of the identity. + */ +@SdkPublicApi +@Immutable +@ThreadSafe +public interface AsyncSignRequest + extends BaseSignRequest, IdentityT>, + ToCopyableBuilder, AsyncSignRequest> { + + /** + * Get a new builder for creating a {@link AsyncSignRequest}. + */ + static Builder builder(IdentityT identity) { + return DefaultAsyncSignRequest.builder(identity); + } + + /** + * A builder for a {@link AsyncSignRequest}. + */ + interface Builder + extends BaseSignRequest.Builder, Publisher, IdentityT>, + CopyableBuilder, AsyncSignRequest> { + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/AsyncSignedRequest.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/AsyncSignedRequest.java new file mode 100644 index 000000000000..ff50abac19a3 --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/AsyncSignedRequest.java @@ -0,0 +1,47 @@ +/* + * 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.http.auth.spi.signer; + +import java.nio.ByteBuffer; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.http.auth.spi.internal.signer.DefaultAsyncSignedRequest; +import software.amazon.awssdk.utils.builder.SdkBuilder; + +/** + * Represents a request with async payload that has been signed by {@link HttpSigner}. + */ +@SdkPublicApi +@Immutable +@ThreadSafe +public interface AsyncSignedRequest extends BaseSignedRequest> { + + /** + * Get a new builder for creating a {@link AsyncSignedRequest}. + */ + static Builder builder() { + return new DefaultAsyncSignedRequest.BuilderImpl(); + } + + /** + * A builder for a {@link AsyncSignedRequest}. + */ + interface Builder extends BaseSignedRequest.Builder>, + SdkBuilder { + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/BaseSignRequest.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/BaseSignRequest.java new file mode 100644 index 000000000000..ec8214c1bc02 --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/BaseSignRequest.java @@ -0,0 +1,110 @@ +/* + * 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.http.auth.spi.signer; + +import java.util.Optional; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.utils.Validate; + +/** + * Base interface to represent input parameters to sign a request using {@link HttpSigner}, independent of payload type. See + * specific sub-interfaces {@link SignRequest} for sync payload and {@link AsyncSignRequest} for async payload. + * + * @param The type of payload of the request. + * @param The type of the identity. + */ +@SdkPublicApi +@Immutable +@ThreadSafe +public interface BaseSignRequest { + + /** + * Returns the HTTP request object, without the request body payload. + */ + SdkHttpRequest request(); + + /** + * Returns the body payload of the request. A payload is optional. By default, the payload will be empty. + */ + Optional payload(); + + /** + * Returns the identity. + */ + IdentityT identity(); + + /** + * Returns the value of a property that the {@link HttpSigner} can use during signing. + */ + T property(SignerProperty property); + + /** + * Ensure that the {@link SignerProperty} is present in the {@link BaseSignRequest}. + *

+ * The value, {@link T}, is return when present, and an exception is thrown otherwise. + */ + default boolean hasProperty(SignerProperty property) { + return property(property) != null; + } + + /** + * Ensure that the {@link SignerProperty} is present in the {@link BaseSignRequest}. + *

+ * The value, {@link T}, is return when present, and an exception is thrown otherwise. + */ + default T requireProperty(SignerProperty property) { + return Validate.notNull(property(property), property.toString() + " must not be null!"); + } + + /** + * Ensure that the {@link SignerProperty} is present in the {@link BaseSignRequest}. + *

+ * The value, {@link T}, is return when present, and the default is returned otherwise. + */ + default T requireProperty(SignerProperty property, T defaultValue) { + return Validate.getOrDefault(property(property), () -> defaultValue); + } + + /** + * A builder for a {@link BaseSignRequest}. + */ + interface Builder, PayloadT, IdentityT extends Identity> { + + /** + * Set the HTTP request object, without the request body payload. + */ + B request(SdkHttpRequest request); + + /** + * Set the body payload of the request. A payload is optional. By default, the payload will be empty. + */ + B payload(PayloadT payload); + + /** + * Set the identity of the request. + */ + B identity(IdentityT identity); + + /** + * Set a property that the {@link HttpSigner} can use during signing. + */ + B putProperty(SignerProperty key, T value); + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/BaseSignedRequest.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/BaseSignedRequest.java new file mode 100644 index 000000000000..b377121dadd9 --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/BaseSignedRequest.java @@ -0,0 +1,61 @@ +/* + * 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.http.auth.spi.signer; + +import java.util.Optional; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.http.SdkHttpRequest; + +/** + /** + * Base interface to a request that has been signed by {@link HttpSigner}, independent of payload type. See specific + * sub-interfaces {@link SignedRequest} for sync payload and {@link AsyncSignedRequest} for async payload. + * + * @param The type of payload of the request. + */ +@SdkPublicApi +@Immutable +@ThreadSafe +public interface BaseSignedRequest { + + /** + * Returns the HTTP request object, without the request body payload. + */ + SdkHttpRequest request(); + + /** + * Returns the body payload of the request. A payload is optional. By default, the payload will be empty. + */ + Optional payload(); + + /** + * A builder for a {@link BaseSignedRequest}. + */ + interface Builder, PayloadT> { + + /** + * Set the HTTP request object, without the request body payload. + */ + B request(SdkHttpRequest request); + + /** + * Set the body payload of the request. A payload is optional. By default, the payload will be empty. + */ + B payload(PayloadT payload); + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/HttpSigner.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/HttpSigner.java new file mode 100644 index 000000000000..5aafbe51e116 --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/HttpSigner.java @@ -0,0 +1,87 @@ +/* + * 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.http.auth.spi.signer; + +import java.time.Clock; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.spi.internal.signer.DefaultAsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.internal.signer.DefaultSignRequest; +import software.amazon.awssdk.identity.spi.Identity; + +/** + * Interface for the process of modifying a request destined for a service so that the service can authenticate the SDK + * customer’s identity. + * + * @param The type of the identity. + */ +@SdkPublicApi +public interface HttpSigner { + + /** + * A {@link Clock} to be used to derive the signing time. This property defaults to the system clock. + * + *

Note, signing time may not be relevant to some signers. + */ + SignerProperty SIGNING_CLOCK = SignerProperty.create(HttpSigner.class, "SigningClock"); + + /** + * Method that takes in inputs to sign a request with sync payload and returns a signed version of the request. + * + * @param request The inputs to sign a request. + * @return A signed version of the request. + */ + SignedRequest sign(SignRequest request); + + /** + * Method that takes in inputs to sign a request with sync payload and returns a signed version of the request. + *

+ * Similar to {@link #sign(SignRequest)}, but takes a lambda to configure a new {@link SignRequest.Builder}. + * This removes the need to call {@link SignRequest#builder(IdentityT)}} and + * {@link SignRequest.Builder#build()}. + * + * @param consumer A {@link Consumer} to which an empty {@link SignRequest.Builder} will be given. + * @return A signed version of the request. + */ + default SignedRequest sign(Consumer> consumer) { + return sign(DefaultSignRequest.builder().applyMutation(consumer).build()); + } + + /** + * Method that takes in inputs to sign a request with async payload and returns a future containing the signed version of the + * request. + * + * @param request The inputs to sign a request. + * @return A future containing the signed version of the request. + */ + CompletableFuture signAsync(AsyncSignRequest request); + + /** + * Method that takes in inputs to sign a request with async payload and returns a future containing the signed version of the + * request. + *

+ * Similar to {@link #signAsync(AsyncSignRequest)}, but takes a lambda to configure a new + * {@link AsyncSignRequest.Builder}. This removes the need to call {@link AsyncSignRequest#builder(IdentityT)}} and + * {@link AsyncSignRequest.Builder#build()}. + * + * @param consumer A {@link Consumer} to which an empty {@link BaseSignRequest.Builder} will be given. + * @return A future containing the signed version of the request. + */ + default CompletableFuture signAsync(Consumer> consumer) { + return signAsync(DefaultAsyncSignRequest.builder().applyMutation(consumer).build()); + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/SignRequest.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/SignRequest.java new file mode 100644 index 000000000000..7e024ce6845f --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/SignRequest.java @@ -0,0 +1,53 @@ +/* + * 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.http.auth.spi.signer; + +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.auth.spi.internal.signer.DefaultSignRequest; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * Input parameters to sign a request with sync payload, using {@link HttpSigner}. + * + * @param The type of the identity. + */ +@SdkPublicApi +@Immutable +@ThreadSafe +public interface SignRequest + extends BaseSignRequest, + ToCopyableBuilder, SignRequest> { + + /** + * Get a new builder for creating a {@link SignRequest}. + */ + static Builder builder(IdentityT identity) { + return DefaultSignRequest.builder(identity); + } + + /** + * A builder for a {@link SignRequest}. + */ + interface Builder + extends BaseSignRequest.Builder, ContentStreamProvider, IdentityT>, + CopyableBuilder, SignRequest> { + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/SignedRequest.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/SignedRequest.java new file mode 100644 index 000000000000..8a043f1800f2 --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/SignedRequest.java @@ -0,0 +1,46 @@ +/* + * 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.http.auth.spi.signer; + +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.auth.spi.internal.signer.DefaultSignedRequest; +import software.amazon.awssdk.utils.builder.SdkBuilder; + +/** + * Represents a request with sync payload that has been signed by {@link HttpSigner}. + */ +@SdkPublicApi +@Immutable +@ThreadSafe +public interface SignedRequest extends BaseSignedRequest { + + /** + * Get a new builder for creating a {@link SignedRequest}. + */ + static Builder builder() { + return new DefaultSignedRequest.BuilderImpl(); + } + + /** + * A builder for a {@link SignedRequest}. + */ + interface Builder extends BaseSignedRequest.Builder, + SdkBuilder { + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/SignerProperty.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/SignerProperty.java new file mode 100644 index 000000000000..4c1921f92b96 --- /dev/null +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/SignerProperty.java @@ -0,0 +1,104 @@ +/* + * 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.http.auth.spi.signer; + +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.utils.Pair; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * A strongly-typed property for input to an {@link HttpSigner}. + * @param The type of the property. + */ +@SdkPublicApi +@Immutable +@ThreadSafe +public final class SignerProperty { + private static final ConcurrentMap, SignerProperty> NAME_HISTORY = new ConcurrentHashMap<>(); + + private final String namespace; + private final String name; + + private SignerProperty(String namespace, String name) { + Validate.paramNotBlank(namespace, "namespace"); + Validate.paramNotBlank(name, "name"); + + this.namespace = namespace; + this.name = name; + ensureUnique(); + } + + /** + * Create a property. + * + * @param the type of the property. + * @param namespace the class *where* the property is being defined + * @param name the name for the property + * @throws IllegalArgumentException if a property with this namespace and name already exist + */ + public static SignerProperty create(Class namespace, String name) { + return new SignerProperty<>(namespace.getName(), name); + } + + private void ensureUnique() { + SignerProperty prev = NAME_HISTORY.putIfAbsent(Pair.of(namespace, name), this); + Validate.isTrue(prev == null, + "No duplicate SignerProperty names allowed but both SignerProperties %s and %s have the same namespace " + + "(%s) and name (%s). SignerProperty should be referenced from a shared static constant to protect " + + "against erroneous or unexpected collisions.", + Integer.toHexString(System.identityHashCode(prev)), + Integer.toHexString(System.identityHashCode(this)), + namespace, + name); + } + + @Override + public String toString() { + return ToString.builder("SignerProperty") + .add("namespace", namespace) + .add("name", name) + .build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SignerProperty that = (SignerProperty) o; + + return Objects.equals(namespace, that.namespace) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode = 31 * hashCode + Objects.hashCode(namespace); + hashCode = 31 * hashCode + Objects.hashCode(name); + return hashCode; + } +} diff --git a/core/http-auth-spi/src/test/java/software/amazon/awssdk/http/auth/spi/scheme/AuthSchemeOptionTest.java b/core/http-auth-spi/src/test/java/software/amazon/awssdk/http/auth/spi/scheme/AuthSchemeOptionTest.java new file mode 100644 index 000000000000..789dc37141a1 --- /dev/null +++ b/core/http-auth-spi/src/test/java/software/amazon/awssdk/http/auth/spi/scheme/AuthSchemeOptionTest.java @@ -0,0 +1,90 @@ +/* + * 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.http.auth.spi.scheme; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.IdentityProperty; + +class AuthSchemeOptionTest { + private static final IdentityProperty IDENTITY_PROPERTY_1 = IdentityProperty.create(AuthSchemeOptionTest.class, "identityKey1"); + private static final SignerProperty SIGNER_PROPERTY_1 = SignerProperty.create(AuthSchemeOptionTest.class, "signingKey1"); + private static final IdentityProperty IDENTITY_PROPERTY_2 = IdentityProperty.create(AuthSchemeOptionTest.class, "identityKey2"); + private static final SignerProperty SIGNER_PROPERTY_2 = SignerProperty.create(AuthSchemeOptionTest.class, "signingKey2"); + + @Test + public void emptyBuilder_isNotSuccessful() { + assertThrows(NullPointerException.class, () -> AuthSchemeOption.builder().build()); + } + + @Test + public void build_withSchemeId_isSuccessful() { + AuthSchemeOption authSchemeOption = AuthSchemeOption.builder().schemeId("my.api#myAuth").build(); + assertEquals("my.api#myAuth", authSchemeOption.schemeId()); + } + + @Test + public void putProperty_sameProperty_isReplaced() { + + AuthSchemeOption authSchemeOption = AuthSchemeOption.builder() + .schemeId("my.api#myAuth") + .putIdentityProperty(IDENTITY_PROPERTY_1, "identity-value1") + .putIdentityProperty(IDENTITY_PROPERTY_1, "identity-value2") + .putSignerProperty(SIGNER_PROPERTY_1, "signing-value1") + .putSignerProperty(SIGNER_PROPERTY_1, "signing-value2") + .build(); + + assertEquals("identity-value2", authSchemeOption.identityProperty(IDENTITY_PROPERTY_1)); + assertEquals("signing-value2", authSchemeOption.signerProperty(SIGNER_PROPERTY_1)); + } + + @Test + public void copyBuilder_addProperty_retains() { + AuthSchemeOption authSchemeOption = AuthSchemeOption.builder() + .schemeId("my.api#myAuth") + .putIdentityProperty(IDENTITY_PROPERTY_1, "identity-value1") + .putSignerProperty(SIGNER_PROPERTY_1, "signing-value1") + .build(); + + authSchemeOption = + authSchemeOption.copy(builder -> builder.putIdentityProperty(IDENTITY_PROPERTY_2, "identity2-value1") + .putSignerProperty(SIGNER_PROPERTY_2, "signing2-value1")); + + assertEquals("identity-value1", authSchemeOption.identityProperty(IDENTITY_PROPERTY_1)); + assertEquals("identity2-value1", authSchemeOption.identityProperty(IDENTITY_PROPERTY_2)); + assertEquals("signing-value1", authSchemeOption.signerProperty(SIGNER_PROPERTY_1)); + assertEquals("signing2-value1", authSchemeOption.signerProperty(SIGNER_PROPERTY_2)); + } + + @Test + public void copyBuilder_updateProperty_updates() { + AuthSchemeOption authSchemeOption = AuthSchemeOption.builder() + .schemeId("my.api#myAuth") + .putIdentityProperty(IDENTITY_PROPERTY_1, "identity-value1") + .putSignerProperty(SIGNER_PROPERTY_1, "signing-value1") + .build(); + + authSchemeOption = + authSchemeOption.copy(builder -> builder.putIdentityProperty(IDENTITY_PROPERTY_1, "identity-value2") + .putSignerProperty(SIGNER_PROPERTY_1, "signing-value2")); + + assertEquals("identity-value2", authSchemeOption.identityProperty(IDENTITY_PROPERTY_1)); + assertEquals("signing-value2", authSchemeOption.signerProperty(SIGNER_PROPERTY_1)); + } +} diff --git a/core/http-auth-spi/src/test/java/software/amazon/awssdk/http/auth/spi/signer/HttpSignerTest.java b/core/http-auth-spi/src/test/java/software/amazon/awssdk/http/auth/spi/signer/HttpSignerTest.java new file mode 100644 index 000000000000..eb2734753a7a --- /dev/null +++ b/core/http-auth-spi/src/test/java/software/amazon/awssdk/http/auth/spi/signer/HttpSignerTest.java @@ -0,0 +1,117 @@ +/* + * 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.http.auth.spi.signer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; + +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.identity.spi.TokenIdentity; + +public class HttpSignerTest { + + private static final SignerProperty KEY = SignerProperty.create(HttpSignerTest.class, "key"); + private static final String VALUE = "value"; + private static final TokenIdentity IDENTITY = TokenIdentity.create("token"); + + final HttpSigner signer = new TestSigner(); + + @Test + public void sign_usingConsumerBuilder_works() { + SignedRequest signedRequest = signer.sign(r -> r.request(mock(SdkHttpRequest.class)) + .identity(IDENTITY) + .putProperty(KEY, VALUE)); + assertNotNull(signedRequest); + } + + @Test + public void sign_usingRequest_works() { + SignedRequest signedRequest = + signer.sign(SignRequest.builder(IDENTITY) + .request(mock(SdkHttpRequest.class)) + .identity(IDENTITY) // Note, this is doable + .putProperty(KEY, VALUE) + .build()); + assertNotNull(signedRequest); + } + + @Test + public void signAsync_usingConsumerBuilder_works() { + Publisher payload = subscriber -> {}; + AsyncSignedRequest signedRequest = signer.signAsync( + r -> r.request(mock(SdkHttpRequest.class)) + .payload(payload) + .identity(IDENTITY) + .putProperty(KEY, VALUE) + ).join(); + + assertNotNull(signedRequest); + } + + @Test + public void signAsync_usingRequest_works() { + Publisher payload = subscriber -> {}; + CompletableFuture signedRequest = + signer.signAsync(AsyncSignRequest.builder(IDENTITY) + .request(mock(SdkHttpRequest.class)) + .payload(payload) + .identity(IDENTITY) // Note, this is doable + .putProperty(KEY, VALUE) + .build()); + assertNotNull(signedRequest); + } + + /** + * NoOp Signer that asserts that the input created via builder or Consumer builder pattern are set up correctly. + * This is similar to what a bearerTokenSigner would look like - which would insert the identity in a Header. + + */ + private static class TestSigner implements HttpSigner { + @Override + public SignedRequest sign(SignRequest request) { + assertEquals(VALUE, request.property(KEY)); + assertEquals(IDENTITY, request.identity()); + + return SignedRequest.builder() + .request(addTokenHeader(request)) + .payload(request.payload().orElse(null)) + .build(); + } + + @Override + public CompletableFuture signAsync(AsyncSignRequest request) { + assertEquals(VALUE, request.property(KEY)); + assertEquals(IDENTITY, request.identity()); + + return CompletableFuture.completedFuture( + AsyncSignedRequest.builder() + .request(addTokenHeader(request)) + .payload(request.payload().orElse(null)) + .build() + ); + } + + private SdkHttpRequest addTokenHeader(BaseSignRequest input) { + // return input.request().copy(b -> b.putHeader("Token-Header", input.identity().token())); + return input.request(); + } + } +} diff --git a/core/http-auth-spi/src/test/java/software/amazon/awssdk/http/auth/spi/signer/SignerPropertyTest.java b/core/http-auth-spi/src/test/java/software/amazon/awssdk/http/auth/spi/signer/SignerPropertyTest.java new file mode 100644 index 000000000000..66402e11e073 --- /dev/null +++ b/core/http-auth-spi/src/test/java/software/amazon/awssdk/http/auth/spi/signer/SignerPropertyTest.java @@ -0,0 +1,43 @@ +/* + * 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.http.auth.spi.signer; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.UUID; +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +public class SignerPropertyTest { + + @Test + public void equalsHashcode() { + EqualsVerifier.forClass(SignerProperty.class) + .withNonnullFields("namespace", "name") + .verify(); + } + + @Test + public void namesMustBeUnique() { + String propertyName = UUID.randomUUID().toString(); + + SignerProperty.create(getClass(), propertyName); + assertThatThrownBy(() -> SignerProperty.create(getClass(), propertyName)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(getClass().getName()) + .hasMessageContaining(propertyName); + } +} diff --git a/core/http-auth/pom.xml b/core/http-auth/pom.xml new file mode 100644 index 000000000000..4c3da993c967 --- /dev/null +++ b/core/http-auth/pom.xml @@ -0,0 +1,92 @@ + + + + + 4.0.0 + + + software.amazon.awssdk + core + 2.21.0-SNAPSHOT + + + http-auth + AWS Java SDK :: HTTP Auth + + The AWS SDK for Java - HTTP Auth module contains interfaces and implementations + for generic HTTP authentication + + https://aws.amazon.com/sdkforjava + + + + software.amazon.awssdk + annotations + ${awsjavasdk.version} + + + software.amazon.awssdk + utils + ${awsjavasdk.version} + + + software.amazon.awssdk + http-client-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + + + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + software.amazon.awssdk.http.auth + + + + + + + + diff --git a/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/internal/scheme/DefaultBearerAuthScheme.java b/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/internal/scheme/DefaultBearerAuthScheme.java new file mode 100644 index 000000000000..91df6b717b22 --- /dev/null +++ b/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/internal/scheme/DefaultBearerAuthScheme.java @@ -0,0 +1,54 @@ +/* + * 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.http.auth.internal.scheme; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; +import software.amazon.awssdk.http.auth.signer.BearerHttpSigner; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.identity.spi.TokenIdentity; + +/** + * A default implementation of {@link BearerAuthScheme}. + */ +@SdkInternalApi +public final class DefaultBearerAuthScheme implements BearerAuthScheme { + private static final DefaultBearerAuthScheme DEFAULT = new DefaultBearerAuthScheme(); + private static final BearerHttpSigner DEFAULT_SIGNER = BearerHttpSigner.create(); + + /** + * Returns an instance of the {@link DefaultBearerAuthScheme}. + */ + public static DefaultBearerAuthScheme create() { + return DEFAULT; + } + + @Override + public String schemeId() { + return SCHEME_ID; + } + + @Override + public IdentityProvider identityProvider(IdentityProviders providers) { + return providers.identityProvider(TokenIdentity.class); + } + + @Override + public BearerHttpSigner signer() { + return DEFAULT_SIGNER; + } +} diff --git a/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/internal/scheme/DefaultNoAuthAuthScheme.java b/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/internal/scheme/DefaultNoAuthAuthScheme.java new file mode 100644 index 000000000000..a46a1a84ef8d --- /dev/null +++ b/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/internal/scheme/DefaultNoAuthAuthScheme.java @@ -0,0 +1,129 @@ +/* + * 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.http.auth.internal.scheme; + +import java.nio.ByteBuffer; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.identity.spi.ResolveIdentityRequest; + +/** + * A default implementation of {@link NoAuthAuthScheme}. This implementation always: + * + *

    + *
  • Returns an {@link IdentityProvider} that always returns the same static instance that implements the + * {@link AnonymousIdentity} interface
  • + *
  • Returns an {@link HttpSigner} that returns the same request given in the signing request.
  • + *
+ */ +@SdkInternalApi +public final class DefaultNoAuthAuthScheme implements NoAuthAuthScheme { + private static final DefaultNoAuthAuthScheme DEFAULT = new DefaultNoAuthAuthScheme(); + private static final IdentityProvider DEFAULT_IDENTITY_PROVIDER = noAuthIdentityProvider(); + private static final HttpSigner DEFAULT_SIGNER = noAuthSigner(); + private static final AnonymousIdentity ANONYMOUS_IDENTITY = anonymousIdentity(); + + /** + * Returns an instance of the {@link NoAuthAuthScheme}. + */ + public static NoAuthAuthScheme create() { + return DEFAULT; + } + + @Override + public String schemeId() { + return SCHEME_ID; + } + + @Override + public IdentityProvider identityProvider(IdentityProviders providers) { + return DEFAULT_IDENTITY_PROVIDER; + } + + @Override + public HttpSigner signer() { + return DEFAULT_SIGNER; + } + + private static IdentityProvider noAuthIdentityProvider() { + return new IdentityProvider() { + + @Override + public Class identityType() { + return AnonymousIdentity.class; + } + + @Override + public CompletableFuture resolveIdentity(ResolveIdentityRequest request) { + return CompletableFuture.completedFuture(ANONYMOUS_IDENTITY); + } + }; + } + + private static HttpSigner noAuthSigner() { + return new HttpSigner() { + @Override + public SignedRequest sign(SignRequest request) { + + return new SignedRequest() { + @Override + public SdkHttpRequest request() { + return request.request(); + } + + @Override + public Optional payload() { + return request.payload(); + } + }; + } + + @Override + public CompletableFuture signAsync(AsyncSignRequest request) { + AsyncSignedRequest result = new AsyncSignedRequest() { + @Override + public SdkHttpRequest request() { + return request.request(); + } + + @Override + public Optional> payload() { + return request.payload(); + } + }; + return CompletableFuture.completedFuture(result); + } + }; + } + + private static AnonymousIdentity anonymousIdentity() { + return new AnonymousIdentity() { + }; + } +} diff --git a/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/internal/signer/DefaultBearerHttpSigner.java b/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/internal/signer/DefaultBearerHttpSigner.java new file mode 100644 index 000000000000..b512b42304ff --- /dev/null +++ b/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/internal/signer/DefaultBearerHttpSigner.java @@ -0,0 +1,70 @@ +/* + * 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.http.auth.internal.signer; + +import java.util.concurrent.CompletableFuture; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.signer.BearerHttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.BaseSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.TokenIdentity; + +/** + * A default implementation of {@link BearerHttpSigner}. + */ +@SdkInternalApi +public final class DefaultBearerHttpSigner implements BearerHttpSigner { + + @Override + public SignedRequest sign(SignRequest request) { + return SignedRequest.builder() + .request(doSign(request)) + .payload(request.payload().orElse(null)) + .build(); + } + + @Override + public CompletableFuture signAsync(AsyncSignRequest request) { + return CompletableFuture.completedFuture( + AsyncSignedRequest.builder() + .request(doSign(request)) + .payload(request.payload().orElse(null)) + .build() + ); + } + + /** + * Using {@link BaseSignRequest}, sign the request with a {@link BaseSignRequest} and re-build it. + */ + private SdkHttpRequest doSign(BaseSignRequest request) { + return request.request().toBuilder() + .putHeader( + "Authorization", + buildAuthorizationHeader(request.identity())) + .build(); + } + + /** + * Use a {@link TokenIdentity} to build an authorization header. + */ + private String buildAuthorizationHeader(TokenIdentity tokenIdentity) { + return "Bearer " + tokenIdentity.token(); + } +} diff --git a/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/scheme/BearerAuthScheme.java b/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/scheme/BearerAuthScheme.java new file mode 100644 index 000000000000..21ad69f1808f --- /dev/null +++ b/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/scheme/BearerAuthScheme.java @@ -0,0 +1,56 @@ +/* + * 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.http.auth.scheme; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.internal.scheme.DefaultBearerAuthScheme; +import software.amazon.awssdk.http.auth.signer.BearerHttpSigner; +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.identity.spi.TokenIdentity; + +/** + * The smithy.api#httpBearerAuth auth + * scheme, which uses a {@link TokenIdentity} and {@link BearerHttpSigner}. + */ +@SdkPublicApi +public interface BearerAuthScheme extends AuthScheme { + + /** + * The scheme ID for this interface. + */ + String SCHEME_ID = "smithy.api#httpBearerAuth"; + + /** + * Get a default implementation of a {@link BearerAuthScheme} + */ + static BearerAuthScheme create() { + return DefaultBearerAuthScheme.create(); + } + + /** + * Retrieve the {@link TokenIdentity} based {@link IdentityProvider} associated with this authentication scheme. + */ + @Override + IdentityProvider identityProvider(IdentityProviders providers); + + /** + * Retrieve the {@link BearerHttpSigner} associated with this authentication scheme. + */ + @Override + BearerHttpSigner signer(); +} diff --git a/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/scheme/NoAuthAuthScheme.java b/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/scheme/NoAuthAuthScheme.java new file mode 100644 index 000000000000..4d3f486b94a4 --- /dev/null +++ b/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/scheme/NoAuthAuthScheme.java @@ -0,0 +1,57 @@ +/* + * 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.http.auth.scheme; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.internal.scheme.DefaultNoAuthAuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; + +/** + * An auth scheme that represents no authentication. + */ +@SdkPublicApi +public interface NoAuthAuthScheme extends AuthScheme { + /** + * The scheme ID for the no-auth auth scheme. + */ + String SCHEME_ID = "smithy.api#noAuth"; + + static NoAuthAuthScheme create() { + return DefaultNoAuthAuthScheme.create(); + } + + /** + * Retrieve the {@link AnonymousIdentity} based {@link IdentityProvider} associated with this authentication scheme. + */ + @Override + IdentityProvider identityProvider(IdentityProviders providers); + + /** + * Retrieve the {@link HttpSigner} associated with this authentication scheme. + */ + @Override + HttpSigner signer(); + + /** + * An anonymous identity used by the no-auth auth scheme. + */ + interface AnonymousIdentity extends Identity { + } +} diff --git a/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/signer/BearerHttpSigner.java b/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/signer/BearerHttpSigner.java new file mode 100644 index 000000000000..bed61bce6130 --- /dev/null +++ b/core/http-auth/src/main/java/software/amazon/awssdk/http/auth/signer/BearerHttpSigner.java @@ -0,0 +1,37 @@ +/* + * 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.http.auth.signer; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.internal.signer.DefaultBearerHttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.identity.spi.TokenIdentity; + +/** + * An {@link HttpSigner} that will sign a request with a bearer-token ({@link TokenIdentity}). + */ +@SdkPublicApi +public interface BearerHttpSigner extends HttpSigner { + + /** + * Get a default implementation of a {@link BearerHttpSigner} + * + * @return BearerHttpSigner + */ + static BearerHttpSigner create() { + return new DefaultBearerHttpSigner(); + } +} diff --git a/core/http-auth/src/test/java/software/amazon/awssdk/http/auth/signer/BearerHttpSignerTest.java b/core/http-auth/src/test/java/software/amazon/awssdk/http/auth/signer/BearerHttpSignerTest.java new file mode 100644 index 000000000000..2b5986771afb --- /dev/null +++ b/core/http-auth/src/test/java/software/amazon/awssdk/http/auth/signer/BearerHttpSignerTest.java @@ -0,0 +1,96 @@ +/* + * 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.http.auth.signer; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.TokenIdentity; +import software.amazon.awssdk.utils.async.SimplePublisher; + +class BearerHttpSignerTest { + + private static final String BEARER_AUTH_MARKER = "Bearer "; + + private static String createExpectedHeader(String token) { + return BEARER_AUTH_MARKER + token; + } + + private static SignRequest generateBasicRequest(String token) { + + return SignRequest.builder(TokenIdentity.create(token)) + .request(SdkHttpRequest.builder() + .method(SdkHttpMethod.POST) + .putHeader("Host", "demo.us-east-1.amazonaws.com") + .putHeader("x-amz-archive-description", "test test") + .encodedPath("/") + .uri(URI.create("http://demo.us-east-1.amazonaws.com")) + .build()) + .payload(() -> new ByteArrayInputStream("{\"TableName\": \"foo\"}".getBytes())) + .build(); + } + + private static AsyncSignRequest generateBasicAsyncRequest(String token) { + + return AsyncSignRequest.builder(TokenIdentity.create(token)) + .request(SdkHttpRequest.builder() + .method(SdkHttpMethod.POST) + .putHeader("Host", "demo.us-east-1.amazonaws.com") + .putHeader("x-amz-archive-description", "test test") + .encodedPath("/") + .uri(URI.create("http://demo.us-east-1.amazonaws.com")) + .build()) + .payload(new SimplePublisher<>()) + .build(); + } + + @Test + public void whenTokenExists_requestIsSignedCorrectly() { + String tokenValue = "mF_9.B5f-4.1JqM"; + + BearerHttpSigner tokenSigner = BearerHttpSigner.create(); + SignedRequest signedRequest = tokenSigner.sign(generateBasicRequest(tokenValue)); + + + String expectedHeader = createExpectedHeader(tokenValue); + assertThat(signedRequest.request().firstMatchingHeader( + "Authorization")).hasValue(expectedHeader); + } + + @Test + public void whenTokenExists_asyncRequestIsSignedCorrectly() { + String tokenValue = "mF_9.B5f-4.1JqM"; + + BearerHttpSigner tokenSigner = BearerHttpSigner.create(); + + AsyncSignedRequest signedRequest = + tokenSigner.signAsync(generateBasicAsyncRequest(tokenValue)).join(); + + + String expectedHeader = createExpectedHeader(tokenValue); + assertThat(signedRequest.request().firstMatchingHeader( + "Authorization")).hasValue(expectedHeader); + } + +} diff --git a/core/identity-spi/pom.xml b/core/identity-spi/pom.xml new file mode 100644 index 000000000000..749fe0eb3269 --- /dev/null +++ b/core/identity-spi/pom.xml @@ -0,0 +1,85 @@ + + + + + 4.0.0 + + + software.amazon.awssdk + core + 2.21.0-SNAPSHOT + + + identity-spi + AWS Java SDK :: Identity SPI + + The AWS SDK for Java - Identity SPI module contains the Identity interfaces that are used by other modules in + the library. + + https://aws.amazon.com/sdkforjava + + + + software.amazon.awssdk + annotations + ${awsjavasdk.version} + + + software.amazon.awssdk + utils + ${awsjavasdk.version} + + + org.junit.jupiter + junit-jupiter + test + + + nl.jqno.equalsverifier + equalsverifier + test + + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + software.amazon.awssdk.identity.spi + + + + + + + + diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentity.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentity.java new file mode 100644 index 000000000000..07e1f26bbdeb --- /dev/null +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentity.java @@ -0,0 +1,75 @@ +/* + * 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.identity.spi; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.identity.spi.internal.DefaultAwsCredentialsIdentity; + +/** + * Provides access to the AWS credentials used for accessing services: AWS access key ID and secret access key. These + * credentials are used to securely sign requests to services (e.g., AWS services) that use them for authentication. + * + *

For more details on AWS access keys, see: + * + * https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys

+ * + * @see AwsSessionCredentialsIdentity + */ +@SdkPublicApi +@ThreadSafe +public interface AwsCredentialsIdentity extends Identity { + + /** + * Retrieve the AWS access key, used to identify the user interacting with services. + */ + String accessKeyId(); + + /** + * Retrieve the AWS secret access key, used to authenticate the user interacting with services. + */ + String secretAccessKey(); + + static Builder builder() { + return DefaultAwsCredentialsIdentity.builder(); + } + + /** + * Constructs a new credentials object, with the specified AWS access key and AWS secret key. + * + * @param accessKeyId The AWS access key, used to identify the user interacting with services. + * @param secretAccessKey The AWS secret access key, used to authenticate the user interacting with services. + */ + static AwsCredentialsIdentity create(String accessKeyId, String secretAccessKey) { + return builder().accessKeyId(accessKeyId) + .secretAccessKey(secretAccessKey) + .build(); + } + + interface Builder { + /** + * The AWS access key, used to identify the user interacting with services. + */ + Builder accessKeyId(String accessKeyId); + + /** + * The AWS secret access key, used to authenticate the user interacting with services. + */ + Builder secretAccessKey(String secretAccessKey); + + AwsCredentialsIdentity build(); + } +} diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentity.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentity.java new file mode 100644 index 000000000000..10e0ec0634ce --- /dev/null +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentity.java @@ -0,0 +1,72 @@ +/* + * 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.identity.spi; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.identity.spi.internal.DefaultAwsSessionCredentialsIdentity; + +/** + * A special type of {@link AwsCredentialsIdentity} that provides a session token to be used in service authentication. Session + * tokens are typically provided by a token broker service, like AWS Security Token Service, and provide temporary access to an + * AWS service. + */ +@SdkPublicApi +@ThreadSafe +public interface AwsSessionCredentialsIdentity extends AwsCredentialsIdentity { + + /** + * Retrieve the AWS session token. This token is retrieved from an AWS token service, and is used for authenticating that this + * user has received temporary permission to access some resource. + */ + String sessionToken(); + + static AwsSessionCredentialsIdentity.Builder builder() { + return DefaultAwsSessionCredentialsIdentity.builder(); + } + + /** + * Constructs a new session credentials object, with the specified AWS access key, AWS secret key and AWS session token. + * + * @param accessKeyId The AWS access key, used to identify the user interacting with services. + * @param secretAccessKey The AWS secret access key, used to authenticate the user interacting with services. + * @param sessionToken The AWS session token, retrieved from an AWS token service, used for authenticating that this user has + * received temporary permission to access some resource. + */ + static AwsSessionCredentialsIdentity create(String accessKeyId, String secretAccessKey, String sessionToken) { + return builder().accessKeyId(accessKeyId) + .secretAccessKey(secretAccessKey) + .sessionToken(sessionToken) + .build(); + } + + interface Builder extends AwsCredentialsIdentity.Builder { + @Override + Builder accessKeyId(String accessKeyId); + + @Override + Builder secretAccessKey(String secretAccessKey); + + /** + * The AWS session token, retrieved from an AWS token service, used for authenticating that this user has + * received temporary permission to access some resource. + */ + Builder sessionToken(String sessionToken); + + @Override + AwsSessionCredentialsIdentity build(); + } +} diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/Identity.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/Identity.java new file mode 100644 index 000000000000..130fc9d67290 --- /dev/null +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/Identity.java @@ -0,0 +1,41 @@ +/* + * 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.identity.spi; + +import java.time.Instant; +import java.util.Optional; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; + +/** + * Interface to represent who is using the SDK, i.e., the identity of the caller, used for authentication. + * + *

Examples include {@link AwsCredentialsIdentity} and {@link TokenIdentity}.

+ * + * @see IdentityProvider + */ +@SdkPublicApi +@ThreadSafe +public interface Identity { + /** + * The time after which this identity will no longer be valid. If this is empty, + * an expiration time is not known (but the identity may still expire at some + * time in the future). + */ + default Optional expirationTime() { + return Optional.empty(); + } +} diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/IdentityProperty.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/IdentityProperty.java new file mode 100644 index 000000000000..e25510e13049 --- /dev/null +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/IdentityProperty.java @@ -0,0 +1,104 @@ +/* + * 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.identity.spi; + +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.utils.Pair; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * A strongly-typed property for input to an {@link IdentityProvider}. + * @param The type of the attribute. + */ +@SdkPublicApi +@Immutable +@ThreadSafe +public final class IdentityProperty { + private static final ConcurrentMap, IdentityProperty> NAME_HISTORY = new ConcurrentHashMap<>(); + + private final String namespace; + private final String name; + + private IdentityProperty(String namespace, String name) { + Validate.paramNotBlank(namespace, "namespace"); + Validate.paramNotBlank(name, "name"); + + this.namespace = namespace; + this.name = name; + ensureUnique(); + } + + /** + * Create a property. + * + * @param the type of the property. + * @param namespace the class *where* the property is being defined + * @param name the name for the property + * @throws IllegalArgumentException if a property with this namespace and name already exist + */ + public static IdentityProperty create(Class namespace, String name) { + return new IdentityProperty<>(namespace.getName(), name); + } + + private void ensureUnique() { + IdentityProperty prev = NAME_HISTORY.putIfAbsent(Pair.of(namespace, name), this); + Validate.isTrue(prev == null, + "No duplicate IdentityProperty names allowed but both IdentityProperties %s and %s have the same " + + "namespace (%s) and name (%s). IdentityProperty should be referenced from a shared static constant to " + + "protect against erroneous or unexpected collisions.", + Integer.toHexString(System.identityHashCode(prev)), + Integer.toHexString(System.identityHashCode(this)), + namespace, + name); + } + + @Override + public String toString() { + return ToString.builder("IdentityProperty") + .add("namespace", namespace) + .add("name", name) + .build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + IdentityProperty that = (IdentityProperty) o; + + return Objects.equals(namespace, that.namespace) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode = 31 * hashCode + Objects.hashCode(namespace); + hashCode = 31 * hashCode + Objects.hashCode(name); + return hashCode; + } +} diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/IdentityProvider.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/IdentityProvider.java new file mode 100644 index 000000000000..f1729a748477 --- /dev/null +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/IdentityProvider.java @@ -0,0 +1,62 @@ +/* + * 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.identity.spi; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; + +/** + * Interface for loading {@link Identity} that is used for authentication. + */ +@SdkPublicApi +@ThreadSafe +public interface IdentityProvider { + /** + * Retrieve the class of identity this identity provider produces. + * + * This is necessary for the SDK core to determine which identity provider should be used to resolve a specific type of + * identity. + */ + Class identityType(); + + /** + * Resolve the identity from this identity provider. + * @param request The request to resolve an Identity + */ + CompletableFuture resolveIdentity(ResolveIdentityRequest request); + + /** + * Resolve the identity from this identity provider. + * + * Similar to {@link #resolveIdentity(ResolveIdentityRequest)}, but takes a lambda to configure a new + * {@link ResolveIdentityRequest.Builder}. This removes the need to call {@link ResolveIdentityRequest#builder()} and + * {@link ResolveIdentityRequest.Builder#build()}. + * + * @param consumer A {@link Consumer} to which an empty {@link ResolveIdentityRequest.Builder} will be given. + */ + default CompletableFuture resolveIdentity(Consumer consumer) { + return resolveIdentity(ResolveIdentityRequest.builder().applyMutation(consumer).build()); + } + + /** + * Resolve the identity from this identity provider. + */ + default CompletableFuture resolveIdentity() { + return resolveIdentity(ResolveIdentityRequest.builder().build()); + } +} diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/IdentityProviders.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/IdentityProviders.java new file mode 100644 index 000000000000..9fd22da84b50 --- /dev/null +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/IdentityProviders.java @@ -0,0 +1,52 @@ +/* + * 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.identity.spi; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.identity.spi.internal.DefaultIdentityProviders; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * An interface to allow retrieving an IdentityProvider based on the identity type. + */ +@SdkPublicApi +public interface IdentityProviders extends ToCopyableBuilder { + + /** + * Retrieve an identity provider for the provided identity type. + */ + IdentityProvider identityProvider(Class identityType); + + /** + * Get a new builder for creating a {@link IdentityProviders}. + */ + static Builder builder() { + return DefaultIdentityProviders.builder(); + } + + /** + * A builder for a {@link IdentityProviders}. + */ + interface Builder extends CopyableBuilder { + + /** + * Add the {@link IdentityProvider} for a given type. If a provider of that type, as determined by {@link + * IdentityProvider#identityType()} is already added, it will be replaced. + */ + Builder putIdentityProvider(IdentityProvider identityProvider); + } +} diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/ResolveIdentityRequest.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/ResolveIdentityRequest.java new file mode 100644 index 000000000000..f14336cd7c70 --- /dev/null +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/ResolveIdentityRequest.java @@ -0,0 +1,60 @@ +/* + * 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.identity.spi; + +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.identity.spi.internal.DefaultResolveIdentityRequest; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * A request to resolve an {@link Identity}. + *

+ * The Identity may be determined for each request based on properties of the request (e.g. different credentials per bucket + * for S3). + * + * @see IdentityProvider + */ +@SdkPublicApi +@Immutable +@ThreadSafe +public interface ResolveIdentityRequest extends ToCopyableBuilder { + + /** + * Get a new builder for creating a {@link ResolveIdentityRequest}. + */ + static Builder builder() { + return DefaultResolveIdentityRequest.builder(); + } + + /** + * Returns the value of a property that the {@link IdentityProvider} can use while resolving the identity. + */ + T property(IdentityProperty property); + + /** + * A builder for a {@link ResolveIdentityRequest}. + */ + interface Builder extends CopyableBuilder { + + /** + * Set a property that the {@link IdentityProvider} can use while resolving the identity. + */ + Builder putProperty(IdentityProperty key, T value); + } +} diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/TokenIdentity.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/TokenIdentity.java new file mode 100644 index 000000000000..794b32811993 --- /dev/null +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/TokenIdentity.java @@ -0,0 +1,79 @@ +/* + * 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.identity.spi; + +import java.util.Objects; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * Provides token which is used to securely authorize requests to services that use token based auth, e.g., OAuth. + * + *

For more details on OAuth tokens, see: + * + * https://oauth.net/2/access-tokens

+ */ +@SdkPublicApi +@ThreadSafe +public interface TokenIdentity extends Identity { + + /** + * Retrieves string field representing the literal token string. + */ + String token(); + + /** + * Constructs a new token object, which can be used to authorize requests to services that use token based auth + * + * @param token The token used to authorize requests. + */ + static TokenIdentity create(String token) { + Validate.paramNotNull(token, "token"); + + return new TokenIdentity() { + @Override + public String token() { + return token; + } + + @Override + public String toString() { + return ToString.builder("TokenIdentity") + .add("token", token) + .build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TokenIdentity that = (TokenIdentity) o; + return Objects.equals(token, that.token()); + } + + @Override + public int hashCode() { + return Objects.hashCode(token()); + } + }; + } +} diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsCredentialsIdentity.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsCredentialsIdentity.java new file mode 100644 index 000000000000..7cca889709b1 --- /dev/null +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsCredentialsIdentity.java @@ -0,0 +1,104 @@ +/* + * 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.identity.spi.internal; + +import java.util.Objects; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +@SdkInternalApi +public final class DefaultAwsCredentialsIdentity implements AwsCredentialsIdentity { + + private final String accessKeyId; + private final String secretAccessKey; + + private DefaultAwsCredentialsIdentity(Builder builder) { + this.accessKeyId = builder.accessKeyId; + this.secretAccessKey = builder.secretAccessKey; + + Validate.paramNotNull(accessKeyId, "accessKeyId"); + Validate.paramNotNull(secretAccessKey, "secretAccessKey"); + } + + public static AwsCredentialsIdentity.Builder builder() { + return new Builder(); + } + + @Override + public String accessKeyId() { + return accessKeyId; + } + + @Override + public String secretAccessKey() { + return secretAccessKey; + } + + @Override + public String toString() { + return ToString.builder("AwsCredentialsIdentity") + .add("accessKeyId", accessKeyId) + .build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AwsCredentialsIdentity that = (AwsCredentialsIdentity) o; + return Objects.equals(accessKeyId, that.accessKeyId()) && + Objects.equals(secretAccessKey, that.secretAccessKey()); + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode = 31 * hashCode + Objects.hashCode(accessKeyId); + hashCode = 31 * hashCode + Objects.hashCode(secretAccessKey); + return hashCode; + } + + private static final class Builder implements AwsCredentialsIdentity.Builder { + private String accessKeyId; + private String secretAccessKey; + + private Builder() { + } + + @Override + public Builder accessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + return this; + } + + @Override + public Builder secretAccessKey(String secretAccessKey) { + this.secretAccessKey = secretAccessKey; + return this; + } + + @Override + public AwsCredentialsIdentity build() { + return new DefaultAwsCredentialsIdentity(this); + } + } +} diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsSessionCredentialsIdentity.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsSessionCredentialsIdentity.java new file mode 100644 index 000000000000..067d490b3838 --- /dev/null +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsSessionCredentialsIdentity.java @@ -0,0 +1,121 @@ +/* + * 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.identity.spi.internal; + +import java.util.Objects; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +@SdkInternalApi +public final class DefaultAwsSessionCredentialsIdentity implements AwsSessionCredentialsIdentity { + + private final String accessKeyId; + private final String secretAccessKey; + private final String sessionToken; + + private DefaultAwsSessionCredentialsIdentity(Builder builder) { + this.accessKeyId = builder.accessKeyId; + this.secretAccessKey = builder.secretAccessKey; + this.sessionToken = builder.sessionToken; + + Validate.paramNotNull(accessKeyId, "accessKeyId"); + Validate.paramNotNull(secretAccessKey, "secretAccessKey"); + Validate.paramNotNull(sessionToken, "sessionToken"); + } + + public static AwsSessionCredentialsIdentity.Builder builder() { + return new Builder(); + } + + @Override + public String accessKeyId() { + return accessKeyId; + } + + @Override + public String secretAccessKey() { + return secretAccessKey; + } + + @Override + public String sessionToken() { + return sessionToken; + } + + @Override + public String toString() { + return ToString.builder("AwsSessionCredentialsIdentity") + .add("accessKeyId", accessKeyId) + .build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AwsSessionCredentialsIdentity that = (AwsSessionCredentialsIdentity) o; + return Objects.equals(accessKeyId, that.accessKeyId()) && + Objects.equals(secretAccessKey, that.secretAccessKey()) && + Objects.equals(sessionToken, that.sessionToken()); + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode = 31 * hashCode + Objects.hashCode(accessKeyId); + hashCode = 31 * hashCode + Objects.hashCode(secretAccessKey); + hashCode = 31 * hashCode + Objects.hashCode(sessionToken); + return hashCode; + } + + private static final class Builder implements AwsSessionCredentialsIdentity.Builder { + private String accessKeyId; + private String secretAccessKey; + private String sessionToken; + + private Builder() { + } + + @Override + public Builder accessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + return this; + } + + @Override + public Builder secretAccessKey(String secretAccessKey) { + this.secretAccessKey = secretAccessKey; + return this; + } + + @Override + public Builder sessionToken(String sessionToken) { + this.sessionToken = sessionToken; + return this; + } + + @Override + public AwsSessionCredentialsIdentity build() { + return new DefaultAwsSessionCredentialsIdentity(this); + } + } +} diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultIdentityProviders.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultIdentityProviders.java new file mode 100644 index 000000000000..863db0710b30 --- /dev/null +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultIdentityProviders.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.identity.spi.internal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkInternalApi; +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.utils.Lazy; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * A default implementation of {@link IdentityProviders}. This implementation holds a map of {@link IdentityProvider}s and + * retrieves from the collection based on identity type. + */ +@Immutable +@SdkInternalApi +public final class DefaultIdentityProviders implements IdentityProviders { + /** + * TODO(sra-identity-auth): Currently, some customers assume we won't interact with the identity providers when we create + * the client. This isn't true - we need to call identityType. To TEMPORARILY work around those customer's tests failing, + * this is marked lazy. Once we fully migrate over to the SRA as the default code path, we should remove this lazy and + * ticket everyone in live who is making those bad assumptions. + */ + private final Lazy, IdentityProvider>> identityProviders; + private final List> identityProvidersList; + + private DefaultIdentityProviders(BuilderImpl builder) { + this.identityProvidersList = new ArrayList<>(builder.identityProviders); + this.identityProviders = new Lazy<>(() -> { + Map, IdentityProvider> result = new HashMap<>(); + for (IdentityProvider identityProvider : identityProvidersList) { + result.put(identityProvider.identityType(), identityProvider); + } + return result; + }); + } + + public static Builder builder() { + return new BuilderImpl(); + } + + @Override + public IdentityProvider identityProvider(Class identityType) { + return (IdentityProvider) identityProviders.getValue().get(identityType); + } + + @Override + public Builder toBuilder() { + return new BuilderImpl(this); + } + + @Override + public String toString() { + return ToString.builder("IdentityProviders") + .add("identityProviders", identityProvidersList) + .build(); + } + + private static final class BuilderImpl implements Builder { + private final List> identityProviders = new ArrayList<>(); + + private BuilderImpl() { + } + + private BuilderImpl(DefaultIdentityProviders identityProviders) { + this.identityProviders.addAll(identityProviders.identityProvidersList); + } + + @Override + public Builder putIdentityProvider(IdentityProvider identityProvider) { + Validate.paramNotNull(identityProvider, "identityProvider"); + identityProviders.add(identityProvider); + return this; + } + + public IdentityProviders build() { + return new DefaultIdentityProviders(this); + } + } +} diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultResolveIdentityRequest.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultResolveIdentityRequest.java new file mode 100644 index 000000000000..a615a42f3610 --- /dev/null +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultResolveIdentityRequest.java @@ -0,0 +1,97 @@ +/* + * 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.identity.spi.internal; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.identity.spi.IdentityProperty; +import software.amazon.awssdk.identity.spi.ResolveIdentityRequest; +import software.amazon.awssdk.utils.ToString; + +@SdkInternalApi +@Immutable +@ThreadSafe +public final class DefaultResolveIdentityRequest implements ResolveIdentityRequest { + + private final Map, Object> properties; + + private DefaultResolveIdentityRequest(BuilderImpl builder) { + this.properties = new HashMap<>(builder.properties); + } + + public static Builder builder() { + return new BuilderImpl(); + } + + @Override + public T property(IdentityProperty property) { + return (T) properties.get(property); + } + + @Override + public Builder toBuilder() { + return new BuilderImpl(this); + } + + @Override + public String toString() { + return ToString.builder("ResolveIdentityRequest") + .add("properties", properties) + .build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DefaultResolveIdentityRequest that = (DefaultResolveIdentityRequest) o; + return properties.equals(that.properties); + } + + @Override + public int hashCode() { + return Objects.hashCode(properties); + } + + @SdkInternalApi + public static final class BuilderImpl implements Builder { + private final Map, Object> properties = new HashMap<>(); + + private BuilderImpl() { + } + + private BuilderImpl(DefaultResolveIdentityRequest resolveIdentityRequest) { + this.properties.putAll(resolveIdentityRequest.properties); + } + + public Builder putProperty(IdentityProperty key, T value) { + this.properties.put(key, value); + return this; + } + + public ResolveIdentityRequest build() { + return new DefaultResolveIdentityRequest(this); + } + } +} diff --git a/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentityTest.java b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentityTest.java new file mode 100644 index 000000000000..33974427ab65 --- /dev/null +++ b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentityTest.java @@ -0,0 +1,67 @@ +/* + * 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.identity.spi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.internal.DefaultAwsCredentialsIdentity; + +public class AwsCredentialsIdentityTest { + private static final String ACCESS_KEY_ID = "accessKeyId"; + private static final String SECRET_ACCESS_KEY = "secretAccessKey"; + + @Test + public void equalsHashcode() { + EqualsVerifier.forClass(DefaultAwsCredentialsIdentity.class) + .verify(); + } + + @Test + public void emptyBuilder_ThrowsException() { + assertThrows(NullPointerException.class, () -> AwsCredentialsIdentity.builder().build()); + } + + @Test + public void builderMissingSecretAccessKey_ThrowsException() { + assertThrows(NullPointerException.class, () -> AwsCredentialsIdentity.builder().accessKeyId(ACCESS_KEY_ID).build()); + } + + @Test + public void builderMissingAccessKeyId_ThrowsException() { + assertThrows(NullPointerException.class, () -> AwsCredentialsIdentity.builder().secretAccessKey(SECRET_ACCESS_KEY).build()); + } + + @Test + public void create_isSuccessful() { + AwsCredentialsIdentity identity = AwsCredentialsIdentity.create(ACCESS_KEY_ID, SECRET_ACCESS_KEY); + assertEquals(ACCESS_KEY_ID, identity.accessKeyId()); + assertEquals(SECRET_ACCESS_KEY, identity.secretAccessKey()); + } + + @Test + public void build_isSuccessful() { + AwsCredentialsIdentity identity = AwsCredentialsIdentity.builder() + .accessKeyId(ACCESS_KEY_ID) + .secretAccessKey(SECRET_ACCESS_KEY) + .build(); + assertEquals(ACCESS_KEY_ID, identity.accessKeyId()); + assertEquals(SECRET_ACCESS_KEY, identity.secretAccessKey()); + } +} diff --git a/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentityTest.java b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentityTest.java new file mode 100644 index 000000000000..3702694e4fdd --- /dev/null +++ b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentityTest.java @@ -0,0 +1,79 @@ +/* + * 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.identity.spi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; +import software.amazon.awssdk.identity.spi.internal.DefaultAwsSessionCredentialsIdentity; + +public class AwsSessionCredentialsIdentityTest { + private static final String ACCESS_KEY_ID = "accessKeyId"; + private static final String SECRET_ACCESS_KEY = "secretAccessKey"; + private static final String SESSION_TOKEN = "sessionToken"; + + @Test + public void equalsHashcode() { + EqualsVerifier.forClass(DefaultAwsSessionCredentialsIdentity.class) + .verify(); + } + + @Test + public void emptyBuilder_ThrowsException() { + assertThrows(NullPointerException.class, () -> AwsSessionCredentialsIdentity.builder().build()); + } + + @Test + public void builderMissingSessionToken_ThrowsException() { + assertThrows(NullPointerException.class, () -> AwsSessionCredentialsIdentity.builder() + .accessKeyId(ACCESS_KEY_ID) + .secretAccessKey(SECRET_ACCESS_KEY) + .build()); + } + + @Test + public void builderMissingAccessKeyId_ThrowsException() { + assertThrows(NullPointerException.class, () -> AwsSessionCredentialsIdentity.builder() + .secretAccessKey(SECRET_ACCESS_KEY) + .sessionToken(SESSION_TOKEN) + .build()); + } + + @Test + public void create_isSuccessful() { + AwsSessionCredentialsIdentity identity = AwsSessionCredentialsIdentity.create(ACCESS_KEY_ID, + SECRET_ACCESS_KEY, + SESSION_TOKEN); + assertEquals(ACCESS_KEY_ID, identity.accessKeyId()); + assertEquals(SECRET_ACCESS_KEY, identity.secretAccessKey()); + assertEquals(SESSION_TOKEN, identity.sessionToken()); + } + + @Test + public void build_isSuccessful() { + AwsSessionCredentialsIdentity identity = AwsSessionCredentialsIdentity.builder() + .accessKeyId(ACCESS_KEY_ID) + .secretAccessKey(SECRET_ACCESS_KEY) + .sessionToken(SESSION_TOKEN) + .build(); + assertEquals(ACCESS_KEY_ID, identity.accessKeyId()); + assertEquals(SECRET_ACCESS_KEY, identity.secretAccessKey()); + assertEquals(SESSION_TOKEN, identity.sessionToken()); + } +} diff --git a/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/IdentityPropertyTest.java b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/IdentityPropertyTest.java new file mode 100644 index 000000000000..608b58a813ad --- /dev/null +++ b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/IdentityPropertyTest.java @@ -0,0 +1,43 @@ +/* + * 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.identity.spi; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.UUID; +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +public class IdentityPropertyTest { + + @Test + public void equalsHashcode() { + EqualsVerifier.forClass(IdentityProperty.class) + .withNonnullFields("namespace", "name") + .verify(); + } + + @Test + public void namesMustBeUnique() { + String propertyName = UUID.randomUUID().toString(); + + IdentityProperty.create(getClass(), propertyName); + assertThatThrownBy(() -> IdentityProperty.create(getClass(), propertyName)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(getClass().getName()) + .hasMessageContaining(propertyName); + } +} diff --git a/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/IdentityProvidersTest.java b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/IdentityProvidersTest.java new file mode 100644 index 000000000000..4a208607984c --- /dev/null +++ b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/IdentityProvidersTest.java @@ -0,0 +1,155 @@ +/* + * 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.identity.spi; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class IdentityProvidersTest { + + @Test + public void builder_empty_builds() { + assertNotNull(IdentityProviders.builder().build()); + } + + @Test + public void identityProvider_withUnknownType_returnsNull() { + IdentityProvider awsCredentialsProvider = new AwsCredentialsProvider(); + IdentityProviders identityProviders = + IdentityProviders.builder().putIdentityProvider(awsCredentialsProvider).build(); + assertNull(identityProviders.identityProvider(TokenIdentity.class)); + } + + @Test + public void identityProvider_canBeRetrieved() { + IdentityProvider awsCredentialsProvider = new AwsCredentialsProvider(); + IdentityProviders identityProviders = + IdentityProviders.builder().putIdentityProvider(awsCredentialsProvider).build(); + assertSame(awsCredentialsProvider, identityProviders.identityProvider(AwsCredentialsIdentity.class)); + } + + @Test + public void putIdentityProvider_ofSameType_isReplaced() { + IdentityProvider awsCredentialsProvider1 = new AwsCredentialsProvider(); + IdentityProvider awsCredentialsProvider2 = new AwsCredentialsProvider(); + IdentityProviders identityProviders = + IdentityProviders.builder() + .putIdentityProvider(awsCredentialsProvider1) + .putIdentityProvider(awsCredentialsProvider2).build(); + assertSame(awsCredentialsProvider2, identityProviders.identityProvider(AwsCredentialsIdentity.class)); + } + + @Test + public void identityProvider_withSubType_returnsAppropriateSubType() { + IdentityProvider awsCredentialsProvider = new AwsCredentialsProvider(); + IdentityProvider awsSessionCredentialsProvider = new AwsSessionCredentialsProvider(); + IdentityProviders identityProviders = + IdentityProviders.builder() + .putIdentityProvider(awsCredentialsProvider) + .putIdentityProvider(awsSessionCredentialsProvider) + .build(); + + assertSame(awsCredentialsProvider, identityProviders.identityProvider(AwsCredentialsIdentity.class)); + assertSame(awsSessionCredentialsProvider, identityProviders.identityProvider(AwsSessionCredentialsIdentity.class)); + } + + @Test + public void identityProvider_withOnlySubType_returnsNullForParentType() { + IdentityProvider awsSessionCredentialsProvider = new AwsSessionCredentialsProvider(); + IdentityProviders identityProviders = + IdentityProviders.builder() + .putIdentityProvider(awsSessionCredentialsProvider) + .build(); + + assertNull(identityProviders.identityProvider(AwsCredentialsIdentity.class)); + } + + @Test + public void copyBuilder_addIdentityProvider_works() { + IdentityProvider awsCredentialsProvider = new AwsCredentialsProvider(); + IdentityProviders identityProviders = + IdentityProviders.builder() + .putIdentityProvider(awsCredentialsProvider) + .build(); + + IdentityProvider tokenProvider = new TokenProvider(); + identityProviders = identityProviders.copy(builder -> builder.putIdentityProvider(tokenProvider)); + + assertSame(awsCredentialsProvider, identityProviders.identityProvider(AwsCredentialsIdentity.class)); + assertSame(tokenProvider, identityProviders.identityProvider(TokenIdentity.class)); + } + + @Test + public void identityProviders_notTouched_untilNeeded() { + // TODO(sra-identity-auth): This should be removed once everything is on useSraAuth = true + IdentityProvider awsCredentialsProvider = Mockito.mock(IdentityProvider.class); + IdentityProviders providers = + IdentityProviders.builder() + .putIdentityProvider(awsCredentialsProvider) + .build() + .toBuilder() + .putIdentityProvider(awsCredentialsProvider) + .build() + .toBuilder() + .build(); + providers.toString(); + Mockito.verifyNoMoreInteractions(awsCredentialsProvider); + } + + private static final class AwsCredentialsProvider implements IdentityProvider { + + @Override + public Class identityType() { + return AwsCredentialsIdentity.class; + } + + @Override + public CompletableFuture resolveIdentity(ResolveIdentityRequest request) { + return null; + } + } + + private static final class AwsSessionCredentialsProvider implements IdentityProvider { + + @Override + public Class identityType() { + return AwsSessionCredentialsIdentity.class; + } + + @Override + public CompletableFuture resolveIdentity(ResolveIdentityRequest request) { + return null; + } + } + + private static final class TokenProvider implements IdentityProvider { + + @Override + public Class identityType() { + return TokenIdentity.class; + } + + @Override + public CompletableFuture resolveIdentity(ResolveIdentityRequest request) { + return null; + } + } +} diff --git a/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/ResolveIdentityRequestTest.java b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/ResolveIdentityRequestTest.java new file mode 100644 index 000000000000..f92d3bf35344 --- /dev/null +++ b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/ResolveIdentityRequestTest.java @@ -0,0 +1,80 @@ +/* + * 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.identity.spi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.identity.spi.internal.DefaultResolveIdentityRequest; + +public class ResolveIdentityRequestTest { + private static final IdentityProperty PROPERTY_1 = + IdentityProperty.create(ResolveIdentityRequestTest.class, "key_1"); + private static final IdentityProperty PROPERTY_2 = + IdentityProperty.create(ResolveIdentityRequestTest.class, "key_2"); + + @Test + public void equalsHashcode() { + EqualsVerifier.forClass(DefaultResolveIdentityRequest.class) + .withNonnullFields("properties") + .verify(); + } + + @Test + public void emptyBuilder_isSuccessful() { + assertNotNull(ResolveIdentityRequest.builder().build()); + } + + @Test + public void build_withProperty_isSuccessful() { + ResolveIdentityRequest request = ResolveIdentityRequest.builder() + .putProperty(PROPERTY_1, "value") + .build(); + assertEquals("value", request.property(PROPERTY_1)); + } + + @Test + public void putProperty_sameProperty_isReplaced() { + ResolveIdentityRequest request = ResolveIdentityRequest.builder() + .putProperty(PROPERTY_1, "value") + .putProperty(PROPERTY_1, "value2") + .build(); + assertEquals("value2", request.property(PROPERTY_1)); + } + + @Test + public void copyBuilder_addProperty_retains() { + ResolveIdentityRequest request = ResolveIdentityRequest.builder() + .putProperty(PROPERTY_1, "key1value1") + .build(); + + request = request.copy(builder -> builder.putProperty(PROPERTY_2, "key2value1")); + assertEquals("key1value1", request.property(PROPERTY_1)); + assertEquals("key2value1", request.property(PROPERTY_2)); + } + + @Test + public void copyBuilder_updateAddProperty_works() { + ResolveIdentityRequest request = ResolveIdentityRequest.builder() + .putProperty(PROPERTY_1, "key1value1") + .build(); + request = request.copy(builder -> builder.putProperty(PROPERTY_1, "key1value2").putProperty(PROPERTY_2, "key2value1")); + assertEquals("key1value2", request.property(PROPERTY_1)); + assertEquals("key2value1", request.property(PROPERTY_2)); + } +} diff --git a/core/imds/pom.xml b/core/imds/pom.xml index 16ba5a209e2a..39ba4030264d 100644 --- a/core/imds/pom.xml +++ b/core/imds/pom.xml @@ -20,7 +20,7 @@ core software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 imds diff --git a/core/json-utils/pom.xml b/core/json-utils/pom.xml index 0c04adf3d058..4bc934dd0993 100644 --- a/core/json-utils/pom.xml +++ b/core/json-utils/pom.xml @@ -20,7 +20,7 @@ core software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/core/metrics-spi/pom.xml b/core/metrics-spi/pom.xml index b94e624c464c..ea9c75e8eb50 100644 --- a/core/metrics-spi/pom.xml +++ b/core/metrics-spi/pom.xml @@ -5,7 +5,7 @@ core software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/core/pom.xml b/core/pom.xml index e136bd4191b8..a132b8743246 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT core @@ -36,6 +36,12 @@ annotations arns auth + identity-spi + http-auth-spi + http-auth + http-auth-aws + http-auth-aws-crt + http-auth-aws-eventstream auth-crt sdk-core aws-core @@ -47,6 +53,8 @@ endpoints-spi imds crt-core + checksums-spi + checksums diff --git a/core/profiles/pom.xml b/core/profiles/pom.xml index 0ee1a8a583cd..e70a4629ec0d 100644 --- a/core/profiles/pom.xml +++ b/core/profiles/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk core - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT profiles diff --git a/core/protocols/aws-cbor-protocol/pom.xml b/core/protocols/aws-cbor-protocol/pom.xml index f1d17fe71b48..a9cb24ab686c 100644 --- a/core/protocols/aws-cbor-protocol/pom.xml +++ b/core/protocols/aws-cbor-protocol/pom.xml @@ -20,7 +20,7 @@ protocols software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/core/protocols/aws-json-protocol/pom.xml b/core/protocols/aws-json-protocol/pom.xml index c850e031008a..317cefc3c67a 100644 --- a/core/protocols/aws-json-protocol/pom.xml +++ b/core/protocols/aws-json-protocol/pom.xml @@ -20,7 +20,7 @@ protocols software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/core/protocols/aws-query-protocol/pom.xml b/core/protocols/aws-query-protocol/pom.xml index 85e428c8bf6e..7054f7848fdd 100644 --- a/core/protocols/aws-query-protocol/pom.xml +++ b/core/protocols/aws-query-protocol/pom.xml @@ -20,7 +20,7 @@ protocols software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/core/protocols/aws-xml-protocol/pom.xml b/core/protocols/aws-xml-protocol/pom.xml index 420ec94ad587..cb1b9ccf53c3 100644 --- a/core/protocols/aws-xml-protocol/pom.xml +++ b/core/protocols/aws-xml-protocol/pom.xml @@ -20,7 +20,7 @@ protocols software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/core/protocols/pom.xml b/core/protocols/pom.xml index db52fd74da02..b705a857beba 100644 --- a/core/protocols/pom.xml +++ b/core/protocols/pom.xml @@ -20,7 +20,7 @@ core software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/core/protocols/protocol-core/pom.xml b/core/protocols/protocol-core/pom.xml index f82fce330caa..cb9045189b84 100644 --- a/core/protocols/protocol-core/pom.xml +++ b/core/protocols/protocol-core/pom.xml @@ -20,7 +20,7 @@ protocols software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/core/regions/pom.xml b/core/regions/pom.xml index 9e68a7c3fc9f..d7d1ac7d772b 100644 --- a/core/regions/pom.xml +++ b/core/regions/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk core - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT regions diff --git a/core/regions/src/main/java/software/amazon/awssdk/regions/RegionScope.java b/core/regions/src/main/java/software/amazon/awssdk/regions/RegionScope.java index 5f94b71d9a9a..7849321a5cca 100644 --- a/core/regions/src/main/java/software/amazon/awssdk/regions/RegionScope.java +++ b/core/regions/src/main/java/software/amazon/awssdk/regions/RegionScope.java @@ -101,6 +101,10 @@ public int hashCode() { private void validateFormat(String regionScope) { Matcher matcher = REGION_SCOPE_PATTERN.matcher(regionScope); if (!matcher.matches()) { + if (regionScope.contains(",")) { + throw new IllegalArgumentException("Incorrect region scope '" + regionScope + "'. Region scopes with more than " + + "one region defined are not supported."); + } throw new IllegalArgumentException("Incorrect region scope '" + regionScope + "'. Region scope must be a" + " string that either is a complete region string, such as 'us-east-1'," + " or uses the wildcard '*' to represent any region that starts with" diff --git a/core/sdk-core/pom.xml b/core/sdk-core/pom.xml index 3f4fe234b16a..41354389b599 100644 --- a/core/sdk-core/pom.xml +++ b/core/sdk-core/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk core - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sdk-core AWS Java SDK :: SDK Core @@ -51,6 +51,31 @@ endpoints-spi ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + + + software.amazon.awssdk + checksums-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + checksums + ${awsjavasdk.version} + + + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + software.amazon.awssdk utils diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java index 9dc55c2ee910..ae6bd6fd85a5 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java @@ -26,6 +26,7 @@ import java.util.TreeMap; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkPreviewApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.interceptor.ExecutionAttribute; @@ -53,6 +54,7 @@ public abstract class RequestOverrideConfiguration { private final ExecutionAttributes executionAttributes; private final EndpointProvider endpointProvider; private final CompressionConfiguration compressionConfiguration; + private final List plugins; protected RequestOverrideConfiguration(Builder builder) { this.headers = CollectionUtils.deepUnmodifiableMap(builder.headers(), () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)); @@ -65,6 +67,7 @@ protected RequestOverrideConfiguration(Builder builder) { this.executionAttributes = ExecutionAttributes.unmodifiableExecutionAttributes(builder.executionAttributes()); this.endpointProvider = builder.endpointProvider(); this.compressionConfiguration = builder.compressionConfiguration(); + this.plugins = Collections.unmodifiableList(new ArrayList<>(builder.plugins())); } /** @@ -148,6 +151,14 @@ public List metricPublishers() { return metricPublishers; } + /** + * Return the plugins that will be used to update the configuration used by the request. + */ + @SdkPreviewApi + public List plugins() { + return plugins; + } + /** * Returns the additional execution attributes to be added to this request. * This collection of attributes is added in addition to the attributes set on the client. @@ -193,7 +204,8 @@ public boolean equals(Object o) { Objects.equals(metricPublishers, that.metricPublishers) && Objects.equals(executionAttributes, that.executionAttributes) && Objects.equals(endpointProvider, that.endpointProvider) && - Objects.equals(compressionConfiguration, that.compressionConfiguration); + Objects.equals(compressionConfiguration, that.compressionConfiguration) && + Objects.equals(plugins, that.plugins); } @Override @@ -209,6 +221,7 @@ public int hashCode() { hashCode = 31 * hashCode + Objects.hashCode(executionAttributes); hashCode = 31 * hashCode + Objects.hashCode(endpointProvider); hashCode = 31 * hashCode + Objects.hashCode(compressionConfiguration); + hashCode = 31 * hashCode + Objects.hashCode(plugins); return hashCode; } @@ -470,6 +483,29 @@ default B putRawQueryParameter(String name, String value) { CompressionConfiguration compressionConfiguration(); + /** + * Sets the plugins used to update the configuration used by this request. + * + * @param plugins The list of plugins for this request. + * @return This object for method chaining. + */ + @SdkPreviewApi + B plugins(List plugins); + + /** + * Add a plugin used to update the configuration used by this request. + * + * @param plugin The plugin to add. + */ + @SdkPreviewApi + B addPlugin(SdkPlugin plugin); + + /** + * Returns the list of registered plugins + */ + @SdkPreviewApi + List plugins(); + /** * Create a new {@code SdkRequestOverrideConfiguration} with the properties set on this builder. * @@ -489,6 +525,8 @@ protected abstract static class BuilderImpl implements Builde private ExecutionAttributes.Builder executionAttributesBuilder = ExecutionAttributes.builder(); private EndpointProvider endpointProvider; private CompressionConfiguration compressionConfiguration; + private List plugins = new ArrayList<>(); + protected BuilderImpl() { } @@ -504,6 +542,7 @@ protected BuilderImpl(RequestOverrideConfiguration sdkRequestOverrideConfig) { executionAttributes(sdkRequestOverrideConfig.executionAttributes()); endpointProvider(sdkRequestOverrideConfig.endpointProvider); compressionConfiguration(sdkRequestOverrideConfig.compressionConfiguration); + plugins(sdkRequestOverrideConfig.plugins); } @Override @@ -691,5 +730,22 @@ public B compressionConfiguration(Consumer com public CompressionConfiguration compressionConfiguration() { return compressionConfiguration; } + + @Override + public B plugins(List plugins) { + this.plugins = new ArrayList<>(plugins); + return (B) this; + } + + @Override + public B addPlugin(SdkPlugin plugin) { + this.plugins.add(plugin); + return (B) this; + } + + @Override + public List plugins() { + return Collections.unmodifiableList(plugins); + } } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkPlugin.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkPlugin.java new file mode 100644 index 000000000000..1048639926ae --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkPlugin.java @@ -0,0 +1,41 @@ +/* + * 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.core; + +import software.amazon.awssdk.annotations.SdkPreviewApi; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.utils.SdkAutoCloseable; + +/** + * A plugin modifies a client's configuration when the client is created or at request execution + * time. + */ +@SdkPreviewApi +@SdkPublicApi +@ThreadSafe +@FunctionalInterface +public interface SdkPlugin extends SdkAutoCloseable { + + /** + * Modify the provided client configuration. + */ + void configureClient(SdkServiceClientConfiguration.Builder config); + + @Override + default void close() { + } +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkServiceClientConfiguration.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkServiceClientConfiguration.java index 7d727d4069fd..ff88e44e3728 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkServiceClientConfiguration.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkServiceClientConfiguration.java @@ -16,11 +16,14 @@ package software.amazon.awssdk.core; import java.net.URI; +import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.function.Consumer; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; /** * Class to expose SDK service client settings to the user, e.g., ClientOverrideConfiguration @@ -30,13 +33,14 @@ public abstract class SdkServiceClientConfiguration { private final ClientOverrideConfiguration overrideConfiguration; private final URI endpointOverride; - private final EndpointProvider endpointProvider; + private final Map> authSchemes; protected SdkServiceClientConfiguration(Builder builder) { this.overrideConfiguration = builder.overrideConfiguration(); this.endpointOverride = builder.endpointOverride(); this.endpointProvider = builder.endpointProvider(); + this.authSchemes = builder.authSchemes(); } /** @@ -66,6 +70,12 @@ public Optional endpointProvider() { return Optional.ofNullable(this.endpointProvider); } + /** + * @return The configured map of auth schemes. + */ + public Map> authSchemes() { + return authSchemes; + } @Override public boolean equals(Object o) { @@ -79,7 +89,8 @@ public boolean equals(Object o) { SdkServiceClientConfiguration serviceClientConfiguration = (SdkServiceClientConfiguration) o; return Objects.equals(overrideConfiguration, serviceClientConfiguration.overrideConfiguration()) && Objects.equals(endpointOverride, serviceClientConfiguration.endpointOverride().orElse(null)) - && Objects.equals(endpointProvider, serviceClientConfiguration.endpointProvider().orElse(null)); + && Objects.equals(endpointProvider, serviceClientConfiguration.endpointProvider().orElse(null)) + && Objects.equals(authSchemes, serviceClientConfiguration.authSchemes); } @Override @@ -87,6 +98,7 @@ public int hashCode() { int result = overrideConfiguration != null ? overrideConfiguration.hashCode() : 0; result = 31 * result + (endpointOverride != null ? endpointOverride.hashCode() : 0); result = 31 * result + (endpointProvider != null ? endpointProvider.hashCode() : 0); + result = 31 * result + (authSchemes != null ? authSchemes.hashCode() : 0); return result; } @@ -119,6 +131,18 @@ default Builder overrideConfiguration(ClientOverrideConfiguration clientOverride throw new UnsupportedOperationException(); } + default Builder overrideConfiguration(Consumer consumer) { + ClientOverrideConfiguration overrideConfiguration = overrideConfiguration(); + ClientOverrideConfiguration.Builder builder; + if (overrideConfiguration != null) { + builder = overrideConfiguration.toBuilder(); + } else { + builder = ClientOverrideConfiguration.builder(); + } + consumer.accept(builder); + return overrideConfiguration(builder.build()); + } + /** * Configure the endpoint override */ @@ -131,6 +155,20 @@ default Builder endpointProvider(EndpointProvider endpointProvider) { throw new UnsupportedOperationException(); } + /** + * Adds the given auth scheme. Replaces an existing auth scheme with the same id. + */ + default Builder putAuthScheme(AuthScheme authScheme) { + throw new UnsupportedOperationException(); + } + + /** + * Returns the configured map of auth schemes. + */ + default Map> authSchemes() { + throw new UnsupportedOperationException(); + } + /** * Build the service client configuration using the configuration on this builder */ diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SelectedAuthScheme.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SelectedAuthScheme.java new file mode 100644 index 000000000000..10cb488792e2 --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SelectedAuthScheme.java @@ -0,0 +1,53 @@ +/* + * 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.core; + +import java.util.concurrent.CompletableFuture; +import software.amazon.awssdk.annotations.SdkProtectedApi; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.utils.Validate; + +/** + * A container for the identity resolver, signer and auth option that we selected for use with this service call attempt. + */ +@SdkProtectedApi +public final class SelectedAuthScheme { + private final CompletableFuture identity; + private final HttpSigner signer; + private final AuthSchemeOption authSchemeOption; + + public SelectedAuthScheme(CompletableFuture identity, + HttpSigner signer, + AuthSchemeOption authSchemeOption) { + this.identity = Validate.paramNotNull(identity, "identity"); + this.signer = Validate.paramNotNull(signer, "signer"); + this.authSchemeOption = Validate.paramNotNull(authSchemeOption, "authSchemeOption"); + } + + public CompletableFuture identity() { + return identity; + } + + public HttpSigner signer() { + return signer; + } + + public AuthSchemeOption authSchemeOption() { + return authSchemeOption; + } +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkClientBuilder.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkClientBuilder.java index e449073cc1e8..d106d6d27879 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkClientBuilder.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkClientBuilder.java @@ -16,10 +16,14 @@ package software.amazon.awssdk.core.client.builder; import java.net.URI; +import java.util.List; import java.util.function.Consumer; +import software.amazon.awssdk.annotations.SdkPreviewApi; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; import software.amazon.awssdk.utils.builder.SdkBuilder; /** @@ -63,4 +67,32 @@ default B overrideConfiguration(Consumer ov * addressing. */ B endpointOverride(URI endpointOverride); + + /** + * Configure this client with an additional auth scheme, or replace one already on the client. + * + *

By default, the SDK will only know about default auth schemes that ship with the service. If you want to modify those + * existing auth schemes or add a custom one (you select with a custom auth scheme resolver), you can add that new auth + * scheme with this method. + */ + default B putAuthScheme(AuthScheme authScheme) { + throw new UnsupportedOperationException(); + } + + /** + * Adds a plugin to the client builder. The plugins will be invoked when building the client to allow them to change the + * configuration of the built client. + */ + @SdkPreviewApi + default B addPlugin(SdkPlugin plugin) { + throw new UnsupportedOperationException(); + } + + /** + * Returns the list of plugins configured on the client builder. + */ + @SdkPreviewApi + default List plugins() { + throw new UnsupportedOperationException(); + } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java index ecc7bfbe2d54..980db8c4706a 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java @@ -34,6 +34,7 @@ import static software.amazon.awssdk.core.client.config.SdkClientOption.ENDPOINT_OVERRIDDEN; import static software.amazon.awssdk.core.client.config.SdkClientOption.EXECUTION_ATTRIBUTES; import static software.amazon.awssdk.core.client.config.SdkClientOption.EXECUTION_INTERCEPTORS; +import static software.amazon.awssdk.core.client.config.SdkClientOption.IDENTITY_PROVIDERS; import static software.amazon.awssdk.core.client.config.SdkClientOption.INTERNAL_USER_AGENT; import static software.amazon.awssdk.core.client.config.SdkClientOption.METRIC_PUBLISHERS; import static software.amazon.awssdk.core.client.config.SdkClientOption.PROFILE_FILE; @@ -62,9 +63,11 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +import software.amazon.awssdk.annotations.SdkPreviewApi; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.annotations.SdkTestInternalApi; import software.amazon.awssdk.core.CompressionConfiguration; +import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.client.config.ClientAsyncConfiguration; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; @@ -84,6 +87,7 @@ import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.async.AsyncExecuteRequest; import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.profiles.Profile; import software.amazon.awssdk.profiles.ProfileFile; @@ -130,6 +134,7 @@ public abstract class SdkDefaultClientBuilder, private SdkHttpClient.Builder httpClientBuilder; private SdkAsyncHttpClient.Builder asyncHttpClientBuilder; + private final List plugins = new ArrayList<>(); protected SdkDefaultClientBuilder() { this(DEFAULT_HTTP_CLIENT_BUILDER, DEFAULT_ASYNC_HTTP_CLIENT_BUILDER); @@ -181,6 +186,9 @@ protected final SdkClientConfiguration syncClientConfiguration() { configuration = mergeChildDefaults(configuration); configuration = mergeGlobalDefaults(configuration); + // Invoke the plugins after defaults and before finalizing the configuration. + configuration = invokePlugins(configuration); + // Create additional configuration from the default-applied configuration configuration = finalizeChildConfiguration(configuration); configuration = finalizeSyncConfiguration(configuration); @@ -209,6 +217,9 @@ protected final SdkClientConfiguration asyncClientConfiguration() { configuration = mergeChildDefaults(configuration); configuration = mergeGlobalDefaults(configuration); + // Invoke the plugins after defaults and before finalizing the configuration. + configuration = invokePlugins(configuration); + // Create additional configuration from the default-applied configuration configuration = finalizeChildConfiguration(configuration); configuration = finalizeAsyncConfiguration(configuration); @@ -217,11 +228,11 @@ protected final SdkClientConfiguration asyncClientConfiguration() { return configuration; } - private SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { + // TODO: delete this and make the method abstract when we break protected APIs + protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) { if (clientOverrideConfiguration == null) { return configuration; } - SdkClientConfiguration.Builder builder = configuration.toBuilder(); builder.option(SCHEDULED_EXECUTOR_SERVICE, clientOverrideConfiguration.scheduledExecutorService().orElse(null)); @@ -280,7 +291,9 @@ private SdkClientConfiguration mergeGlobalDefaults(SdkClientConfiguration config ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow()) .option(USER_AGENT_PREFIX, SdkUserAgent.create().userAgent()) .option(USER_AGENT_SUFFIX, "") - .option(CRC32_FROM_COMPRESSED_DATA_ENABLED, false)); + .option(CRC32_FROM_COMPRESSED_DATA_ENABLED, false) + .option(IDENTITY_PROVIDERS, IdentityProviders.builder().build())); + return addCompressionConfigGlobalDefaults(configuration); } @@ -393,6 +406,15 @@ private SdkClientConfiguration finalizeConfiguration(SdkClientConfiguration conf .build(); } + /** + * By default, returns the configuration as-is. Classes extending this method will take care of running the plugins and + * return the updated configuration if plugins are supported. + */ + @SdkPreviewApi + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + return config; + } + private String resolveClientUserAgent(SdkClientConfiguration config, RetryPolicy retryPolicy) { return ApplyUserAgentStage.resolveClientUserAgent(config.option(USER_AGENT_PREFIX), config.option(INTERNAL_USER_AGENT), @@ -587,6 +609,17 @@ public final B metricPublishers(List metricPublishers) { return thisBuilder(); } + @Override + public final B addPlugin(SdkPlugin plugin) { + plugins.add(Validate.paramNotNull(plugin, "plugin")); + return thisBuilder(); + } + + @Override + public final List plugins() { + return plugins; + } + /** * Return "this" for method chaining. */ diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientConfiguration.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientConfiguration.java index aabc7a5ba631..8c389ea4f9d2 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientConfiguration.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientConfiguration.java @@ -113,6 +113,13 @@ public Builder option(ClientOption option, T value) { return this; } + /** + * Retrieve the value of a specific option. + */ + public T option(ClientOption option) { + return this.attributes.get(option); + } + @Override public SdkClientConfiguration build() { return new SdkClientConfiguration(attributes.build()); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java index f93acab2487b..f9a17da2b1d8 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java @@ -32,6 +32,9 @@ import software.amazon.awssdk.endpoints.EndpointProvider; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.utils.AttributeMap; @@ -185,6 +188,23 @@ public final class SdkClientOption extends ClientOption { */ public static final SdkClientOption ENDPOINT_PROVIDER = new SdkClientOption<>(EndpointProvider.class); + /** + * The {@link AuthSchemeProvider} configured on the client. + */ + public static final SdkClientOption AUTH_SCHEME_PROVIDER = + new SdkClientOption<>(AuthSchemeProvider.class); + + /** + * The {@link AuthScheme}s configured on the client. + */ + public static final SdkClientOption>> AUTH_SCHEMES = + new SdkClientOption<>(new UnsafeValueType(Map.class)); + + /** + * The IdentityProviders configured on the client. + */ + public static final SdkClientOption IDENTITY_PROVIDERS = new SdkClientOption<>(IdentityProviders.class); + /** * The container for any client contexts parameters set on the client. */ diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/handler/ClientExecutionParams.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/handler/ClientExecutionParams.java index b7ad38664c37..017b94cc57a0 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/handler/ClientExecutionParams.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/handler/ClientExecutionParams.java @@ -22,6 +22,7 @@ import software.amazon.awssdk.core.Response; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.exception.SdkException; import software.amazon.awssdk.core.http.HttpResponseHandler; import software.amazon.awssdk.core.interceptor.ExecutionAttribute; @@ -55,6 +56,7 @@ public final class ClientExecutionParams { private CredentialType credentialType; private MetricCollector metricCollector; private final ExecutionAttributes attributes = new ExecutionAttributes(); + private SdkClientConfiguration requestConfiguration; public Marshaller getMarshaller() { return marshaller; @@ -213,4 +215,13 @@ public ExecutionAttributes executionAttributes() { public MetricCollector getMetricCollector() { return metricCollector; } + + public SdkClientConfiguration requestConfiguration() { + return requestConfiguration; + } + + public ClientExecutionParams withRequestConfiguration(SdkClientConfiguration requestConfiguration) { + this.requestConfiguration = requestConfiguration; + return this; + } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionAttribute.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionAttribute.java index ce6bab1d99ed..3c4dd0819375 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionAttribute.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionAttribute.java @@ -15,10 +15,16 @@ package software.amazon.awssdk.core.interceptor; +import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.utils.Validate; /** * An attribute attached to a particular execution, stored in {@link ExecutionAttributes}. @@ -47,6 +53,7 @@ public final class ExecutionAttribute { private static final ConcurrentMap> NAME_HISTORY = new ConcurrentHashMap<>(); private final String name; + private final ValueStorage storage; /** * Creates a new {@link ExecutionAttribute} bound to the provided type param. @@ -54,10 +61,54 @@ public final class ExecutionAttribute { * @param name Descriptive name for the attribute, used primarily for debugging purposes. */ public ExecutionAttribute(String name) { + this(name, null); + } + + private ExecutionAttribute(String name, ValueStorage storage) { this.name = name; + this.storage = storage == null ? + new DefaultValueStorage() : + storage; ensureUnique(); } + /** + * Create an execution attribute whose value is derived from another attribute. + * + *

Whenever this value is read, its value is read from a different "real" attribute, and whenever this value is written its + * value is written to the "real" attribute, instead. + * + *

This is useful when new attributes are created to replace old attributes, but for backwards-compatibility those old + * attributes still need to be made available. + * + * @param name The name of the attribute to create + * @param attributeType The type of the attribute being created + * @param realAttribute The "real" attribute from which this attribute is derived + */ + public static DerivedAttributeBuilder derivedBuilder(String name, + @SuppressWarnings("unused") Class attributeType, + ExecutionAttribute realAttribute) { + return new DerivedAttributeBuilder<>(name, realAttribute); + } + + /** + * Create an execution attribute whose value get mapped to another execution attribute. + * + *

Whenever this value is read, its value is read from the given attribute, but whenever this value is written its + * value is written to the given attribute AND the mapped attribute. + * + *

This is useful when you have some attribute that depends on the value of another. + * + * @param name The name of the attribute to create + * @param attributeType The type of the attribute being created + * @param realAttributeSupplier The supplier for the mapped attribute which is mapped from this attribute + */ + protected static MappedAttributeBuilder mappedBuilder(String name, + @SuppressWarnings("unused") Class attributeType, + Supplier> realAttributeSupplier) { + return new MappedAttributeBuilder<>(name, realAttributeSupplier); + } + private void ensureUnique() { ExecutionAttribute prev = NAME_HISTORY.putIfAbsent(name, this); if (prev != null) { @@ -104,4 +155,191 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hashCode(name); } + + /** + * Visible for {@link ExecutionAttributes} to invoke when writing or reading values for this attribute. + */ + ValueStorage storage() { + return storage; + } + + public static final class DerivedAttributeBuilder { + private final String name; + private final ExecutionAttribute realAttribute; + private Function readMapping; + private BiFunction writeMapping; + + private DerivedAttributeBuilder(String name, ExecutionAttribute realAttribute) { + this.name = name; + this.realAttribute = realAttribute; + } + + /** + * Set the "read" mapping for this derived attribute. The provided function accepts the current value of the + * "real" attribute and returns the value of the derived attribute. + */ + public DerivedAttributeBuilder readMapping(Function readMapping) { + this.readMapping = readMapping; + return this; + } + + /** + * Set the "write" mapping for this derived attribute. The provided function accepts the current value of the "real" + * attribute, the value that we're trying to set to the derived attribute, and returns the value to set to the "real" + * attribute. + */ + public DerivedAttributeBuilder writeMapping(BiFunction writeMapping) { + this.writeMapping = writeMapping; + return this; + } + + public ExecutionAttribute build() { + return new ExecutionAttribute<>(name, new DerivationValueStorage<>(this)); + } + } + + /** + * The value storage allows reading or writing values to this attribute. Used by {@link ExecutionAttributes} for storing + * attribute values, whether they are "real" or derived. + */ + interface ValueStorage { + /** + * Retrieve an attribute's value from the provided attribute map. + */ + T get(Map, Object> attributes); + + /** + * Set an attribute's value to the provided attribute map. + */ + void set(Map, Object> attributes, T value); + + /** + * Set an attribute's value to the provided attribute map, if the value is not already in the map. + */ + void setIfAbsent(Map, Object> attributes, T value); + } + + /** + * An implementation of {@link ValueStorage} that stores the current execution attribute in the provided attributes map. + */ + private final class DefaultValueStorage implements ValueStorage { + @SuppressWarnings("unchecked") // Safe because of the implementation of set() + @Override + public T get(Map, Object> attributes) { + return (T) attributes.get(ExecutionAttribute.this); + } + + @Override + public void set(Map, Object> attributes, T value) { + attributes.put(ExecutionAttribute.this, value); + } + + @Override + public void setIfAbsent(Map, Object> attributes, T value) { + attributes.putIfAbsent(ExecutionAttribute.this, value); + } + } + + /** + * An implementation of {@link ValueStorage} that derives its value from a different execution attribute in the provided + * attributes map. + */ + private static final class DerivationValueStorage implements ValueStorage { + private final ExecutionAttribute realAttribute; + private final Function readMapping; + private final BiFunction writeMapping; + + private DerivationValueStorage(DerivedAttributeBuilder builder) { + this.realAttribute = Validate.paramNotNull(builder.realAttribute, "realAttribute"); + this.readMapping = Validate.paramNotNull(builder.readMapping, "readMapping"); + this.writeMapping = Validate.paramNotNull(builder.writeMapping, "writeMapping"); + } + + @SuppressWarnings("unchecked") // Safe because of the implementation of set + @Override + public T get(Map, Object> attributes) { + return readMapping.apply((U) attributes.get(realAttribute)); + } + + @SuppressWarnings("unchecked") // Safe because of the implementation of set + @Override + public void set(Map, Object> attributes, T value) { + attributes.compute(realAttribute, (k, real) -> writeMapping.apply((U) real, value)); + } + + @Override + public void setIfAbsent(Map, Object> attributes, T value) { + T currentValue = get(attributes); + if (currentValue == null) { + set(attributes, value); + } + } + } + + private static final class MappedValueStorage implements ValueStorage { + private final Supplier> realAttributeSupplier; + private final AtomicReference previousValue = new AtomicReference<>(); + private final BiFunction readMapping; + private final BiFunction writeMapping; + + private MappedValueStorage(MappedAttributeBuilder builder) { + this.realAttributeSupplier = Validate.paramNotNull(builder.realAttributeSupplier, "realAttributeSupplier"); + this.readMapping = Validate.paramNotNull(builder.readMapping, "readMapping"); + this.writeMapping = Validate.paramNotNull(builder.writeMapping, "writeMapping"); + } + + @SuppressWarnings("unchecked") // Safe because of the implementation of set + @Override + public T get(Map, Object> attributes) { + return previousValue.updateAndGet(t -> readMapping.apply(t, (U) attributes.get(realAttributeSupplier.get()))); + } + + @SuppressWarnings("unchecked") // Safe because of the implementation of set + @Override + public void set(Map, Object> attributes, T value) { + previousValue.set(value); + attributes.compute(realAttributeSupplier.get(), (k, real) -> writeMapping.apply((U) real, value)); + } + + @Override + public void setIfAbsent(Map, Object> attributes, T value) { + previousValue.set(value); + attributes.computeIfAbsent(realAttributeSupplier.get(), k -> writeMapping.apply(null, value)); + } + } + + protected static final class MappedAttributeBuilder { + private final String name; + private final Supplier> realAttributeSupplier; + private BiFunction readMapping; + private BiFunction writeMapping; + + private MappedAttributeBuilder(String name, Supplier> realAttributeSupplier) { + this.name = name; + this.realAttributeSupplier = realAttributeSupplier; + } + + /** + * Set the "read" mapping for this derived attribute. The provided function accepts the current value of the + * "real" attribute and returns the value of the derived attribute. + */ + public MappedAttributeBuilder readMapping(BiFunction readMapping) { + this.readMapping = readMapping; + return this; + } + + /** + * Set the "write" mapping for this derived attribute. The provided function accepts the current value of the "real" + * attribute, the value that we're trying to set to the derived attribute, and returns the value to set to the "real" + * attribute. + */ + public MappedAttributeBuilder writeMapping(BiFunction writeMapping) { + this.writeMapping = writeMapping; + return this; + } + + public ExecutionAttribute build() { + return new ExecutionAttribute<>(name, new MappedValueStorage<>(this)); + } + } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionAttributes.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionAttributes.java index 7de1b8f772d7..099d05ad911f 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionAttributes.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionAttributes.java @@ -44,14 +44,13 @@ public ExecutionAttributes() { protected ExecutionAttributes(Map, ?> attributes) { this.attributes = new HashMap<>(attributes); } - + /** * Retrieve the current value of the provided attribute in this collection of attributes. This will return null if the value * is not set. */ - @SuppressWarnings("unchecked") // Cast is safe due to implementation of {@link #putAttribute} public U getAttribute(ExecutionAttribute attribute) { - return (U) attributes.get(attribute); + return attribute.storage().get(attributes); } /** @@ -66,14 +65,22 @@ public Map, Object> getAttributes() { * This will return Optional Value. */ public Optional getOptionalAttribute(ExecutionAttribute attribute) { - return Optional.ofNullable((U) attributes.get(attribute)); + return Optional.ofNullable(getAttribute(attribute)); } /** * Update or set the provided attribute in this collection of attributes. */ public ExecutionAttributes putAttribute(ExecutionAttribute attribute, U value) { - this.attributes.put(attribute, value); + attribute.storage().set(attributes, value); + return this; + } + + /** + * Set the provided attribute in this collection of attributes if it does not already exist in the collection. + */ + public ExecutionAttributes putAttributeIfAbsent(ExecutionAttribute attribute, U value) { + attribute.storage().setIfAbsent(attributes, value); return this; } @@ -95,14 +102,6 @@ public void putAbsentAttributes(ExecutionAttributes lowerPrecedenceExecutionAttr } } - /** - * Set the provided attribute in this collection of attributes if it does not already exist in the collection. - */ - public ExecutionAttributes putAttributeIfAbsent(ExecutionAttribute attribute, U value) { - attributes.putIfAbsent(attribute, value); - return this; - } - public static Builder builder() { return new Builder(); } @@ -163,15 +162,18 @@ public ExecutionAttributes putAttributeIfAbsent(ExecutionAttribute attrib } } + /** + * TODO: We should deprecate this builder - execution attributes are mutable - why do we need a builder? We can just use + * copy() if it's because of {@link #unmodifiableExecutionAttributes(ExecutionAttributes)}. + */ public static final class Builder implements CopyableBuilder { - - private final Map, Object> executionAttributes = new HashMap<>(); + private final Map, Object> executionAttributes = new HashMap<>(32); private Builder() { } - private Builder(ExecutionAttributes attributes) { - this.executionAttributes.putAll(attributes.attributes); + private Builder(ExecutionAttributes source) { + this.executionAttributes.putAll(source.attributes); } /** @@ -179,7 +181,7 @@ private Builder(ExecutionAttributes attributes) { */ public ExecutionAttributes.Builder put(ExecutionAttribute key, T value) { Validate.notNull(key, "Key to set must not be null."); - executionAttributes.put(key, value); + key.storage().set(executionAttributes, value); return this; } @@ -187,10 +189,19 @@ public ExecutionAttributes.Builder put(ExecutionAttribute key, T value) { * Adds all the attributes from the map provided. */ public ExecutionAttributes.Builder putAll(Map, ?> attributes) { - executionAttributes.putAll(attributes); + attributes.forEach(this::unsafePut); return this; } + /** + * There is no way to make this safe without runtime checks, which we can't do because we don't have the class of T. + * This will just throw an exception at runtime if the types don't match up. + */ + @SuppressWarnings("unchecked") + private void unsafePut(ExecutionAttribute key, Object value) { + key.storage().set(executionAttributes, (T) value); + } + @Override public ExecutionAttributes build() { return new ExecutionAttributes(executionAttributes); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkExecutionAttribute.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkExecutionAttribute.java index 4abbb390a60f..337e235e910e 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkExecutionAttribute.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkExecutionAttribute.java @@ -15,17 +15,35 @@ package software.amazon.awssdk.core.interceptor; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.CRC32; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.CRC32C; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.SHA1; +import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.SHA256; + import java.net.URI; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; import software.amazon.awssdk.core.ClientType; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.ServiceConfiguration; import software.amazon.awssdk.core.checksums.Algorithm; import software.amazon.awssdk.core.checksums.ChecksumSpecs; import software.amazon.awssdk.core.checksums.ChecksumValidation; import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.Identity; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.utils.CompletableFutureUtils; +import software.amazon.awssdk.utils.ImmutableMap; /** * Contains attributes attached to the execution. This information is available to {@link ExecutionInterceptor}s and @@ -53,6 +71,12 @@ public class SdkExecutionAttribute { public static final ExecutionAttribute OPERATION_NAME = new ExecutionAttribute<>("OperationName"); + /** + * The {@link MetricCollector} associated with the overall API call. + */ + public static final ExecutionAttribute API_CALL_METRIC_COLLECTOR = new ExecutionAttribute<>( + "ApiCallMetricCollector"); + /** * The {@link MetricCollector} associated with the current, ongoing API call attempt. This is not set until the actual * internal API call attempt starts. @@ -95,7 +119,12 @@ public class SdkExecutionAttribute { * The RESOLVED_CHECKSUM_SPECS holds the final checksum which will be used for checksum computation. */ public static final ExecutionAttribute RESOLVED_CHECKSUM_SPECS = - new ExecutionAttribute<>("ResolvedChecksumSpecs"); + ExecutionAttribute.mappedBuilder("ResolvedChecksumSpecs", + ChecksumSpecs.class, + () -> SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME) + .readMapping(SdkExecutionAttribute::signerChecksumReadMapping) + .writeMapping(SdkExecutionAttribute::signerChecksumWriteMapping) + .build(); /** * The Algorithm used for checksum validation of a response. @@ -109,6 +138,86 @@ public class SdkExecutionAttribute { public static final ExecutionAttribute HTTP_RESPONSE_CHECKSUM_VALIDATION = new ExecutionAttribute<>( "HttpResponseChecksumValidation"); + private static final ImmutableMap CHECKSUM_ALGORITHM_MAP = ImmutableMap.of( + Algorithm.SHA256, SHA256, + Algorithm.SHA1, SHA1, + Algorithm.CRC32, CRC32, + Algorithm.CRC32C, CRC32C + ); + + private static final ImmutableMap ALGORITHM_MAP = ImmutableMap.of( + SHA256, Algorithm.SHA256, + SHA1, Algorithm.SHA1, + CRC32, Algorithm.CRC32, + CRC32C, Algorithm.CRC32C + ); + protected SdkExecutionAttribute() { } + + private static ChecksumSpecs signerChecksumReadMapping(ChecksumSpecs checksumSpecs, + SelectedAuthScheme authScheme) { + if (authScheme == null) { + return null; + } + + ChecksumAlgorithm checksumAlgorithm = + authScheme.authSchemeOption().signerProperty(AwsV4FamilyHttpSigner.CHECKSUM_ALGORITHM); + + if (checksumAlgorithm == null) { + return null; + } + + + return ChecksumSpecs.builder() + .algorithm(ALGORITHM_MAP.getOrDefault(checksumAlgorithm, null)) + .isRequestStreaming(checksumSpecs != null && checksumSpecs.isRequestStreaming()) + .isRequestChecksumRequired(checksumSpecs != null && checksumSpecs.isRequestChecksumRequired()) + .isValidationEnabled(checksumSpecs != null && checksumSpecs.isValidationEnabled()) + .headerName(checksumSpecs != null ? checksumSpecs.headerName() : null) + .responseValidationAlgorithms(checksumSpecs != null ? checksumSpecs.responseValidationAlgorithms() + : null) + .build(); + } + + private static SelectedAuthScheme signerChecksumWriteMapping(SelectedAuthScheme authScheme, + ChecksumSpecs checksumSpecs) { + ChecksumAlgorithm checksumAlgorithm = + checksumSpecs == null ? null + : CHECKSUM_ALGORITHM_MAP.getOrDefault(checksumSpecs.algorithm(), null); + + if (authScheme == null) { + // This is an unusual use-case. + // Let's assume they're setting the checksum-algorithm so that they can call the signer directly. If that's true, + // then it doesn't really matter what we store other than the checksum-algorithm. + return new SelectedAuthScheme<>(CompletableFuture.completedFuture(new UnsetIdentity()), + new UnsetHttpSigner(), + AuthSchemeOption.builder() + .schemeId("unset") + .putSignerProperty(AwsV4FamilyHttpSigner.CHECKSUM_ALGORITHM, + checksumAlgorithm) + .build()); + } + + return new SelectedAuthScheme<>(authScheme.identity(), + authScheme.signer(), + authScheme.authSchemeOption() + .copy(o -> o.putSignerProperty(AwsV4FamilyHttpSigner.CHECKSUM_ALGORITHM, + checksumAlgorithm))); + } + + private static class UnsetIdentity implements Identity { + } + + private static class UnsetHttpSigner implements HttpSigner { + @Override + public SignedRequest sign(SignRequest request) { + throw new IllegalStateException("A signer was not configured."); + } + + @Override + public CompletableFuture signAsync(AsyncSignRequest request) { + return CompletableFutureUtils.failedFuture(new IllegalStateException("A signer was not configured.")); + } + } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java index 75e999bc1020..f2a57e5c866a 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java @@ -15,13 +15,18 @@ package software.amazon.awssdk.core.interceptor; +import java.util.Map; import software.amazon.awssdk.annotations.SdkProtectedApi; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.interceptor.trait.HttpChecksum; import software.amazon.awssdk.core.interceptor.trait.HttpChecksumRequired; import software.amazon.awssdk.core.internal.interceptor.trait.RequestCompression; import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.endpoints.EndpointProvider; import software.amazon.awssdk.http.SdkHttpExecutionAttributes; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.utils.AttributeMap; /** @@ -93,6 +98,28 @@ public final class SdkInternalExecutionAttribute extends SdkExecutionAttribute { public static final ExecutionAttribute IS_DISCOVERED_ENDPOINT = new ExecutionAttribute<>("IsDiscoveredEndpoint"); + /** + * The auth scheme provider used to resolve the auth scheme for a request. + */ + public static final ExecutionAttribute AUTH_SCHEME_RESOLVER = + new ExecutionAttribute<>("AuthSchemeProvider"); + + /** + * The auth schemes available for a request. + */ + public static final ExecutionAttribute>> AUTH_SCHEMES = new ExecutionAttribute<>("AuthSchemes"); + + /** + * The {@link IdentityProviders} for a request. + */ + public static final ExecutionAttribute IDENTITY_PROVIDERS = new ExecutionAttribute<>("IdentityProviders"); + + /** + * The selected auth scheme for a request. + */ + public static final ExecutionAttribute> SELECTED_AUTH_SCHEME = + new ExecutionAttribute<>("SelectedAuthScheme"); + /** * The supported compression algorithms for an operation, and whether the operation is streaming or not. */ diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseAsyncClientHandler.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseAsyncClientHandler.java index 67f81c65711b..0c2f91a3a424 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseAsyncClientHandler.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseAsyncClientHandler.java @@ -56,14 +56,12 @@ @SdkInternalApi public abstract class BaseAsyncClientHandler extends BaseClientHandler implements AsyncClientHandler { private static final Logger log = Logger.loggerFor(BaseAsyncClientHandler.class); - private final SdkClientConfiguration clientConfiguration; private final AmazonAsyncHttpClient client; private final Function crc32Validator; protected BaseAsyncClientHandler(SdkClientConfiguration clientConfiguration, AmazonAsyncHttpClient client) { super(clientConfiguration); - this.clientConfiguration = clientConfiguration; this.client = client; this.crc32Validator = response -> Crc32Validation.validate(isCalculateCrc32FromCompressedData(), response); } @@ -201,7 +199,8 @@ private Comple InterceptorContext finalizeSdkHttpRequestContext = finalizeSdkHttpFullRequest(executionParams, executionContext, inputT, - clientConfiguration); + resolveRequestConfiguration( + executionParams)); SdkHttpFullRequest marshalled = (SdkHttpFullRequest) finalizeSdkHttpRequestContext.httpRequest(); @@ -223,8 +222,10 @@ private Comple .build(); } + SdkClientConfiguration clientConfiguration = resolveRequestConfiguration(executionParams); CompletableFuture invokeFuture = - invoke(marshalled, + invoke(clientConfiguration, + marshalled, finalizeSdkHttpRequestContext.asyncRequestBody().orElse(null), inputT, executionContext, @@ -272,6 +273,7 @@ private TransformingAsyncResponseHandler resolveErrorRes * configured in the ExecutionContext beforehand. **/ private CompletableFuture invoke( + SdkClientConfiguration clientConfiguration, SdkHttpFullRequest request, AsyncRequestBody requestProvider, InputT originalRequest, @@ -282,6 +284,7 @@ private CompletableFuture invoke( .request(request) .originalRequest(originalRequest) .executionContext(executionContext) + .httpClientDependencies(c -> c.clientConfiguration(clientConfiguration)) .execute(responseHandler); } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java index 21421eeb911a..393770d898f7 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java @@ -192,6 +192,7 @@ private static InterceptorContext runModifyHttpRequestAndHttpContentInterceptors protected ExecutionContext invokeInterceptorsAndCreateExecutionContext( ClientExecutionParams params) { + SdkClientConfiguration clientConfiguration = resolveRequestConfiguration(params); SdkRequest originalRequest = params.getInput(); ExecutionAttributes executionAttributes = params.executionAttributes(); @@ -247,6 +248,14 @@ protected void validateSigningConfiguration(SdkHttpRequest request, Signer signe } } + protected SdkClientConfiguration resolveRequestConfiguration(ClientExecutionParams params) { + SdkClientConfiguration config = params.requestConfiguration(); + if (config != null) { + return config; + } + return clientConfiguration; + } + /** * Decorate response handlers by running after unmarshalling Interceptors and adding http response metadata. */ diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseSyncClientHandler.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseSyncClientHandler.java index 3a6466ac79c3..03ea0683397b 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseSyncClientHandler.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseSyncClientHandler.java @@ -44,13 +44,11 @@ @SdkInternalApi public abstract class BaseSyncClientHandler extends BaseClientHandler implements SyncClientHandler { - private final SdkClientConfiguration clientConfiguration; private final AmazonSyncHttpClient client; protected BaseSyncClientHandler(SdkClientConfiguration clientConfiguration, AmazonSyncHttpClient client) { super(clientConfiguration); - this.clientConfiguration = clientConfiguration; this.client = client; } @@ -92,14 +90,16 @@ public void close() { * Invoke the request using the http client. Assumes credentials (or lack thereof) have been * configured in the OldExecutionContext beforehand. **/ - private OutputT invoke(SdkHttpFullRequest request, - SdkRequest originalRequest, - ExecutionContext executionContext, - HttpResponseHandler> responseHandler) { + private OutputT invoke(SdkClientConfiguration clientConfiguration, + SdkHttpFullRequest request, + SdkRequest originalRequest, + ExecutionContext executionContext, + HttpResponseHandler> responseHandler) { return client.requestExecutionBuilder() .request(request) .originalRequest(originalRequest) .executionContext(executionContext) + .httpClientDependencies(c -> c.clientConfiguration(clientConfiguration)) .execute(responseHandler); } @@ -151,7 +151,8 @@ private ReturnT doExecute( InterceptorContext sdkHttpFullRequestContext = finalizeSdkHttpFullRequest(executionParams, executionContext, inputT, - clientConfiguration); + resolveRequestConfiguration( + executionParams)); SdkHttpFullRequest marshalled = (SdkHttpFullRequest) sdkHttpFullRequestContext.httpRequest(); @@ -168,7 +169,9 @@ private ReturnT doExecute( .build(); } - return invoke(marshalled, + SdkClientConfiguration clientConfiguration = resolveRequestConfiguration(executionParams); + return invoke(clientConfiguration, + marshalled, inputT, executionContext, responseHandler); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonAsyncHttpClient.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonAsyncHttpClient.java index 5f00eb4cfc71..b531bf7fe956 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonAsyncHttpClient.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonAsyncHttpClient.java @@ -18,6 +18,7 @@ import static software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder.async; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.core.ClientType; @@ -77,14 +78,14 @@ public void close() { * @return A builder used to configure and execute a HTTP request. */ public RequestExecutionBuilder requestExecutionBuilder() { - return new RequestExecutionBuilderImpl(); + return new RequestExecutionBuilderImpl() + .httpClientDependencies(httpClientDependencies); } /** * Interface to configure a request execution and execute the request. */ public interface RequestExecutionBuilder { - /** * Fluent setter for {@link AsyncRequestBody} * @@ -117,6 +118,16 @@ public interface RequestExecutionBuilder { */ RequestExecutionBuilder originalRequest(SdkRequest originalRequest); + RequestExecutionBuilder httpClientDependencies(HttpClientDependencies httpClientDependencies); + + HttpClientDependencies httpClientDependencies(); + + default RequestExecutionBuilder httpClientDependencies(Consumer mutator) { + HttpClientDependencies.Builder builder = httpClientDependencies().toBuilder(); + mutator.accept(builder); + return httpClientDependencies(builder.build()); + } + /** * Executes the request with the given configuration. * @@ -128,13 +139,25 @@ public interface RequestExecutionBuilder { CompletableFuture execute(TransformingAsyncResponseHandler> responseHandler); } - private class RequestExecutionBuilderImpl implements RequestExecutionBuilder { + private static class RequestExecutionBuilderImpl implements RequestExecutionBuilder { + private HttpClientDependencies httpClientDependencies; private AsyncRequestBody requestProvider; private SdkHttpFullRequest request; private SdkRequest originalRequest; private ExecutionContext executionContext; + @Override + public RequestExecutionBuilder httpClientDependencies(HttpClientDependencies httpClientDependencies) { + this.httpClientDependencies = httpClientDependencies; + return this; + } + + @Override + public HttpClientDependencies httpClientDependencies() { + return httpClientDependencies; + } + @Override public RequestExecutionBuilder requestProvider(AsyncRequestBody requestProvider) { this.requestProvider = requestProvider; @@ -202,6 +225,5 @@ private RequestExecutionContext createRequestExecutionDependencies() { .executionContext(executionContext) .build(); } - } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonSyncHttpClient.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonSyncHttpClient.java index aed81c4c0aed..2e8de0e2979d 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonSyncHttpClient.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonSyncHttpClient.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.core.internal.http; +import java.util.function.Consumer; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.core.ClientType; @@ -80,7 +81,8 @@ public void close() { * @return A builder used to configure and execute a HTTP request. */ public RequestExecutionBuilder requestExecutionBuilder() { - return new RequestExecutionBuilderImpl(); + return new RequestExecutionBuilderImpl() + .httpClientDependencies(httpClientDependencies); } /** @@ -106,6 +108,16 @@ public interface RequestExecutionBuilder { */ RequestExecutionBuilder executionContext(ExecutionContext executionContext); + RequestExecutionBuilder httpClientDependencies(HttpClientDependencies httpClientDependencies); + + HttpClientDependencies httpClientDependencies(); + + default RequestExecutionBuilder httpClientDependencies(Consumer mutator) { + HttpClientDependencies.Builder builder = httpClientDependencies().toBuilder(); + mutator.accept(builder); + return httpClientDependencies(builder.build()); + } + /** * Executes the request with the given configuration. * @@ -129,8 +141,9 @@ public boolean needsConnectionLeftOpen() { } } - private class RequestExecutionBuilderImpl implements RequestExecutionBuilder { + private static class RequestExecutionBuilderImpl implements RequestExecutionBuilder { + private HttpClientDependencies httpClientDependencies; private SdkHttpFullRequest request; private SdkRequest originalRequest; private ExecutionContext executionContext; @@ -154,6 +167,17 @@ public RequestExecutionBuilder executionContext(ExecutionContext executionContex return this; } + @Override + public RequestExecutionBuilder httpClientDependencies(HttpClientDependencies httpClientDependencies) { + this.httpClientDependencies = httpClientDependencies; + return this; + } + + @Override + public HttpClientDependencies httpClientDependencies() { + return this.httpClientDependencies; + } + @Override public OutputT execute(HttpResponseHandler> responseHandler) { // TODO: We currently have two ways of passing messages to the HTTP client: through the request or through the @@ -209,7 +233,5 @@ private RequestExecutionContext createRequestExecutionDependencies() { .executionContext(executionContext) .build(); } - } - } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/HttpClientDependencies.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/HttpClientDependencies.java index b85218e9ce8e..5f69d87a4ae8 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/HttpClientDependencies.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/HttpClientDependencies.java @@ -19,7 +19,6 @@ import java.util.function.Consumer; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.core.SdkGlobalTime; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline; import software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder; @@ -32,15 +31,15 @@ */ @SdkInternalApi public final class HttpClientDependencies implements SdkAutoCloseable { - private final ClockSkewAdjuster clockSkewAdjuster; - private final SdkClientConfiguration clientConfiguration; - /** * Time offset may be mutated by {@link RequestPipeline} implementations if a clock skew is detected. */ - private volatile int timeOffset = SdkGlobalTime.getGlobalTimeOffset(); + private final SdkClientTime sdkClientTime; + private final ClockSkewAdjuster clockSkewAdjuster; + private final SdkClientConfiguration clientConfiguration; private HttpClientDependencies(Builder builder) { + this.sdkClientTime = builder.sdkClientTime != null ? builder.sdkClientTime : new SdkClientTime(); this.clockSkewAdjuster = builder.clockSkewAdjuster != null ? builder.clockSkewAdjuster : new ClockSkewAdjuster(); this.clientConfiguration = paramNotNull(builder.clientConfiguration, "ClientConfiguration"); } @@ -64,15 +63,18 @@ public ClockSkewAdjuster clockSkewAdjuster() { * @return Current time offset. This is mutable and should not be cached. */ public int timeOffset() { - return timeOffset; + return sdkClientTime.getTimeOffset(); } /** * Updates the time offset of the client as well as the global time offset. */ public void updateTimeOffset(int timeOffset) { - this.timeOffset = timeOffset; - SdkGlobalTime.setGlobalTimeOffset(timeOffset); + sdkClientTime.setTimeOffset(timeOffset); + } + + public Builder toBuilder() { + return new Builder(this); } @Override @@ -84,12 +86,19 @@ public void close() { * Builder for {@link HttpClientDependencies}. */ public static class Builder { + private SdkClientTime sdkClientTime; private ClockSkewAdjuster clockSkewAdjuster; private SdkClientConfiguration clientConfiguration; private Builder() { } + private Builder(HttpClientDependencies from) { + this.sdkClientTime = from.sdkClientTime; + this.clientConfiguration = from.clientConfiguration; + this.clockSkewAdjuster = from.clockSkewAdjuster; + } + public Builder clockSkewAdjuster(ClockSkewAdjuster clockSkewAdjuster) { this.clockSkewAdjuster = clockSkewAdjuster; return this; diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/SdkClientTime.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/SdkClientTime.java new file mode 100644 index 000000000000..0a6ca1e6f11e --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/SdkClientTime.java @@ -0,0 +1,47 @@ +/* + * 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.core.internal.http; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.core.SdkGlobalTime; +import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline; + +/** + * Used for clock skew adjustment between the client JVM where the SDK is run, and the server side. This class mirrors + * {@link SdkGlobalTime} but it's local to the current client and only accessed internally by {@link HttpClientDependencies}. + */ +@SdkInternalApi +public final class SdkClientTime { + /** + * Time offset may be mutated by {@link RequestPipeline} implementations if a clock skew is detected. + */ + private volatile int timeOffset = SdkGlobalTime.getGlobalTimeOffset(); + + /** + * Gets the latest recorded time offset. + */ + public int getTimeOffset() { + return timeOffset; + } + + /** + * Sets the latest recorded time offset. + */ + public void setTimeOffset(int timeOffset) { + this.timeOffset = timeOffset; + SdkGlobalTime.setGlobalTimeOffset(timeOffset); + } +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncBeforeTransmissionExecutionInterceptorsStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncBeforeTransmissionExecutionInterceptorsStage.java index cc558091e42c..4eff8547d362 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncBeforeTransmissionExecutionInterceptorsStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncBeforeTransmissionExecutionInterceptorsStage.java @@ -33,6 +33,7 @@ public CompletableFuture execute(CompletableFuture { if (t != null) { + future.completeExceptionally(t); return; } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStage.java index 547b27f9c2d3..ff0c641ec4de 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStage.java @@ -15,27 +15,46 @@ package software.amazon.awssdk.core.internal.http.pipeline.stages; +import java.nio.ByteBuffer; +import java.time.Clock; import java.time.Duration; +import java.util.Optional; import java.util.concurrent.CompletableFuture; +import org.reactivestreams.Publisher; import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.http.ExecutionContext; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.core.internal.http.HttpClientDependencies; import software.amazon.awssdk.core.internal.http.RequestExecutionContext; import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline; +import software.amazon.awssdk.core.internal.util.MetricUtils; import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.core.signer.AsyncRequestBodySigner; import software.amazon.awssdk.core.signer.AsyncSigner; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.BaseSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.Identity; import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.utils.Logger; @SdkInternalApi public class AsyncSigningStage implements RequestPipeline> { + private static final Logger log = Logger.loggerFor(AsyncSigningStage.class); + private final HttpClientDependencies dependencies; public AsyncSigningStage(HttpClientDependencies dependencies) { @@ -48,7 +67,119 @@ public AsyncSigningStage(HttpClientDependencies dependencies) { @Override public CompletableFuture execute(SdkHttpFullRequest request, RequestExecutionContext context) throws Exception { - return signRequest(request, context); + + updateHttpRequestInInterceptorContext(request, context.executionContext()); + + // Whether pre / post SRA, if old Signer is setup in context, that's the one to use + if (context.signer() != null) { + return signRequest(request, context); + } + // else if AUTH_SCHEMES != null (implies SRA), use SelectedAuthScheme + if (context.executionAttributes().getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES) != null) { + SelectedAuthScheme selectedAuthScheme = + context.executionAttributes().getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + log.debug(() -> String.format("Using SelectedAuthScheme: %s", selectedAuthScheme.authSchemeOption().schemeId())); + return sraSignRequest(request, context, selectedAuthScheme); + } + // else, this implies pre SRA client with authType=None, so don't need to do anything + return CompletableFuture.completedFuture(request); + } + + private CompletableFuture sraSignRequest(SdkHttpFullRequest request, + RequestExecutionContext context, + SelectedAuthScheme selectedAuthScheme) { + adjustForClockSkew(context.executionAttributes()); + CompletableFuture identityFuture = selectedAuthScheme.identity(); + return identityFuture.thenCompose(identity -> { + CompletableFuture signedRequestFuture = MetricUtils.reportDuration( + () -> doSraSign(request, context, selectedAuthScheme, identity), + context.attemptMetricCollector(), + CoreMetric.SIGNING_DURATION); + + return signedRequestFuture.thenApply(r -> { + updateHttpRequestInInterceptorContext(r, context.executionContext()); + return r; + }); + }); + } + + private CompletableFuture doSraSign(SdkHttpFullRequest request, + RequestExecutionContext context, + SelectedAuthScheme selectedAuthScheme, + T identity) { + AuthSchemeOption authSchemeOption = selectedAuthScheme.authSchemeOption(); + HttpSigner signer = selectedAuthScheme.signer(); + + if (context.requestProvider() == null) { + SignRequest.Builder signRequestBuilder = SignRequest + .builder(identity) + .putProperty(HttpSigner.SIGNING_CLOCK, signingClock()) + .request(request) + .payload(request.contentStreamProvider().orElse(null)); + authSchemeOption.forEachSignerProperty(signRequestBuilder::putProperty); + + SignedRequest signedRequest = signer.sign(signRequestBuilder.build()); + return CompletableFuture.completedFuture(toSdkHttpFullRequest(signedRequest)); + } + + AsyncSignRequest.Builder signRequestBuilder = AsyncSignRequest + .builder(identity) + .putProperty(HttpSigner.SIGNING_CLOCK, signingClock()) + .request(request) + .payload(context.requestProvider()); + authSchemeOption.forEachSignerProperty(signRequestBuilder::putProperty); + + CompletableFuture signedRequestFuture = signer.signAsync(signRequestBuilder.build()); + return signedRequestFuture.thenCompose(signedRequest -> { + SdkHttpFullRequest result = toSdkHttpFullRequest(signedRequest); + updateAsyncRequestBodyInContexts(context, signedRequest); + return CompletableFuture.completedFuture(result); + }); + } + + private static void updateAsyncRequestBodyInContexts(RequestExecutionContext context, AsyncSignedRequest signedRequest) { + AsyncRequestBody newAsyncRequestBody; + Optional> optionalPayload = signedRequest.payload(); + if (optionalPayload.isPresent()) { + Publisher signedPayload = optionalPayload.get(); + if (signedPayload instanceof AsyncRequestBody) { + newAsyncRequestBody = (AsyncRequestBody) signedPayload; + } else { + newAsyncRequestBody = AsyncRequestBody.fromPublisher(signedPayload); + } + } else { + newAsyncRequestBody = null; + } + + context.requestProvider(newAsyncRequestBody); + + ExecutionContext executionContext = context.executionContext(); + executionContext.interceptorContext(executionContext.interceptorContext() + .copy(b -> b.asyncRequestBody(newAsyncRequestBody))); + } + + private SdkHttpFullRequest toSdkHttpFullRequest(SignedRequest signedRequest) { + return toSdkHttpFullRequestBuilder(signedRequest).contentStreamProvider(signedRequest.payload().orElse(null)).build(); + } + + private SdkHttpFullRequest toSdkHttpFullRequest(AsyncSignedRequest signedRequest) { + SdkHttpRequest request = signedRequest.request(); + if (request instanceof SdkHttpFullRequest) { + return (SdkHttpFullRequest) request; + } + return toSdkHttpFullRequestBuilder(signedRequest).build(); + } + + private SdkHttpFullRequest.Builder toSdkHttpFullRequestBuilder(BaseSignedRequest baseSignedRequest) { + SdkHttpRequest request = baseSignedRequest.request(); + return SdkHttpFullRequest.builder() + .protocol(request.protocol()) + .method(request.method()) + .host(request.host()) + .port(request.port()) + .encodedPath(request.encodedPath()) + .applyMutation(r -> request.forEachHeader(r::putHeader)) + .applyMutation(r -> request.forEachRawQueryParameter(r::putRawQueryParameter)); } /** @@ -56,15 +187,9 @@ public CompletableFuture execute(SdkHttpFullRequest request, */ private CompletableFuture signRequest(SdkHttpFullRequest request, RequestExecutionContext context) { - updateInterceptorContext(request, context.executionContext()); - Signer signer = context.signer(); MetricCollector metricCollector = context.attemptMetricCollector(); - if (!shouldSign(signer)) { - return CompletableFuture.completedFuture(request); - } - adjustForClockSkew(context.executionAttributes()); AsyncSigner asyncSigner = asAsyncSigner(signer, context); @@ -77,7 +202,7 @@ private CompletableFuture signRequest(SdkHttpFullRequest req Duration.ofNanos(System.nanoTime() - signingStart))); return signedRequestFuture.thenApply(r -> { - updateInterceptorContext(r, context.executionContext()); + updateHttpRequestInInterceptorContext(r, context.executionContext()); return r; }); } @@ -85,17 +210,16 @@ private CompletableFuture signRequest(SdkHttpFullRequest req /** * TODO: Remove when we stop having two copies of the request. */ - private void updateInterceptorContext(SdkHttpFullRequest request, ExecutionContext executionContext) { + private void updateHttpRequestInInterceptorContext(SdkHttpFullRequest request, ExecutionContext executionContext) { executionContext.interceptorContext(executionContext.interceptorContext().copy(b -> b.httpRequest(request))); } /** - * We sign if a signer is provided is not null. - * - * @return True if request should be signed, false if not. + * Returns the {@link Clock} used for signing that already accounts for clock skew when detected by the retryable stage. */ - private boolean shouldSign(Signer signer) { - return signer != null; + private Clock signingClock() { + int offsetInSeconds = dependencies.timeOffset(); + return Clock.offset(Clock.systemUTC(), Duration.ofSeconds(-offsetInSeconds)); } /** @@ -127,7 +251,5 @@ private AsyncSigner asAsyncSigner(Signer signer, RequestExecutionContext context return CompletableFuture.completedFuture(signedRequest); }; - - } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStage.java index aaf1c27428d9..ed57ed8ab18f 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStage.java @@ -19,6 +19,7 @@ import static software.amazon.awssdk.core.HttpChecksumConstant.CONTENT_SHA_256_FOR_UNSIGNED_TRAILER; import static software.amazon.awssdk.core.HttpChecksumConstant.DEFAULT_ASYNC_CHUNK_SIZE; import static software.amazon.awssdk.core.HttpChecksumConstant.SIGNING_METHOD; +import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.AUTH_SCHEMES; import static software.amazon.awssdk.core.internal.io.AwsChunkedEncodingInputStream.DEFAULT_CHUNK_SIZE; import static software.amazon.awssdk.core.internal.util.ChunkContentUtils.calculateChecksumTrailerLength; import static software.amazon.awssdk.core.internal.util.ChunkContentUtils.calculateStreamContentLength; @@ -74,6 +75,11 @@ public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, Re return request; } + // If SRA is enabled, skip flexible checksum in header, since it is handled by SRA signer + if (sraSigningEnabled(context)) { + return request; + } + if (flexibleChecksumInHeaderRequired(context, resolvedChecksumSpecs)) { addFlexibleChecksumInHeader(request, context, resolvedChecksumSpecs); return request; @@ -128,6 +134,12 @@ private void addMd5ChecksumInHeader(SdkHttpFullRequest.Builder request) { } private boolean flexibleChecksumInTrailerRequired(RequestExecutionContext context, ChecksumSpecs checksumSpecs) { + + // If SRA is enabled and it's sync client, skip flexible checksum trailer, since it is handled in SRA signer + if (sraSigningEnabled(context) && clientType == ClientType.SYNC) { + return false; + } + boolean hasRequestBody = true; if (clientType == ClientType.SYNC) { hasRequestBody = context.executionContext().interceptorContext().requestBody().isPresent(); @@ -146,6 +158,10 @@ private boolean flexibleChecksumInTrailerRequired(RequestExecutionContext contex clientType, checksumSpecs, hasRequestBody, isContentStreaming); } + private static boolean sraSigningEnabled(RequestExecutionContext context) { + return context.executionAttributes().getAttribute(AUTH_SCHEMES) != null; + } + /** * Adds flexible checksum to trailers. * diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStage.java index 53a0f6f670b9..2399c39cab58 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStage.java @@ -15,22 +15,32 @@ package software.amazon.awssdk.core.internal.http.pipeline.stages; +import java.time.Clock; import java.time.Duration; +import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.http.ExecutionContext; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.core.internal.http.HttpClientDependencies; import software.amazon.awssdk.core.internal.http.InterruptMonitor; import software.amazon.awssdk.core.internal.http.RequestExecutionContext; import software.amazon.awssdk.core.internal.http.pipeline.RequestToRequestPipeline; import software.amazon.awssdk.core.internal.util.MetricUtils; import software.amazon.awssdk.core.metrics.CoreMetric; -import software.amazon.awssdk.core.signer.AsyncRequestBodySigner; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpRequest; +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.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.Identity; import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.utils.CompletableFutureUtils; +import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.Pair; /** @@ -40,6 +50,8 @@ @SdkInternalApi public class SigningStage implements RequestToRequestPipeline { + private static final Logger log = Logger.loggerFor(SigningStage.class); + private final HttpClientDependencies dependencies; public SigningStage(HttpClientDependencies dependencies) { @@ -52,57 +64,105 @@ public SigningStage(HttpClientDependencies dependencies) { @Override public SdkHttpFullRequest execute(SdkHttpFullRequest request, RequestExecutionContext context) throws Exception { InterruptMonitor.checkInterrupted(); - return signRequest(request, context); + + updateHttpRequestInInterceptorContext(request, context.executionContext()); + + // Whether pre / post SRA, if old Signer is setup in context, that's the one to use + if (context.signer() != null) { + return signRequest(request, context); + } + // else if AUTH_SCHEMES != null (implies SRA), use SelectedAuthScheme + if (context.executionAttributes().getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES) != null) { + SelectedAuthScheme selectedAuthScheme = + context.executionAttributes().getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + log.debug(() -> String.format("Using SelectedAuthScheme: %s", selectedAuthScheme.authSchemeOption().schemeId())); + return sraSignRequest(request, context, selectedAuthScheme); + } + // else, this implies pre SRA client, with authType=None, so don't need to do anything + return request; + } + + private SdkHttpFullRequest sraSignRequest(SdkHttpFullRequest request, + RequestExecutionContext context, + SelectedAuthScheme selectedAuthScheme) { + adjustForClockSkew(context.executionAttributes()); + CompletableFuture identityFuture = selectedAuthScheme.identity(); + T identity = CompletableFutureUtils.joinLikeSync(identityFuture); + + Pair measuredSign = MetricUtils.measureDuration( + () -> doSraSign(request, selectedAuthScheme, identity)); + context.attemptMetricCollector().reportMetric(CoreMetric.SIGNING_DURATION, measuredSign.right()); + + SdkHttpFullRequest signedRequest = measuredSign.left(); + updateHttpRequestInInterceptorContext(signedRequest, context.executionContext()); + return signedRequest; + } + + private SdkHttpFullRequest doSraSign(SdkHttpFullRequest request, + SelectedAuthScheme selectedAuthScheme, + T identity) { + SignRequest.Builder signRequestBuilder = SignRequest + .builder(identity) + .putProperty(HttpSigner.SIGNING_CLOCK, signingClock()) + .request(request) + .payload(request.contentStreamProvider().orElse(null)); + AuthSchemeOption authSchemeOption = selectedAuthScheme.authSchemeOption(); + authSchemeOption.forEachSignerProperty(signRequestBuilder::putProperty); + + HttpSigner signer = selectedAuthScheme.signer(); + SignedRequest signedRequest = signer.sign(signRequestBuilder.build()); + return toSdkHttpFullRequest(signedRequest); + } + + private SdkHttpFullRequest toSdkHttpFullRequest(SignedRequest signedRequest) { + SdkHttpRequest request = signedRequest.request(); + + return SdkHttpFullRequest.builder() + .contentStreamProvider(signedRequest.payload().orElse(null)) + .protocol(request.protocol()) + .method(request.method()) + .host(request.host()) + .port(request.port()) + .encodedPath(request.encodedPath()) + .applyMutation(r -> request.forEachHeader(r::putHeader)) + .applyMutation(r -> request.forEachRawQueryParameter(r::putRawQueryParameter)) + .build(); } /** * Sign the request if the signer if provided and credentials are present. */ - private SdkHttpFullRequest signRequest(SdkHttpFullRequest request, RequestExecutionContext context) throws Exception { - updateInterceptorContext(request, context.executionContext()); - + private SdkHttpFullRequest signRequest(SdkHttpFullRequest request, RequestExecutionContext context) { Signer signer = context.signer(); MetricCollector metricCollector = context.attemptMetricCollector(); - if (shouldSign(signer)) { - adjustForClockSkew(context.executionAttributes()); - - Pair measuredSign = MetricUtils.measureDuration(() -> - signer.sign(request, context.executionAttributes())); + adjustForClockSkew(context.executionAttributes()); - metricCollector.reportMetric(CoreMetric.SIGNING_DURATION, measuredSign.right()); + Pair measuredSign = MetricUtils.measureDuration( + () -> signer.sign(request, context.executionAttributes())); - SdkHttpFullRequest signedRequest = measuredSign.left(); + metricCollector.reportMetric(CoreMetric.SIGNING_DURATION, measuredSign.right()); - if (signer instanceof AsyncRequestBodySigner) { - //Transform request body provider with signing operator - AsyncRequestBody transformedRequestProvider = - ((AsyncRequestBodySigner) signer) - .signAsyncRequestBody(signedRequest, context.requestProvider(), context.executionAttributes()); - context.requestProvider(transformedRequestProvider); - } - updateInterceptorContext(signedRequest, context.executionContext()); - return signedRequest; - } + SdkHttpFullRequest signedRequest = measuredSign.left(); - return request; + updateHttpRequestInInterceptorContext(signedRequest, context.executionContext()); + return signedRequest; } /** * TODO: Remove when we stop having two copies of the request. */ - private void updateInterceptorContext(SdkHttpFullRequest request, ExecutionContext executionContext) { + private void updateHttpRequestInInterceptorContext(SdkHttpFullRequest request, ExecutionContext executionContext) { executionContext.interceptorContext(executionContext.interceptorContext().copy(b -> b.httpRequest(request))); } /** - * We sign if a signer is provided is not null. - * - * @return True if request should be signed, false if not. + * Returns the {@link Clock} used for signing that already accounts for clock skew when detected by the retryable stage. */ - private boolean shouldSign(Signer signer) { - return signer != null; + private Clock signingClock() { + int offsetInSeconds = dependencies.timeOffset(); + return Clock.offset(Clock.systemUTC(), Duration.ofSeconds(-offsetInSeconds)); } /** diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/util/MetricUtils.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/util/MetricUtils.java index bee59eae51ff..9041c3a6c9a3 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/util/MetricUtils.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/util/MetricUtils.java @@ -22,6 +22,7 @@ import java.net.URISyntaxException; import java.time.Duration; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.core.exception.SdkClientException; @@ -32,6 +33,7 @@ import software.amazon.awssdk.http.SdkHttpFullResponse; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.metrics.NoOpMetricCollector; +import software.amazon.awssdk.metrics.SdkMetric; import software.amazon.awssdk.utils.Pair; /** @@ -56,6 +58,26 @@ public static Pair measureDuration(Supplier c) { return Pair.of(result, d); } + /** + * Report a duration metric of the given {@link CompletableFuture} supplier. + * + * @param c The callable to measure. + * @param metricCollector The MetricCollector where the metric is to be reported. + * @param metric The metric to be reported. + * @return A {@code Pair} containing the result of {@code c} and the duration. + */ + public static CompletableFuture reportDuration(Supplier> c, + MetricCollector metricCollector, + SdkMetric metric) { + long start = System.nanoTime(); + CompletableFuture result = c.get(); + result.whenComplete((r, t) -> { + Duration d = Duration.ofNanos(System.nanoTime() - start); + metricCollector.reportMetric(metric, d); + }); + return result; + } + /** * Measure the duration of the given callable. * diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java index bc4a00954ca8..21b9b6f7d28b 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java @@ -41,6 +41,7 @@ import static software.amazon.awssdk.core.client.config.SdkClientOption.SCHEDULED_EXECUTOR_SERVICE; import static software.amazon.awssdk.core.internal.SdkInternalTestAdvancedClientOption.ENDPOINT_OVERRIDDEN_OVERRIDE; +import com.google.common.collect.ImmutableSet; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; @@ -53,6 +54,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Supplier; @@ -367,6 +369,9 @@ public void explicitAsyncHttpClientProvided_ClientIsNotManagedBySdk() { @Test public void clientBuilderFieldsHaveBeanEquivalents() throws Exception { + // Mutating properties might not have bean equivalents. This is probably fine, since very few customers require + // bean-equivalent methods and it's not clear what they'd expect them to be named anyway. Ignore these methods for now. + Set NON_BEAN_EQUIVALENT_METHODS = ImmutableSet.of("addPlugin", "plugins", "putAuthScheme"); SdkClientBuilder builder = testClientBuilder(); BeanInfo beanInfo = Introspector.getBeanInfo(builder.getClass()); @@ -375,10 +380,14 @@ public void clientBuilderFieldsHaveBeanEquivalents() throws Exception { Arrays.stream(clientBuilderMethods).filter(m -> !m.isSynthetic()).forEach(builderMethod -> { String propertyName = builderMethod.getName(); + if (NON_BEAN_EQUIVALENT_METHODS.contains(propertyName)) { + return; + } + Optional propertyForMethod = - Arrays.stream(beanInfo.getPropertyDescriptors()) - .filter(property -> property.getName().equals(propertyName)) - .findFirst(); + Arrays.stream(beanInfo.getPropertyDescriptors()) + .filter(property -> property.getName().equals(propertyName)) + .findFirst(); assertThat(propertyForMethod).as(propertyName + " property").hasValueSatisfying(property -> { assertThat(property.getReadMethod()).as(propertyName + " getter").isNull(); @@ -387,7 +396,6 @@ public void clientBuilderFieldsHaveBeanEquivalents() throws Exception { }); } - @Test public void defaultProfileFileSupplier_isStaticOrHasIdentityCaching() { SdkClientConfiguration config = diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStageTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStageTest.java new file mode 100644 index 000000000000..134002694aa9 --- /dev/null +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStageTest.java @@ -0,0 +1,571 @@ +/* + * 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.core.internal.http.pipeline.stages; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static software.amazon.awssdk.core.interceptor.SdkExecutionAttribute.TIME_OFFSET; +import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME; +import static software.amazon.awssdk.core.metrics.CoreMetric.SIGNING_DURATION; + +import java.nio.ByteBuffer; +import java.time.Clock; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.concurrent.CompletableFuture; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SelectedAuthScheme; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.http.ExecutionContext; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.InterceptorContext; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.internal.http.HttpClientDependencies; +import software.amazon.awssdk.core.internal.http.RequestExecutionContext; +import software.amazon.awssdk.core.signer.AsyncRequestBodySigner; +import software.amazon.awssdk.core.signer.AsyncSigner; +import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.metrics.MetricCollector; +import utils.ValidSdkObjects; + +@RunWith(MockitoJUnitRunner.class) +public class AsyncSigningStageTest { + private static final int TEST_TIME_OFFSET = 17; + private static final SignerProperty SIGNER_PROPERTY = SignerProperty.create(AsyncSigningStageTest.class, "key"); + + @Mock + private Identity identity; + + @Mock + private HttpSigner httpSigner; + + @Mock + private Signer oldSigner; + + @Mock + MetricCollector metricCollector; + + @Captor + private ArgumentCaptor> signRequestCaptor; + + @Captor + private ArgumentCaptor> asyncSignRequestCaptor; + + private HttpClientDependencies httpClientDependencies; + private AsyncSigningStage stage; + + @Before + public void setup() { + httpClientDependencies = HttpClientDependencies.builder() + .clientConfiguration(SdkClientConfiguration.builder().build()) + .build(); + // when tests update TimeOffset to non-zero value, it also sets SdkGlobalTime.setGlobalTimeOffset, + // so explicitly setting this to default value before each test. + httpClientDependencies.updateTimeOffset(0); + stage = new AsyncSigningStage(httpClientDependencies); + } + + @Test + public void execute_selectedAuthScheme_nullSigner_doesSraSign() throws Exception { + // Set up a scheme with a signer property + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(identity), + httpSigner, + AuthSchemeOption.builder() + .schemeId("my.auth#myAuth") + .putSignerProperty(SIGNER_PROPERTY, "value") + .build()); + RequestExecutionContext context = createContext(selectedAuthScheme, null); + + SdkHttpRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + when(httpSigner.sign(ArgumentMatchers.>any())) + .thenReturn(SignedRequest.builder() + .request(signedRequest) + .build()); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + SdkHttpFullRequest result = stage.execute(request, context).join(); + assertThat(context.executionAttributes().getAttribute(TIME_OFFSET)) + .isEqualTo(httpClientDependencies.timeOffset()); + + assertThat(result).usingRecursiveComparison().isEqualTo(signedRequest); + // assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // assert that the input to the signer is as expected, including that signer properties are set + verify(httpSigner).sign(signRequestCaptor.capture()); + SignRequest signRequest = signRequestCaptor.getValue(); + assertThat(signRequest.identity()).isSameAs(identity); + assertThat(signRequest.request()).isSameAs(request); + assertThat(signRequest.property(SIGNER_PROPERTY)).isEqualTo("value"); + + // Assert that the time offset set was zero + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK)).isNotNull(); + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK).instant()) + .isCloseTo(Instant.now(), within(10, ChronoUnit.MILLIS)); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(oldSigner); + } + + @Test + public void execute_selectedAuthScheme_nullSigner_timeOffsetSet_doesSraSignAndAdjustTheSigningClock() throws Exception { + AsyncRequestBody asyncPayload = AsyncRequestBody.fromString("async request body"); + // Set up a scheme with a signer property + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(identity), + httpSigner, + AuthSchemeOption.builder() + .schemeId("my.auth#myAuth") + .putSignerProperty(SIGNER_PROPERTY, "value") + .build()); + RequestExecutionContext context = createContext(selectedAuthScheme, asyncPayload, null); + + SdkHttpRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + Publisher signedPayload = AsyncRequestBody.fromString("signed async request body"); + when(httpSigner.signAsync(ArgumentMatchers.>any())) + .thenReturn( + CompletableFuture.completedFuture(AsyncSignedRequest.builder() + .request(signedRequest) + .payload(signedPayload) + .build())); + + // Setup the timeoffset to test that the clock is setup properly. + httpClientDependencies.updateTimeOffset(TEST_TIME_OFFSET); + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + SdkHttpFullRequest result = stage.execute(request, context).join(); + assertThat(context.executionAttributes().getAttribute(TIME_OFFSET)) + .isEqualTo(httpClientDependencies.timeOffset()); + + assertThat(result).isSameAs(signedRequest); + // assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // assert that the input to the signer is as expected, including that signer properties are set + verify(httpSigner).signAsync(asyncSignRequestCaptor.capture()); + AsyncSignRequest signRequest = asyncSignRequestCaptor.getValue(); + assertThat(signRequest.identity()).isSameAs(identity); + assertThat(signRequest.request()).isSameAs(request); + assertThat(signRequest.property(SIGNER_PROPERTY)).isEqualTo("value"); + + // Assert that the signing clock is setup properly + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK)).isNotNull(); + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK).instant()) + .isCloseTo(Instant.now().minusSeconds(17) + , within(10, ChronoUnit.MILLIS)); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(oldSigner); + } + + @Test + public void execute_selectedAuthScheme_nullSigner_doesSraSignAndDoesNotOverrideAuthSchemeOptionClock() throws Exception { + AsyncRequestBody asyncPayload = AsyncRequestBody.fromString("async request body"); + // Set up a scheme with a signer property and the signing clock set + Clock clock = SigningStageTest.testClock(); + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(identity), + httpSigner, + AuthSchemeOption.builder() + .schemeId("my.auth#myAuth") + .putSignerProperty(SIGNER_PROPERTY, "value") + // The auth scheme option includes the signing clock property + .putSignerProperty(HttpSigner.SIGNING_CLOCK, clock) + .build()); + + RequestExecutionContext context = createContext(selectedAuthScheme, asyncPayload, null); + + SdkHttpRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + when(httpSigner.signAsync(ArgumentMatchers.>any())) + .thenReturn( + CompletableFuture.completedFuture(AsyncSignedRequest.builder() + .request(signedRequest) + .build())); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + SdkHttpFullRequest result = stage.execute(request, context).join(); + assertThat(context.executionAttributes().getAttribute(TIME_OFFSET)) + .isEqualTo(httpClientDependencies.timeOffset()); + + assertThat(result).isSameAs(signedRequest); + // assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // assert that the input to the signer is as expected, including that signer properties are set + verify(httpSigner).signAsync(asyncSignRequestCaptor.capture()); + AsyncSignRequest signRequest = asyncSignRequestCaptor.getValue(); + assertThat(signRequest.identity()).isSameAs(identity); + assertThat(signRequest.request()).isSameAs(request); + assertThat(signRequest.property(SIGNER_PROPERTY)).isEqualTo("value"); + + // Assert that the time offset set was zero + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK)).isNotNull(); + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK).instant()) + .isCloseTo(Instant.now(), within(10, ChronoUnit.MILLIS)); + + // assert that the signing stage does not override the auth-option provided clock. + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK)).isSameAs(clock); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(oldSigner); + } + + @Test + public void execute_selectedAuthScheme_asyncRequestBody_doesSraSignAsync() throws Exception { + AsyncRequestBody asyncPayload = AsyncRequestBody.fromString("async request body"); + + // Set up a scheme with a signer property + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(identity), + httpSigner, + AuthSchemeOption.builder() + .schemeId("my.auth#myAuth") + .putSignerProperty(SIGNER_PROPERTY, "value") + .build()); + RequestExecutionContext context = createContext(selectedAuthScheme, asyncPayload, null); + + SdkHttpRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + Publisher signedPayload = AsyncRequestBody.fromString("signed async request body"); + when(httpSigner.signAsync(ArgumentMatchers.>any())) + .thenReturn( + CompletableFuture.completedFuture(AsyncSignedRequest.builder() + .request(signedRequest) + .payload(signedPayload) + .build())); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + SdkHttpFullRequest result = stage.execute(request, context).join(); + assertThat(context.executionAttributes().getAttribute(TIME_OFFSET)) + .isEqualTo(httpClientDependencies.timeOffset()); + + assertThat(result).isSameAs(signedRequest); + // assert that contexts are updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + assertThat(context.executionContext().interceptorContext().asyncRequestBody().get()).isSameAs(signedPayload); + assertThat(context.requestProvider()).isSameAs(signedPayload); + + // assert that the input to the signer is as expected, including that signer properties are set + verify(httpSigner).signAsync(asyncSignRequestCaptor.capture()); + AsyncSignRequest signRequest = asyncSignRequestCaptor.getValue(); + assertThat(signRequest.identity()).isSameAs(identity); + assertThat(signRequest.request()).isSameAs(request); + assertThat(signRequest.payload().get()).isSameAs(asyncPayload); + assertThat(signRequest.property(SIGNER_PROPERTY)).isEqualTo("value"); + + // Assert that the time offset set was zero + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK)).isNotNull(); + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK).instant()) + .isCloseTo(Instant.now(), within(10, ChronoUnit.MILLIS)); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(oldSigner); + } + + @Test + public void execute_selectedNoAuthAuthScheme_nullSigner_doesSraSign() throws Exception { + AsyncRequestBody asyncPayload = AsyncRequestBody.fromString("async request body"); + // Set up a scheme with smithy.api#noAuth + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(identity), + httpSigner, + AuthSchemeOption.builder() + .schemeId("smithy.api#noAuth") + .putSignerProperty(SIGNER_PROPERTY, "value") + .build()); + RequestExecutionContext context = createContext(selectedAuthScheme, asyncPayload, null); + + SdkHttpRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + Publisher signedPayload = AsyncRequestBody.fromString("signed async request body"); + when(httpSigner.signAsync(ArgumentMatchers.>any())) + .thenReturn( + CompletableFuture.completedFuture(AsyncSignedRequest.builder() + .request(signedRequest) + .payload(signedPayload) + .build())); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + SdkHttpFullRequest result = stage.execute(request, context).join(); + assertThat(context.executionAttributes().getAttribute(TIME_OFFSET)) + .isEqualTo(httpClientDependencies.timeOffset()); + + assertThat(result).isSameAs(signedRequest); + // assert that contexts are updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + assertThat(context.executionContext().interceptorContext().asyncRequestBody().get()).isSameAs(signedPayload); + assertThat(context.requestProvider()).isSameAs(signedPayload); + + // assert that the input to the signer is as expected, including that signer properties are set + verify(httpSigner).signAsync(asyncSignRequestCaptor.capture()); + AsyncSignRequest signRequest = asyncSignRequestCaptor.getValue(); + assertThat(signRequest.identity()).isSameAs(identity); + assertThat(signRequest.request()).isSameAs(request); + assertThat(signRequest.property(SIGNER_PROPERTY)).isEqualTo("value"); + assertThat(signRequest.payload().get()).isSameAs(asyncPayload); + + // Assert that the time offset set was zero + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK)).isNotNull(); + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK).instant()) + .isCloseTo(Instant.now(), within(10, ChronoUnit.MILLIS)); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(oldSigner); + } + + @Test + public void execute_nullSelectedAuthScheme_signer_doesPreSraSign() throws Exception { + RequestExecutionContext context = createContext(null, oldSigner); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + + SdkHttpFullRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + // Creating a copy because original executionAttributes may be directly mutated by SigningStage, e.g., timeOffset + when(oldSigner.sign(request, context.executionAttributes().copy().putAttribute(TIME_OFFSET, 0))).thenReturn(signedRequest); + + SdkHttpFullRequest result = stage.execute(request, context).join(); + + assertThat(result).isSameAs(signedRequest); + // assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(httpSigner); + } + + @Test + public void execute_nullSelectedAuthScheme_AsyncRequestBodySigner_doesPreSraSignAsyncRequestBody() throws Exception { + AsyncRequestBody asyncPayload = AsyncRequestBody.fromString("async request body"); + AsyncRequestBody signedPayload = AsyncRequestBody.fromString("signed async request body"); + + TestAsyncRequestBodySigner asyncRequestBodySigner = mock(TestAsyncRequestBodySigner.class); + RequestExecutionContext context = createContext(null, asyncPayload, asyncRequestBodySigner); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + + SdkHttpFullRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + // Creating a copy because original executionAttributes may be directly mutated by SigningStage, e.g., timeOffset + when(asyncRequestBodySigner.sign(request, context.executionAttributes().copy().putAttribute(TIME_OFFSET, 0))).thenReturn(signedRequest); + when(asyncRequestBodySigner.signAsyncRequestBody(signedRequest, + asyncPayload, + context.executionAttributes().copy().putAttribute(TIME_OFFSET, 0))) + .thenReturn(signedPayload); + + SdkHttpFullRequest result = stage.execute(request, context).join(); + + assertThat(result).isSameAs(signedRequest); + // assert that contexts are updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + // Note: pre SRA code did not set this, so commenting it out, as compared to the SRA test. + // assertThat(context.executionContext().interceptorContext().asyncRequestBody().get()).isSameAs(signedPayload); + assertThat(context.requestProvider()).isSameAs(signedPayload); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(httpSigner); + } + + @Test + public void execute_nullSelectedAuthScheme_AsyncSigner_doesPreSraSignAsync() throws Exception { + AsyncRequestBody asyncPayload = AsyncRequestBody.fromString("async request body"); + + TestAsyncSigner asyncSigner = mock(TestAsyncSigner.class); + RequestExecutionContext context = createContext(null, asyncPayload, asyncSigner); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + + SdkHttpFullRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + // Creating a copy because original executionAttributes may be directly mutated by SigningStage, e.g., timeOffset + when(asyncSigner.sign(request, + asyncPayload, + context.executionAttributes().copy().putAttribute(TIME_OFFSET, 0))) + .thenReturn(CompletableFuture.completedFuture(signedRequest)); + + SdkHttpFullRequest result = stage.execute(request, context).join(); + + assertThat(result).isSameAs(signedRequest); + // assert that contexts are updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + // Note: compared to SRA code, AsyncSigner use case did not set asyncPayload on the context or the + // interceptorContext. + + // So commenting the interceptorContext assertion, as compared to the SRA test. + // assertThat(context.executionContext().interceptorContext().asyncRequestBody().get()).isSameAs(asyncPayload); + + // And the context.requestProvider is the input asyncPayload itself, there is no different signedPayload as compared to + // the SRA test. + assertThat(context.requestProvider()).isSameAs(asyncPayload); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(httpSigner); + } + + @Test + public void execute_nullSelectedAuthScheme_nullSigner_skipsSigning() throws Exception { + RequestExecutionContext context = createContext(null, null); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + SdkHttpFullRequest result = stage.execute(request, context).join(); + + assertThat(result).isSameAs(request); + // assert that interceptor context is updated with result, which is same as request. + // To ensure this asserts the logic in the SigningStage to update the InterceptorContext before the signing logic, + // the request is not set in the InterceptorContext in createContext() + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(request); + + verifyNoInteractions(metricCollector); + + verifyNoInteractions(httpSigner); + } + + @Test + public void execute_nullSelectedAuthScheme_signer_usesTimeOffset() throws Exception { + httpClientDependencies.updateTimeOffset(100); + + RequestExecutionContext context = createContext(null, oldSigner); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + + SdkHttpFullRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + // Creating a copy because original executionAttributes may be directly mutated by SigningStage, e.g., timeOffset + when(oldSigner.sign(request, context.executionAttributes().copy().putAttribute(TIME_OFFSET, 100))).thenReturn(signedRequest); + + SdkHttpFullRequest result = stage.execute(request, context).join(); + assertThat(context.executionAttributes().getAttribute(TIME_OFFSET)) + .isEqualTo(httpClientDependencies.timeOffset()); + + assertThat(result).isSameAs(signedRequest); + // assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(httpSigner); + } + + @Test + public void execute_selectedAuthScheme_signer_doesPreSraSign() throws Exception { + // Set up a scheme + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(identity), + httpSigner, + AuthSchemeOption.builder().schemeId("my.auth#myAuth").build()); + + RequestExecutionContext context = createContext(selectedAuthScheme, oldSigner); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + + SdkHttpFullRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + // Creating a copy because original executionAttributes may be directly mutated by SigningStage, e.g., timeOffset + when(oldSigner.sign(request, context.executionAttributes().copy().putAttribute(TIME_OFFSET, 0))).thenReturn(signedRequest); + + SdkHttpFullRequest result = stage.execute(request, context).join(); + + assertThat(result).isSameAs(signedRequest); + // assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(httpSigner); + } + + private RequestExecutionContext createContext(SelectedAuthScheme selectedAuthScheme, Signer oldSigner) { + return createContext(selectedAuthScheme, null, oldSigner); + } + + private RequestExecutionContext createContext(SelectedAuthScheme selectedAuthScheme, + AsyncRequestBody requestProvider, + Signer oldSigner) { + SdkRequest sdkRequest = ValidSdkObjects.sdkRequest(); + InterceptorContext interceptorContext = + InterceptorContext.builder() + .request(sdkRequest) + // Normally, this would be set, but there is logic to update the InterceptorContext before and + // after signing, so keeping it not set here, so that logic can be asserted in tests. + // .httpRequest(request) + .build(); + + ExecutionAttributes.Builder executionAttributes = ExecutionAttributes.builder() + .put(SELECTED_AUTH_SCHEME, selectedAuthScheme); + if (selectedAuthScheme != null) { + // Doesn't matter that it is empty, just needs to non-null, which implies SRA path. + executionAttributes.put(SdkInternalExecutionAttribute.AUTH_SCHEMES, new HashMap<>()); + } + + ExecutionContext executionContext = ExecutionContext.builder() + .executionAttributes(executionAttributes.build()) + .interceptorContext(interceptorContext) + .signer(oldSigner) + .build(); + + RequestExecutionContext context = RequestExecutionContext.builder() + .executionContext(executionContext) + .originalRequest(sdkRequest) + .requestProvider(requestProvider) + .build(); + context.attemptMetricCollector(metricCollector); + return context; + } + + private interface TestAsyncRequestBodySigner extends Signer, AsyncRequestBodySigner { + } + + private interface TestAsyncSigner extends Signer, AsyncSigner { + } +} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStageTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStageTest.java new file mode 100644 index 000000000000..865535a6298d --- /dev/null +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStageTest.java @@ -0,0 +1,424 @@ +/* + * 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.core.internal.http.pipeline.stages; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static software.amazon.awssdk.core.interceptor.SdkExecutionAttribute.TIME_OFFSET; +import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME; +import static software.amazon.awssdk.core.metrics.CoreMetric.SIGNING_DURATION; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.concurrent.CompletableFuture; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SelectedAuthScheme; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.http.ExecutionContext; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.InterceptorContext; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.internal.http.HttpClientDependencies; +import software.amazon.awssdk.core.internal.http.RequestExecutionContext; +import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpRequest; +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.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.metrics.MetricCollector; +import utils.ValidSdkObjects; + +@RunWith(MockitoJUnitRunner.class) +public class SigningStageTest { + + private static final int TEST_TIME_OFFSET = 17; + private static final SignerProperty SIGNER_PROPERTY = + SignerProperty.create(SigningStageTest.class, "key"); + + @Mock + private Identity identity; + + @Mock + private HttpSigner httpSigner; + + @Mock + private Signer oldSigner; + + @Mock + MetricCollector metricCollector; + + @Captor + private ArgumentCaptor> signRequestCaptor; + + private HttpClientDependencies httpClientDependencies; + private SigningStage stage; + + @Before + public void setup() { + httpClientDependencies = HttpClientDependencies.builder() + .clientConfiguration(SdkClientConfiguration.builder().build()) + .build(); + // when tests update TimeOffset to non-zero value, it also sets SdkGlobalTime.setGlobalTimeOffset, + // so explicitly setting this to default value before each test. + httpClientDependencies.updateTimeOffset(0); + stage = new SigningStage(httpClientDependencies); + } + + @Test + public void execute_selectedAuthScheme_nullSigner_doesSraSign() throws Exception { + // Set up a scheme with a signer property + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(identity), + httpSigner, + AuthSchemeOption.builder() + .schemeId("my.auth#myAuth") + .putSignerProperty(SIGNER_PROPERTY, "value") + .build()); + RequestExecutionContext context = createContext(selectedAuthScheme, null); + + SdkHttpRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + when(httpSigner.sign(ArgumentMatchers.>any())) + .thenReturn(SignedRequest.builder() + .request(signedRequest) + .build()); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + SdkHttpFullRequest result = stage.execute(request, context); + assertThat(context.executionAttributes().getAttribute(TIME_OFFSET)) + .isEqualTo(httpClientDependencies.timeOffset()); + + assertThat(result).usingRecursiveComparison().isEqualTo(signedRequest); + // assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // assert that the input to the signer is as expected, including that signer properties are set + verify(httpSigner).sign(signRequestCaptor.capture()); + SignRequest signRequest = signRequestCaptor.getValue(); + assertThat(signRequest.identity()).isSameAs(identity); + assertThat(signRequest.request()).isSameAs(request); + assertThat(signRequest.property(SIGNER_PROPERTY)).isEqualTo("value"); + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK)).isNotNull(); + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK).instant()) + .isCloseTo(Instant.now(), within(10, ChronoUnit.MILLIS)); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(oldSigner); + } + + @Test + public void execute_selectedAuthScheme_nullSigner_timeOffsetSet_doesSraSignAndAdjustTheSigningClock() throws Exception { + // Set up a scheme with a signer property + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(identity), + httpSigner, + AuthSchemeOption.builder() + .schemeId("my.auth#myAuth") + .putSignerProperty(SIGNER_PROPERTY, "value") + .build()); + RequestExecutionContext context = createContext(selectedAuthScheme, null); + + // Setup the timeoffset to test that the clock is setup properly. + httpClientDependencies.updateTimeOffset(TEST_TIME_OFFSET); + + SdkHttpRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + when(httpSigner.sign(ArgumentMatchers.>any())) + .thenReturn(SignedRequest.builder() + .request(signedRequest) + .build()); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + SdkHttpFullRequest result = stage.execute(request, context); + assertThat(context.executionAttributes().getAttribute(TIME_OFFSET)) + .isEqualTo(httpClientDependencies.timeOffset()); + + assertThat(result).usingRecursiveComparison().isEqualTo(signedRequest); + // Assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // Assert that the input to the signer is as expected, including that signer properties are set + verify(httpSigner).sign(signRequestCaptor.capture()); + SignRequest signRequest = signRequestCaptor.getValue(); + assertThat(signRequest.identity()).isSameAs(identity); + assertThat(signRequest.request()).isSameAs(request); + assertThat(signRequest.property(SIGNER_PROPERTY)).isEqualTo("value"); + + // Assert that the signing clock is setup properly + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK)).isNotNull(); + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK).instant()) + .isCloseTo(Instant.now().minusSeconds(17) + , within(10, ChronoUnit.MILLIS)); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(oldSigner); + } + + @Test + public void execute_selectedAuthScheme_nullSigner_doesSraSignAndDoesNotOverrideAuthSchemeOptionClock() throws Exception { + // Set up a scheme with a signer property and the signing clock set + Clock clock = testClock(); + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(identity), + httpSigner, + AuthSchemeOption.builder() + .schemeId("my.auth#myAuth") + .putSignerProperty(SIGNER_PROPERTY, "value") + // The auth scheme option includes the signing clock property + .putSignerProperty(HttpSigner.SIGNING_CLOCK, clock) + .build()); + RequestExecutionContext context = createContext(selectedAuthScheme, null); + + SdkHttpRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + when(httpSigner.sign(ArgumentMatchers.>any())) + .thenReturn(SignedRequest.builder() + .request(signedRequest) + .build()); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + SdkHttpFullRequest result = stage.execute(request, context); + assertThat(context.executionAttributes().getAttribute(TIME_OFFSET)) + .isEqualTo(httpClientDependencies.timeOffset()); + + assertThat(result).usingRecursiveComparison().isEqualTo(signedRequest); + // assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // assert that the input to the signer is as expected, including that signer properties are set + verify(httpSigner).sign(signRequestCaptor.capture()); + SignRequest signRequest = signRequestCaptor.getValue(); + assertThat(signRequest.identity()).isSameAs(identity); + assertThat(signRequest.request()).isSameAs(request); + assertThat(signRequest.property(SIGNER_PROPERTY)).isEqualTo("value"); + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK)).isNotNull(); + + // assert that the signing stage does not override the auth-option provided clock. + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK)).isSameAs(clock); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(oldSigner); + } + + @Test + public void execute_selectedNoAuthAuthScheme_nullSigner_doesSraSign() throws Exception { + // Set up a scheme with smithy.api#noAuth + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(identity), + httpSigner, + AuthSchemeOption.builder() + .schemeId("smithy.api#noAuth") + .build()); + RequestExecutionContext context = createContext(selectedAuthScheme, null); + + SdkHttpRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + when(httpSigner.sign(ArgumentMatchers.>any())) + .thenReturn(SignedRequest.builder() + .request(signedRequest) + .build()); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + SdkHttpFullRequest result = stage.execute(request, context); + assertThat(context.executionAttributes().getAttribute(TIME_OFFSET)) + .isEqualTo(httpClientDependencies.timeOffset()); + + assertThat(result).usingRecursiveComparison().isEqualTo(signedRequest); + // assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // assert that the input to the signer is as expected, including that signer properties are set + verify(httpSigner).sign(signRequestCaptor.capture()); + SignRequest signRequest = signRequestCaptor.getValue(); + assertThat(signRequest.identity()).isSameAs(identity); + assertThat(signRequest.request()).isSameAs(request); + assertThat(signRequest.property(SIGNER_PROPERTY)).isNull(); + + // Assert that the time offset set was zero + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK)).isNotNull(); + assertThat(signRequest.property(HttpSigner.SIGNING_CLOCK).instant()) + .isCloseTo(Instant.now(), within(10, ChronoUnit.MILLIS)); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(oldSigner); + } + + @Test + public void execute_nullSelectedAuthScheme_signer_doesPreSraSign() throws Exception { + RequestExecutionContext context = createContext(null, oldSigner); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + + SdkHttpFullRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + // Creating a copy because original executionAttributes may be directly mutated by SigningStage, e.g., timeOffset + when(oldSigner.sign(request, context.executionAttributes().copy().putAttribute(TIME_OFFSET, 0))).thenReturn(signedRequest); + + SdkHttpFullRequest result = stage.execute(request, context); + + assertThat(result).isSameAs(signedRequest); + // assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(httpSigner); + } + + @Test + public void execute_nullSelectedAuthScheme_nullSigner_skipsSigning() throws Exception { + RequestExecutionContext context = createContext(null, null); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + SdkHttpFullRequest result = stage.execute(request, context); + + assertThat(result).isSameAs(request); + // assert that interceptor context is updated with result, which is same as request. + // To ensure this asserts the logic in the SigningStage to update the InterceptorContext before the signing logic, + // the request is not set in the InterceptorContext in createContext() + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(request); + + verifyNoInteractions(metricCollector); + + verifyNoInteractions(httpSigner); + } + + @Test + public void execute_nullSelectedAuthScheme_signer_usesTimeOffset() throws Exception { + httpClientDependencies.updateTimeOffset(100); + + RequestExecutionContext context = createContext(null, oldSigner); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + + SdkHttpFullRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + // Creating a copy because original executionAttributes may be directly mutated by SigningStage, e.g., timeOffset + when(oldSigner.sign(request, context.executionAttributes().copy().putAttribute(TIME_OFFSET, 100))).thenReturn(signedRequest); + + SdkHttpFullRequest result = stage.execute(request, context); + + assertThat(result).isSameAs(signedRequest); + // assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(httpSigner); + } + + @Test + public void execute_selectedAuthScheme_signer_doesPreSraSign() throws Exception { + // Set up a scheme + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(identity), + httpSigner, + AuthSchemeOption.builder().schemeId("my.auth#myAuth").build()); + + RequestExecutionContext context = createContext(selectedAuthScheme, oldSigner); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + + SdkHttpFullRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + // Creating a copy because original executionAttributes may be directly mutated by SigningStage, e.g., timeOffset + when(oldSigner.sign(request, context.executionAttributes().copy().putAttribute(TIME_OFFSET, 0))).thenReturn(signedRequest); + + SdkHttpFullRequest result = stage.execute(request, context); + + assertThat(result).isSameAs(signedRequest); + // assert that interceptor context is updated with result + assertThat(context.executionContext().interceptorContext().httpRequest()).isSameAs(result); + + // assert that metrics are collected + verify(metricCollector).reportMetric(eq(SIGNING_DURATION), any()); + + verifyNoInteractions(httpSigner); + } + + private RequestExecutionContext createContext(SelectedAuthScheme selectedAuthScheme, Signer oldSigner) { + SdkRequest sdkRequest = ValidSdkObjects.sdkRequest(); + InterceptorContext interceptorContext = + InterceptorContext.builder() + .request(sdkRequest) + // Normally, this would be set, but there is logic to update the InterceptorContext before and + // after signing, so keeping it not set here, so that logic can be asserted in tests. + // .httpRequest(request) + .build(); + + ExecutionAttributes.Builder executionAttributes = ExecutionAttributes.builder() + .put(SELECTED_AUTH_SCHEME, selectedAuthScheme); + if (selectedAuthScheme != null) { + // Doesn't matter that it is empty, just needs to non-null, which implies SRA path. + executionAttributes.put(SdkInternalExecutionAttribute.AUTH_SCHEMES, new HashMap<>()); + } + + ExecutionContext executionContext = ExecutionContext.builder() + .executionAttributes(executionAttributes.build()) + .interceptorContext(interceptorContext) + .signer(oldSigner) + .build(); + + RequestExecutionContext context = RequestExecutionContext.builder() + .executionContext(executionContext) + .originalRequest(sdkRequest) + .build(); + context.attemptMetricCollector(metricCollector); + return context; + } + + public static Clock testClock() { + return new Clock() { + @Override + public ZoneId getZone() { + throw new UnsupportedOperationException(); + } + + @Override + public Clock withZone(ZoneId zone) { + throw new UnsupportedOperationException(); + } + + @Override + public Instant instant() { + return Instant.now(); + } + }; + } +} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/util/MetricUtilsTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/util/MetricUtilsTest.java index b32a543a7404..1a150620e87e 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/util/MetricUtilsTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/util/MetricUtilsTest.java @@ -16,19 +16,24 @@ package software.amazon.awssdk.core.internal.util; import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import java.io.IOException; import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; import software.amazon.awssdk.core.http.HttpResponseHandler; import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.http.HttpMetric; import software.amazon.awssdk.http.SdkHttpFullResponse; import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.metrics.SdkMetric; import software.amazon.awssdk.utils.Pair; public class MetricUtilsTest { @@ -36,7 +41,7 @@ public class MetricUtilsTest { public ExpectedException thrown = ExpectedException.none(); @Test - public void testMeasureDuration_returnsAccurateDurationInformation() { + public void measureDuration_returnsAccurateDurationInformation() { long testDurationNanos = Duration.ofMillis(1).toNanos(); Pair measuredExecute = MetricUtils.measureDuration(() -> { @@ -51,7 +56,7 @@ public void testMeasureDuration_returnsAccurateDurationInformation() { } @Test - public void testMeasureDuration_returnsCallableReturnValue() { + public void measureDuration_returnsCallableReturnValue() { String result = "foo"; Pair measuredExecute = MetricUtils.measureDuration(() -> result); @@ -60,7 +65,7 @@ public void testMeasureDuration_returnsCallableReturnValue() { } @Test - public void testMeasureDurationUnsafe_doesNotWrapException() throws Exception { + public void measureDurationUnsafe_doesNotWrapException() throws Exception { IOException ioe = new IOException("boom"); thrown.expect(IOException.class); @@ -75,7 +80,7 @@ public void testMeasureDurationUnsafe_doesNotWrapException() throws Exception { } @Test - public void testMeasureDuration_doesNotWrapException() { + public void measureDuration_doesNotWrapException() { RuntimeException e = new RuntimeException("boom"); thrown.expect(RuntimeException.class); @@ -91,7 +96,82 @@ public void testMeasureDuration_doesNotWrapException() { } @Test - public void testCollectHttpMetrics_collectsAllExpectedMetrics() { + public void reportDuration_completableFuture_reportsAccurateDurationInformation() { + MetricCollector mockCollector = mock(MetricCollector.class); + SdkMetric mockMetric = mock(SdkMetric.class); + + CompletableFuture future = new CompletableFuture<>(); + CompletableFuture result = MetricUtils.reportDuration(() -> future, mockCollector, mockMetric); + + long testDurationNanos = Duration.ofMillis(1).toNanos(); + long start = System.nanoTime(); + // spin thread instead of Thread.sleep() for a bit more accuracy... + while (System.nanoTime() - start < testDurationNanos) { + } + future.complete("foo"); + future.join(); + + ArgumentCaptor duration = ArgumentCaptor.forClass(Duration.class); + verify(mockCollector).reportMetric(eq(mockMetric), duration.capture()); + assertThat(duration.getValue()).isGreaterThanOrEqualTo(Duration.ofNanos(testDurationNanos)); + } + + @Test + public void reportDuration_completableFuture_completesExceptionally_reportsAccurateDurationInformation() { + MetricCollector mockCollector = mock(MetricCollector.class); + SdkMetric mockMetric = mock(SdkMetric.class); + + CompletableFuture future = new CompletableFuture<>(); + CompletableFuture result = MetricUtils.reportDuration(() -> future, mockCollector, mockMetric); + + long testDurationNanos = Duration.ofMillis(1).toNanos(); + long start = System.nanoTime(); + // spin thread instead of Thread.sleep() for a bit more accuracy... + while (System.nanoTime() - start < testDurationNanos) { + } + future.completeExceptionally(new RuntimeException("future failed")); + try { + future.join(); + } catch (CompletionException e) { + ArgumentCaptor duration = ArgumentCaptor.forClass(Duration.class); + verify(mockCollector).reportMetric(eq(mockMetric), duration.capture()); + assertThat(duration.getValue()).isGreaterThanOrEqualTo(Duration.ofNanos(testDurationNanos)); + } + } + + @Test + public void reportDuration_completableFuture_returnsCallableReturnValue() { + MetricCollector mockCollector = mock(MetricCollector.class); + SdkMetric mockMetric = mock(SdkMetric.class); + + CompletableFuture future = new CompletableFuture<>(); + + CompletableFuture result = MetricUtils.reportDuration(() -> future, mockCollector, mockMetric); + + assertThat(result).isEqualTo(result); + } + + @Test + public void reportDuration_completableFuture_doesNotWrapException() { + MetricCollector mockCollector = mock(MetricCollector.class); + SdkMetric mockMetric = mock(SdkMetric.class); + + RuntimeException e = new RuntimeException("boom"); + + thrown.expect(RuntimeException.class); + + try { + MetricUtils.reportDuration(() -> { + throw e; + }, mockCollector, mockMetric); + } catch (RuntimeException caught) { + assertThat(caught).isSameAs(e); + throw caught; + } + } + + @Test + public void collectHttpMetrics_collectsAllExpectedMetrics() { MetricCollector mockCollector = mock(MetricCollector.class); int statusCode = 200; diff --git a/docs/design/core/sra-identity-auth/DecisionLog.md b/docs/design/core/sra-identity-auth/DecisionLog.md new file mode 100644 index 000000000000..1b7b738d1993 --- /dev/null +++ b/docs/design/core/sra-identity-auth/DecisionLog.md @@ -0,0 +1,49 @@ +# Decision Log for Smithy Reference Architecture Identity and Auth support + +## Log Entry Template + +**Source:** (Meeting/aside/pair programming discussion/daily standup) to (discuss/implement) X + +**Attendees:** (names) + +**Closed Decisions:** + +1. Question? Decision. Justification. + +**Open Decisions:** + +1. (Old/Reopened/New) Question? + +## 3/31/23 + +**Source:** Meeting for API surface area review of Identity changes made as part of SRA. + +**Attendees:** Anna-Karin, David, Debora, Dongie, Jay, John, Matt, Olivier, Zoe + +**Closed Decisions:** + +1. **Should the new interface `AwsCredentialsIdentity` provide `create()` methods?** + 1. Yes, to provide customers a way to easily create instances of this type without needing to depend on + `AwsBasicCredentials` from the `auth` module. Some duplication of code from `AwsBasicCredentials` is okay. + 2. The implementation of `create()` can use an anonymous inner class, instead of creating a new class with a name. +2. **How should `AwsCredentialsProviderChain` support the new Identity type `AwsCredentialsIdentity`?** + 1. `Builder.addCredentialsProvider()` can be overloaded to accept the new type. + 2. The varargs methods `of()` and `Builder.credentialsProviders()` can be overloaded to accept the new type. This + would not be ambiguous when called with zero args, because according to + https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5 the more specific method is chosen, + i.e., `AwsCredentialsProviderChain.of()` would invoke `of(AwsCredentialsProvider...)`. + 3. The `Builder.credentialsProviders()` method accepting a `Collection` cannot be overloaded because both methods + would have the same erasure. So use a method with a different name - `credentialsIdentityProviders()`. We don't + want to add `Identity` to the other methods (varargs, `add`, `of`) too, as it might mislead to thinking that's a + different "property" of the chain. So a separate method name is used only in this one-off. +3. **How should an `IdentityResolver` define which `IdentityProperty`s it supports?** + 1. An `IdentityResolver` should define a `public static` for each `IdentityProperty` it supports and document the + behavior of how it uses it during `resolveIdentity`. This will help the caller determine how to construct an + appropriate `ResolveIdentityRequest`. + 2. Should there be stronger abstraction for any property? + 1. We discussed potential use cases like metrics collector / telemetry. If we have a compelling use case, we can + add it later. Though it would have to be not AWS specific to be added to these generic interfaces. + +**Open Decisions:** + +None \ No newline at end of file diff --git a/http-client-spi/pom.xml b/http-client-spi/pom.xml index f7ecfcc3bb51..d4fefebdc051 100644 --- a/http-client-spi/pom.xml +++ b/http-client-spi/pom.xml @@ -22,7 +22,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT http-client-spi AWS Java SDK :: HTTP Client Interface diff --git a/http-clients/apache-client/pom.xml b/http-clients/apache-client/pom.xml index 2899afbd8579..a3705f481484 100644 --- a/http-clients/apache-client/pom.xml +++ b/http-clients/apache-client/pom.xml @@ -21,7 +21,7 @@ http-clients software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT apache-client diff --git a/http-clients/aws-crt-client/pom.xml b/http-clients/aws-crt-client/pom.xml index 3c06bcf750d4..d9fea1aaaf32 100644 --- a/http-clients/aws-crt-client/pom.xml +++ b/http-clients/aws-crt-client/pom.xml @@ -21,7 +21,7 @@ http-clients software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/http-clients/netty-nio-client/pom.xml b/http-clients/netty-nio-client/pom.xml index de8b1b03ec39..7ca3364f0c6d 100644 --- a/http-clients/netty-nio-client/pom.xml +++ b/http-clients/netty-nio-client/pom.xml @@ -20,7 +20,7 @@ http-clients software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/fault/ServerConnectivityErrorMessageTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/fault/ServerConnectivityErrorMessageTest.java index 7d1424b6c754..70857614033b 100644 --- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/fault/ServerConnectivityErrorMessageTest.java +++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/fault/ServerConnectivityErrorMessageTest.java @@ -135,6 +135,7 @@ void closeTimeHasCorrectMessage(TestCase testCase) throws Exception { .httpMethod(SdkHttpMethod.GET) .contentPublisher(new EmptyPublisher()) .build())) + .as("Closing time: " + testCase.closeTime) .hasMessageContaining(testCase.errorMessageSubstring); } diff --git a/http-clients/pom.xml b/http-clients/pom.xml index b2791960a913..53d4fb5a39a3 100644 --- a/http-clients/pom.xml +++ b/http-clients/pom.xml @@ -21,7 +21,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/http-clients/url-connection-client/pom.xml b/http-clients/url-connection-client/pom.xml index 02df6e42a7bf..706c35e45baf 100644 --- a/http-clients/url-connection-client/pom.xml +++ b/http-clients/url-connection-client/pom.xml @@ -20,7 +20,7 @@ http-clients software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/metric-publishers/cloudwatch-metric-publisher/pom.xml b/metric-publishers/cloudwatch-metric-publisher/pom.xml index 9ce5bf441578..5fe6266835a3 100644 --- a/metric-publishers/cloudwatch-metric-publisher/pom.xml +++ b/metric-publishers/cloudwatch-metric-publisher/pom.xml @@ -17,7 +17,7 @@ software.amazon.awssdk metric-publishers - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cloudwatch-metric-publisher diff --git a/metric-publishers/pom.xml b/metric-publishers/pom.xml index 5d5b2f02980c..38b5165c46ff 100644 --- a/metric-publishers/pom.xml +++ b/metric-publishers/pom.xml @@ -17,7 +17,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT metric-publishers diff --git a/pom.xml b/pom.xml index 69dad7623131..fe22f8ef4df9 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT pom AWS Java SDK :: Parent The Amazon Web Services SDK for Java provides Java APIs @@ -84,6 +84,7 @@ test/auth-tests test/region-testing test/ruleset-testing-core + test/old-client-version-compatibility-test ${scm.github.url} @@ -518,6 +519,9 @@ software.amazon.awssdk:aws-sdk-java org.codehaus.plexus:plexus-utils + + + software.amazon.awssdk:http-auth @@ -637,6 +641,9 @@ *.internal.* software.amazon.awssdk.thirdparty.* software.amazon.awssdk.regions.* + + software.amazon.awssdk.auth.credentials.AwsCredentials + software.amazon.awssdk.auth.token.credentials.SdkToken software.amazon.awssdk.services.s3.model.ObjectPart diff --git a/release-scripts/pom.xml b/release-scripts/pom.xml index b72820cf64bf..2409d8d6e09c 100644 --- a/release-scripts/pom.xml +++ b/release-scripts/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../pom.xml release-scripts diff --git a/release-scripts/src/main/java/software/amazon/awssdk/release/Cli.java b/release-scripts/src/main/java/software/amazon/awssdk/release/Cli.java index e285c1f5628a..7ccf024b5b8a 100644 --- a/release-scripts/src/main/java/software/amazon/awssdk/release/Cli.java +++ b/release-scripts/src/main/java/software/amazon/awssdk/release/Cli.java @@ -63,5 +63,13 @@ protected static Option optionalOption(String longCommand, String description) { return new Option(null, longCommand, true, description); } + protected static Option optionalMultiValueOption(String longCommand, String description) { + return Option.builder() + .longOpt(longCommand) + .desc(description) + .hasArgs() + .build(); + } + protected abstract void run(CommandLine commandLine) throws Exception; } diff --git a/release-scripts/src/main/java/software/amazon/awssdk/release/CreateNewServiceModuleMain.java b/release-scripts/src/main/java/software/amazon/awssdk/release/CreateNewServiceModuleMain.java index 845a78ec169a..9a9f4fbafc1d 100644 --- a/release-scripts/src/main/java/software/amazon/awssdk/release/CreateNewServiceModuleMain.java +++ b/release-scripts/src/main/java/software/amazon/awssdk/release/CreateNewServiceModuleMain.java @@ -24,11 +24,18 @@ import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.cli.CommandLine; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Node; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.internal.CodegenNamingUtils; @@ -46,20 +53,63 @@ * --service-module-name service-module-name * --service-protocol json" * + * + *

By default the service new pom will include a dependency to the http-auth-aws module, this is only needed if the service + * has one or more operations signed by any of the aws algorithms, e.g., sigv4 or sigv4a, but not needed if the service uses, + * say, bearer auth (e.g., codecatalyst at the moment). Excluding this can be done by adding the + * {@code --exclude-internal-dependency http-auth-aws} switch. For example + *

+ * mvn exec:java -pl :release-scripts \
+ *     -Dexec.mainClass="software.amazon.awssdk.release.CreateNewServiceModuleMain" \
+ *     -Dexec.args="--maven-project-root /path/to/root
+ *                  --maven-project-version 2.1.4-SNAPSHOT
+ *                  --service-id 'Service Id'
+ *                  --service-module-name service-module-name
+ *                  --service-protocol json
+ *                  --exclude-internal-dependency http-auth-aws"
+ * 
*/ public class CreateNewServiceModuleMain extends Cli { + + private static final Set DEFAULT_INTERNAL_DEPENDENCIES = toSet("http-auth-aws"); + private CreateNewServiceModuleMain() { super(requiredOption("service-module-name", "The name of the service module to be created."), requiredOption("service-id", "The service ID of the service module to be created."), requiredOption("service-protocol", "The protocol of the service module to be created."), requiredOption("maven-project-root", "The root directory for the maven project."), - requiredOption("maven-project-version", "The maven version of the service module to be created.")); + requiredOption("maven-project-version", "The maven version of the service module to be created."), + optionalMultiValueOption("include-internal-dependency", "Includes an internal dependency from new service pom."), + optionalMultiValueOption("exclude-internal-dependency", "Excludes an internal dependency from new service pom.")); } public static void main(String[] args) { new CreateNewServiceModuleMain().run(args); } + static Set toSet(String...args) { + Set result = new LinkedHashSet<>(); + for (String arg : args) { + result.add(arg); + } + return Collections.unmodifiableSet(result); + + } + + static List toList(String[] optionValues) { + if (optionValues == null) { + return Collections.emptyList(); + } + return Arrays.asList(optionValues); + } + + static Set computeInternalDependencies(List includes, List excludes) { + Set result = new LinkedHashSet<>(DEFAULT_INTERNAL_DEPENDENCIES); + result.addAll(includes); + excludes.forEach(result::remove); + return Collections.unmodifiableSet(result); + } + @Override protected void run(CommandLine commandLine) throws Exception { new NewServiceCreator(commandLine).run(); @@ -71,6 +121,7 @@ private static class NewServiceCreator { private final String serviceModuleName; private final String serviceId; private final String serviceProtocol; + private final Set internalDependencies; private NewServiceCreator(CommandLine commandLine) { this.mavenProjectRoot = Paths.get(commandLine.getOptionValue("maven-project-root").trim()); @@ -78,7 +129,10 @@ private NewServiceCreator(CommandLine commandLine) { this.serviceModuleName = commandLine.getOptionValue("service-module-name").trim(); this.serviceId = commandLine.getOptionValue("service-id").trim(); this.serviceProtocol = transformSpecialProtocols(commandLine.getOptionValue("service-protocol").trim()); - + this.internalDependencies = computeInternalDependencies(toList(commandLine + .getOptionValues("include-internal-dependency")), + toList(commandLine + .getOptionValues("exclude-internal-dependency"))); Validate.isTrue(Files.exists(mavenProjectRoot), "Project root does not exist: " + mavenProjectRoot); } @@ -98,6 +152,9 @@ public void run() throws Exception { createNewModuleFromTemplate(templateModulePath, newServiceModulePath); replaceTemplatePlaceholders(newServiceModulePath); + + Path newServicePom = newServiceModulePath.resolve("pom.xml"); + new AddInternalDependenciesTransformer(internalDependencies).transform(newServicePom); } private void createNewModuleFromTemplate(Path templateModulePath, Path newServiceModule) throws IOException { @@ -142,4 +199,22 @@ private String mavenName(String serviceId) { .collect(Collectors.joining(" ")); } } + + static class AddInternalDependenciesTransformer extends PomTransformer { + private final Set internalDependencies; + + AddInternalDependenciesTransformer(Set internalDependencies) { + this.internalDependencies = internalDependencies; + } + + @Override + protected void updateDocument(Document doc) { + Node project = findChild(doc, "project"); + Node dependencies = findChild(project, "dependencies"); + for (String internalDependency : internalDependencies) { + dependencies.appendChild(sdkDependencyElement(doc, internalDependency)); + } + } + } + } diff --git a/release-scripts/src/main/java/software/amazon/awssdk/release/NewServiceMain.java b/release-scripts/src/main/java/software/amazon/awssdk/release/NewServiceMain.java index cfc6ced3d5cc..accbc9988a3b 100644 --- a/release-scripts/src/main/java/software/amazon/awssdk/release/NewServiceMain.java +++ b/release-scripts/src/main/java/software/amazon/awssdk/release/NewServiceMain.java @@ -16,6 +16,8 @@ package software.amazon.awssdk.release; import static java.nio.charset.StandardCharsets.UTF_8; +import static software.amazon.awssdk.release.CreateNewServiceModuleMain.computeInternalDependencies; +import static software.amazon.awssdk.release.CreateNewServiceModuleMain.toList; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -24,6 +26,9 @@ import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.cli.CommandLine; @@ -49,18 +54,29 @@ * */ public class NewServiceMain extends Cli { + + private static final Set DEFAULT_INTERNAL_DEPENDENCIES = defaultInternalDependencies(); + private NewServiceMain() { super(requiredOption("service-module-name", "The name of the service module to be created."), requiredOption("service-id", "The service ID of the service module to be created."), requiredOption("service-protocol", "The protocol of the service module to be created."), requiredOption("maven-project-root", "The root directory for the maven project."), - requiredOption("maven-project-version", "The maven version of the service module to be created.")); + requiredOption("maven-project-version", "The maven version of the service module to be created."), + optionalMultiValueOption("include-internal-dependency", "Includes an internal dependency from new service pom."), + optionalMultiValueOption("exclude-internal-dependency", "Excludes an internal dependency from new service pom.")); } public static void main(String[] args) { new NewServiceMain().run(args); } + private static Set defaultInternalDependencies() { + Set defaultInternalDependencies = new LinkedHashSet<>(); + defaultInternalDependencies.add("http-auth-aws"); + return Collections.unmodifiableSet(defaultInternalDependencies); + } + @Override protected void run(CommandLine commandLine) throws Exception { new NewServiceCreator(commandLine).run(); @@ -72,6 +88,7 @@ private static class NewServiceCreator { private final String serviceModuleName; private final String serviceId; private final String serviceProtocol; + private final Set internalDependencies; private NewServiceCreator(CommandLine commandLine) { this.mavenProjectRoot = Paths.get(commandLine.getOptionValue("maven-project-root").trim()); @@ -79,7 +96,10 @@ private NewServiceCreator(CommandLine commandLine) { this.serviceModuleName = commandLine.getOptionValue("service-module-name").trim(); this.serviceId = commandLine.getOptionValue("service-id").trim(); this.serviceProtocol = transformSpecialProtocols(commandLine.getOptionValue("service-protocol").trim()); - + this.internalDependencies = computeInternalDependencies(toList(commandLine + .getOptionValues("include-internal-dependency")), + toList(commandLine + .getOptionValues("exclude-internal-dependency"))); Validate.isTrue(Files.exists(mavenProjectRoot), "Project root does not exist: " + mavenProjectRoot); } @@ -100,6 +120,7 @@ public void run() throws Exception { createNewModuleFromTemplate(templateModulePath, newServiceModulePath); replaceTemplatePlaceholders(newServiceModulePath); + Path servicesPomPath = mavenProjectRoot.resolve("services").resolve("pom.xml"); Path aggregatePomPath = mavenProjectRoot.resolve("aws-sdk-java").resolve("pom.xml"); Path bomPomPath = mavenProjectRoot.resolve("bom").resolve("pom.xml"); @@ -107,6 +128,9 @@ public void run() throws Exception { new AddSubmoduleTransformer().transform(servicesPomPath); new AddDependencyTransformer().transform(aggregatePomPath); new AddDependencyManagementDependencyTransformer().transform(bomPomPath); + + Path newServicePom = newServiceModulePath.resolve("pom.xml"); + new CreateNewServiceModuleMain.AddInternalDependenciesTransformer(internalDependencies).transform(newServicePom); } private void createNewModuleFromTemplate(Path templateModulePath, Path newServiceModule) throws IOException { diff --git a/services-custom/dynamodb-enhanced/pom.xml b/services-custom/dynamodb-enhanced/pom.xml index 477a2035cc09..fdb9b796418b 100644 --- a/services-custom/dynamodb-enhanced/pom.xml +++ b/services-custom/dynamodb-enhanced/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services-custom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT dynamodb-enhanced AWS Java SDK :: DynamoDB :: Enhanced Client diff --git a/services-custom/iam-policy-builder/pom.xml b/services-custom/iam-policy-builder/pom.xml index 3ce559d005c9..358a5159b1d4 100644 --- a/services-custom/iam-policy-builder/pom.xml +++ b/services-custom/iam-policy-builder/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml iam-policy-builder diff --git a/services-custom/pom.xml b/services-custom/pom.xml index 15b7f7c575df..8ee77a96f4ee 100644 --- a/services-custom/pom.xml +++ b/services-custom/pom.xml @@ -19,7 +19,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT services-custom AWS Java SDK :: Custom Services diff --git a/services-custom/s3-transfer-manager/pom.xml b/services-custom/s3-transfer-manager/pom.xml index b7b82c00dc3f..6cfcfb9a69fb 100644 --- a/services-custom/s3-transfer-manager/pom.xml +++ b/services-custom/s3-transfer-manager/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml s3-transfer-manager diff --git a/services/accessanalyzer/pom.xml b/services/accessanalyzer/pom.xml index 37fe5449c7f5..4623808fe620 100644 --- a/services/accessanalyzer/pom.xml +++ b/services/accessanalyzer/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT accessanalyzer AWS Java SDK :: Services :: AccessAnalyzer @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version}
+ + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/account/pom.xml b/services/account/pom.xml index 8dc79fc31988..ba8d1881c413 100644 --- a/services/account/pom.xml +++ b/services/account/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT account AWS Java SDK :: Services :: Account @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/acm/pom.xml b/services/acm/pom.xml index 63a8de15cd34..e4b47842cebe 100644 --- a/services/acm/pom.xml +++ b/services/acm/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT acm AWS Java SDK :: Services :: AWS Certificate Manager @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/acm/src/it/java/software/amazon/awssdk/services/acm/AwsCertficateManagerIntegrationTest.java b/services/acm/src/it/java/software/amazon/awssdk/services/acm/AwsCertficateManagerIntegrationTest.java index 253b8d348e9b..fc281388891c 100644 --- a/services/acm/src/it/java/software/amazon/awssdk/services/acm/AwsCertficateManagerIntegrationTest.java +++ b/services/acm/src/it/java/software/amazon/awssdk/services/acm/AwsCertficateManagerIntegrationTest.java @@ -19,7 +19,6 @@ import org.junit.BeforeClass; import org.junit.Test; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.core.exception.SdkServiceException; import software.amazon.awssdk.services.acm.model.AcmException; import software.amazon.awssdk.services.acm.model.GetCertificateRequest; import software.amazon.awssdk.services.acm.model.ListCertificatesRequest; diff --git a/services/acmpca/pom.xml b/services/acmpca/pom.xml index 7edecce8ea4a..37ee353fab71 100644 --- a/services/acmpca/pom.xml +++ b/services/acmpca/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT acmpca AWS Java SDK :: Services :: ACM PCA @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/alexaforbusiness/pom.xml b/services/alexaforbusiness/pom.xml index 3a6c2c4efb3c..a3ce08b01e6e 100644 --- a/services/alexaforbusiness/pom.xml +++ b/services/alexaforbusiness/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 alexaforbusiness @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/amp/pom.xml b/services/amp/pom.xml index ae4872e49cfd..9040aeefc1b3 100644 --- a/services/amp/pom.xml +++ b/services/amp/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT amp AWS Java SDK :: Services :: Amp @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/amplify/pom.xml b/services/amplify/pom.xml index ba0b1d445018..3158ea5c4cf1 100644 --- a/services/amplify/pom.xml +++ b/services/amplify/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT amplify AWS Java SDK :: Services :: Amplify @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/amplifybackend/pom.xml b/services/amplifybackend/pom.xml index c00d4faeb309..86d14599d84c 100644 --- a/services/amplifybackend/pom.xml +++ b/services/amplifybackend/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT amplifybackend AWS Java SDK :: Services :: Amplify Backend @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/amplifyuibuilder/pom.xml b/services/amplifyuibuilder/pom.xml index db53e5c41126..3da601cb1551 100644 --- a/services/amplifyuibuilder/pom.xml +++ b/services/amplifyuibuilder/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT amplifyuibuilder AWS Java SDK :: Services :: Amplify UI Builder @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/apigateway/pom.xml b/services/apigateway/pom.xml index cc3c0e1163be..4cbae7daca40 100644 --- a/services/apigateway/pom.xml +++ b/services/apigateway/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT apigateway AWS Java SDK :: Services :: Amazon API Gateway @@ -62,5 +62,10 @@ guava test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/apigatewaymanagementapi/pom.xml b/services/apigatewaymanagementapi/pom.xml index bf283d3e2cff..c2776623ce5c 100644 --- a/services/apigatewaymanagementapi/pom.xml +++ b/services/apigatewaymanagementapi/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT apigatewaymanagementapi AWS Java SDK :: Services :: ApiGatewayManagementApi @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/apigatewayv2/pom.xml b/services/apigatewayv2/pom.xml index dc42b5d1ff91..f60aa92a383b 100644 --- a/services/apigatewayv2/pom.xml +++ b/services/apigatewayv2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT apigatewayv2 AWS Java SDK :: Services :: ApiGatewayV2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/appconfig/pom.xml b/services/appconfig/pom.xml index 186b7e9966ca..0ef8481518ff 100644 --- a/services/appconfig/pom.xml +++ b/services/appconfig/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT appconfig AWS Java SDK :: Services :: AppConfig @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/appconfigdata/pom.xml b/services/appconfigdata/pom.xml index 35fe39bcfca9..fa9a1dcad965 100644 --- a/services/appconfigdata/pom.xml +++ b/services/appconfigdata/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT appconfigdata AWS Java SDK :: Services :: App Config Data @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/appfabric/pom.xml b/services/appfabric/pom.xml index 1704fb6929e1..325e86a097de 100644 --- a/services/appfabric/pom.xml +++ b/services/appfabric/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT appfabric AWS Java SDK :: Services :: App Fabric @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/appflow/pom.xml b/services/appflow/pom.xml index 0d200c841205..c0b7c7b7ddf0 100644 --- a/services/appflow/pom.xml +++ b/services/appflow/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT appflow AWS Java SDK :: Services :: Appflow @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/appintegrations/pom.xml b/services/appintegrations/pom.xml index f2ba291bd16e..0f5e3f4404d9 100644 --- a/services/appintegrations/pom.xml +++ b/services/appintegrations/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT appintegrations AWS Java SDK :: Services :: App Integrations @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/applicationautoscaling/pom.xml b/services/applicationautoscaling/pom.xml index 02e98a0c3ee9..0fbfc8a359b8 100644 --- a/services/applicationautoscaling/pom.xml +++ b/services/applicationautoscaling/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT applicationautoscaling AWS Java SDK :: Services :: AWS Application Auto Scaling @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/applicationcostprofiler/pom.xml b/services/applicationcostprofiler/pom.xml index 13008d27d99e..1e58ae603d81 100644 --- a/services/applicationcostprofiler/pom.xml +++ b/services/applicationcostprofiler/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT applicationcostprofiler AWS Java SDK :: Services :: Application Cost Profiler @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/applicationdiscovery/pom.xml b/services/applicationdiscovery/pom.xml index 03942c474590..27ea9ce4682f 100644 --- a/services/applicationdiscovery/pom.xml +++ b/services/applicationdiscovery/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT applicationdiscovery AWS Java SDK :: Services :: AWS Application Discovery Service @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/applicationinsights/pom.xml b/services/applicationinsights/pom.xml index 7b310f62883f..9c7a5706e946 100644 --- a/services/applicationinsights/pom.xml +++ b/services/applicationinsights/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT applicationinsights AWS Java SDK :: Services :: Application Insights @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/appmesh/pom.xml b/services/appmesh/pom.xml index 6ee79ac910d8..6ef65bd45bc0 100644 --- a/services/appmesh/pom.xml +++ b/services/appmesh/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT appmesh AWS Java SDK :: Services :: App Mesh @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/apprunner/pom.xml b/services/apprunner/pom.xml index 8afb7c6afe22..ccb47a221690 100644 --- a/services/apprunner/pom.xml +++ b/services/apprunner/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT apprunner AWS Java SDK :: Services :: App Runner @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/appstream/pom.xml b/services/appstream/pom.xml index 86484790ddb3..bb350deb581d 100644 --- a/services/appstream/pom.xml +++ b/services/appstream/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT appstream AWS Java SDK :: Services :: Amazon AppStream @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/appsync/pom.xml b/services/appsync/pom.xml index aa065b5942df..e496ecb6ca0b 100644 --- a/services/appsync/pom.xml +++ b/services/appsync/pom.xml @@ -21,7 +21,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT appsync @@ -41,5 +41,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/arczonalshift/pom.xml b/services/arczonalshift/pom.xml index a242023ec372..92afe0f1e607 100644 --- a/services/arczonalshift/pom.xml +++ b/services/arczonalshift/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT arczonalshift AWS Java SDK :: Services :: ARC Zonal Shift @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/athena/pom.xml b/services/athena/pom.xml index bd64ba153b00..02edef92f81b 100644 --- a/services/athena/pom.xml +++ b/services/athena/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT athena AWS Java SDK :: Services :: Amazon Athena @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/auditmanager/pom.xml b/services/auditmanager/pom.xml index e9e0db4d7741..630f8f6d0ab9 100644 --- a/services/auditmanager/pom.xml +++ b/services/auditmanager/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT auditmanager AWS Java SDK :: Services :: Audit Manager @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/autoscaling/pom.xml b/services/autoscaling/pom.xml index a2b51c33cc0f..1669cc3fabae 100644 --- a/services/autoscaling/pom.xml +++ b/services/autoscaling/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT autoscaling AWS Java SDK :: Services :: Auto Scaling @@ -70,6 +70,11 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/autoscalingplans/pom.xml b/services/autoscalingplans/pom.xml index de396a86874b..fff8e7a92200 100644 --- a/services/autoscalingplans/pom.xml +++ b/services/autoscalingplans/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT autoscalingplans AWS Java SDK :: Services :: Auto Scaling Plans @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/backup/pom.xml b/services/backup/pom.xml index e48af35bb463..392adca9cc88 100644 --- a/services/backup/pom.xml +++ b/services/backup/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT backup AWS Java SDK :: Services :: Backup @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/backupgateway/pom.xml b/services/backupgateway/pom.xml index 41562227d805..89274d46f248 100644 --- a/services/backupgateway/pom.xml +++ b/services/backupgateway/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT backupgateway AWS Java SDK :: Services :: Backup Gateway @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/backupstorage/pom.xml b/services/backupstorage/pom.xml index a15f6218cec0..f5656adff0c0 100644 --- a/services/backupstorage/pom.xml +++ b/services/backupstorage/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT backupstorage AWS Java SDK :: Services :: Backup Storage @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/batch/pom.xml b/services/batch/pom.xml index b6f2169f4493..f71fa31b7085 100644 --- a/services/batch/pom.xml +++ b/services/batch/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT batch AWS Java SDK :: Services :: AWS Batch @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/bedrock/pom.xml b/services/bedrock/pom.xml index 896376bc96e5..698e6ce5891f 100644 --- a/services/bedrock/pom.xml +++ b/services/bedrock/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT bedrock AWS Java SDK :: Services :: Bedrock @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/bedrockruntime/pom.xml b/services/bedrockruntime/pom.xml index 333d5189108e..1d23535f360a 100644 --- a/services/bedrockruntime/pom.xml +++ b/services/bedrockruntime/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT bedrockruntime AWS Java SDK :: Services :: Bedrock Runtime @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/billingconductor/pom.xml b/services/billingconductor/pom.xml index 83c2fb4210dd..fbccee61b20b 100644 --- a/services/billingconductor/pom.xml +++ b/services/billingconductor/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT billingconductor AWS Java SDK :: Services :: Billingconductor @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/braket/pom.xml b/services/braket/pom.xml index 1228e7e8d270..bd67b65993f5 100644 --- a/services/braket/pom.xml +++ b/services/braket/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT braket AWS Java SDK :: Services :: Braket @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/budgets/pom.xml b/services/budgets/pom.xml index 56e42c3a51ea..0603e2bc1433 100644 --- a/services/budgets/pom.xml +++ b/services/budgets/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT budgets AWS Java SDK :: Services :: AWS Budgets @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/chime/pom.xml b/services/chime/pom.xml index e122e16bca2f..3a47763a11ee 100644 --- a/services/chime/pom.xml +++ b/services/chime/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT chime AWS Java SDK :: Services :: Chime @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/chimesdkidentity/pom.xml b/services/chimesdkidentity/pom.xml index f8cfaff18e34..72dea7a80bbf 100644 --- a/services/chimesdkidentity/pom.xml +++ b/services/chimesdkidentity/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT chimesdkidentity AWS Java SDK :: Services :: Chime SDK Identity @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/chimesdkmediapipelines/pom.xml b/services/chimesdkmediapipelines/pom.xml index b70ba320e55d..a1ade990a0bb 100644 --- a/services/chimesdkmediapipelines/pom.xml +++ b/services/chimesdkmediapipelines/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT chimesdkmediapipelines AWS Java SDK :: Services :: Chime SDK Media Pipelines @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/chimesdkmeetings/pom.xml b/services/chimesdkmeetings/pom.xml index 2f8745c128c6..045034211ca4 100644 --- a/services/chimesdkmeetings/pom.xml +++ b/services/chimesdkmeetings/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT chimesdkmeetings AWS Java SDK :: Services :: Chime SDK Meetings @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/chimesdkmessaging/pom.xml b/services/chimesdkmessaging/pom.xml index 69bbd058cc59..1386b6511b78 100644 --- a/services/chimesdkmessaging/pom.xml +++ b/services/chimesdkmessaging/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT chimesdkmessaging AWS Java SDK :: Services :: Chime SDK Messaging @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/chimesdkvoice/pom.xml b/services/chimesdkvoice/pom.xml index 340e94dca904..f71a4008c19d 100644 --- a/services/chimesdkvoice/pom.xml +++ b/services/chimesdkvoice/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT chimesdkvoice AWS Java SDK :: Services :: Chime SDK Voice @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cleanrooms/pom.xml b/services/cleanrooms/pom.xml index 79ed4f15692b..abdc34765a59 100644 --- a/services/cleanrooms/pom.xml +++ b/services/cleanrooms/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cleanrooms AWS Java SDK :: Services :: Clean Rooms @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cloud9/pom.xml b/services/cloud9/pom.xml index 7886425d4946..6c2f979c0047 100644 --- a/services/cloud9/pom.xml +++ b/services/cloud9/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 cloud9 @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cloudcontrol/pom.xml b/services/cloudcontrol/pom.xml index ae7ae32c6b39..3aae96ec20a1 100644 --- a/services/cloudcontrol/pom.xml +++ b/services/cloudcontrol/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cloudcontrol AWS Java SDK :: Services :: Cloud Control @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/clouddirectory/pom.xml b/services/clouddirectory/pom.xml index 424664a743eb..d34810710481 100644 --- a/services/clouddirectory/pom.xml +++ b/services/clouddirectory/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT clouddirectory AWS Java SDK :: Services :: Amazon CloudDirectory @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cloudformation/pom.xml b/services/cloudformation/pom.xml index 5a2ef4c7c123..709038872c2f 100644 --- a/services/cloudformation/pom.xml +++ b/services/cloudformation/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cloudformation AWS Java SDK :: Services :: AWS CloudFormation @@ -69,5 +69,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cloudfront/pom.xml b/services/cloudfront/pom.xml index 0be74e529c98..b5329881efcd 100644 --- a/services/cloudfront/pom.xml +++ b/services/cloudfront/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cloudfront AWS Java SDK :: Services :: Amazon CloudFront @@ -67,6 +67,11 @@ equalsverifier test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + net.bytebuddy byte-buddy diff --git a/services/cloudhsm/pom.xml b/services/cloudhsm/pom.xml index b149c6c0e9e3..a9d4de015918 100644 --- a/services/cloudhsm/pom.xml +++ b/services/cloudhsm/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cloudhsm AWS Java SDK :: Services :: AWS CloudHSM @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cloudhsmv2/pom.xml b/services/cloudhsmv2/pom.xml index 4d2fc81edf32..7541705a42da 100644 --- a/services/cloudhsmv2/pom.xml +++ b/services/cloudhsmv2/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 cloudhsmv2 @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cloudsearch/pom.xml b/services/cloudsearch/pom.xml index c498be3114d9..2d7e73d7e343 100644 --- a/services/cloudsearch/pom.xml +++ b/services/cloudsearch/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cloudsearch AWS Java SDK :: Services :: Amazon CloudSearch @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cloudsearchdomain/pom.xml b/services/cloudsearchdomain/pom.xml index 86e694f96d11..5801311ee0d8 100644 --- a/services/cloudsearchdomain/pom.xml +++ b/services/cloudsearchdomain/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cloudsearchdomain AWS Java SDK :: Services :: Amazon CloudSearch Domain @@ -59,5 +59,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cloudtrail/pom.xml b/services/cloudtrail/pom.xml index f4c9ea7fc2b4..f0a6f8159e04 100644 --- a/services/cloudtrail/pom.xml +++ b/services/cloudtrail/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cloudtrail AWS Java SDK :: Services :: AWS CloudTrail @@ -68,5 +68,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cloudtraildata/pom.xml b/services/cloudtraildata/pom.xml index 76340225451e..c32ba15882bb 100644 --- a/services/cloudtraildata/pom.xml +++ b/services/cloudtraildata/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cloudtraildata AWS Java SDK :: Services :: Cloud Trail Data @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cloudwatch/pom.xml b/services/cloudwatch/pom.xml index 18c7fcf1a30b..89ad4d7acc4b 100644 --- a/services/cloudwatch/pom.xml +++ b/services/cloudwatch/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cloudwatch AWS Java SDK :: Services :: Amazon CloudWatch @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cloudwatchevents/pom.xml b/services/cloudwatchevents/pom.xml index ca6387f3c1e5..e7ebffc135f7 100644 --- a/services/cloudwatchevents/pom.xml +++ b/services/cloudwatchevents/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cloudwatchevents AWS Java SDK :: Services :: Amazon CloudWatch Events @@ -58,5 +58,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cloudwatchlogs/pom.xml b/services/cloudwatchlogs/pom.xml index 3655a89fd626..7b19cc549e9c 100644 --- a/services/cloudwatchlogs/pom.xml +++ b/services/cloudwatchlogs/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cloudwatchlogs AWS Java SDK :: Services :: Amazon CloudWatch Logs @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/codeartifact/pom.xml b/services/codeartifact/pom.xml index d0d109f87a0f..c67dfc9a7564 100644 --- a/services/codeartifact/pom.xml +++ b/services/codeartifact/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codeartifact AWS Java SDK :: Services :: Codeartifact @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/codebuild/pom.xml b/services/codebuild/pom.xml index ad58bc26babf..fe5bcc3d81c4 100644 --- a/services/codebuild/pom.xml +++ b/services/codebuild/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codebuild AWS Java SDK :: Services :: AWS Code Build @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/codecatalyst/pom.xml b/services/codecatalyst/pom.xml index 14c7ca5b775c..5ea56a9349ff 100644 --- a/services/codecatalyst/pom.xml +++ b/services/codecatalyst/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codecatalyst AWS Java SDK :: Services :: Code Catalyst @@ -56,5 +56,16 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth + ${awsjavasdk.version} + + + software.amazon.awssdk + ssooidc + ${awsjavasdk.version} + test + diff --git a/services/codecatalyst/src/test/java/software/amazon/awssdk/services/codecatalyst/BearerCredentialTest.java b/services/codecatalyst/src/test/java/software/amazon/awssdk/services/codecatalyst/BearerCredentialTest.java new file mode 100644 index 000000000000..7a7416f04785 --- /dev/null +++ b/services/codecatalyst/src/test/java/software/amazon/awssdk/services/codecatalyst/BearerCredentialTest.java @@ -0,0 +1,61 @@ +/* + * 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.codecatalyst; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import software.amazon.awssdk.auth.token.credentials.StaticTokenProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.testutils.service.AwsIntegrationTestBase; +import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; + +public class BearerCredentialTest extends AwsIntegrationTestBase { + @Test + public void syncClientSendsBearerToken() { + try (MockSyncHttpClient httpClient = new MockSyncHttpClient(); + CodeCatalystClient codeCatalyst = + CodeCatalystClient.builder() + .region(Region.US_WEST_2) + .tokenProvider(StaticTokenProvider.create(() -> "foo-token")) + .httpClient(httpClient) + .build()) { + httpClient.stubNextResponse200(); + codeCatalyst.listSpaces(r -> {}); + + assertThat(httpClient.getLastRequest().firstMatchingHeader("Authorization")) + .hasValue("Bearer foo-token"); + } + } + + @Test + public void asyncClientSendsBearerToken() { + try (MockAsyncHttpClient httpClient = new MockAsyncHttpClient(); + CodeCatalystAsyncClient codeCatalyst = + CodeCatalystAsyncClient.builder() + .region(Region.US_WEST_2) + .tokenProvider(StaticTokenProvider.create(() -> "foo-token")) + .httpClient(httpClient) + .build()) { + httpClient.stubNextResponse200(); + codeCatalyst.listSpaces(r -> {}).join(); + + assertThat(httpClient.getLastRequest().firstMatchingHeader("Authorization")) + .hasValue("Bearer foo-token"); + } + } +} diff --git a/services/codecommit/pom.xml b/services/codecommit/pom.xml index 30a6a06254dd..293c2bd38577 100644 --- a/services/codecommit/pom.xml +++ b/services/codecommit/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codecommit AWS Java SDK :: Services :: AWS CodeCommit @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/codedeploy/pom.xml b/services/codedeploy/pom.xml index fb8dc0ce800a..f846dee4d84c 100644 --- a/services/codedeploy/pom.xml +++ b/services/codedeploy/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codedeploy AWS Java SDK :: Services :: AWS CodeDeploy @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/codeguruprofiler/pom.xml b/services/codeguruprofiler/pom.xml index 73f682095be7..f0c2d56f40a4 100644 --- a/services/codeguruprofiler/pom.xml +++ b/services/codeguruprofiler/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codeguruprofiler AWS Java SDK :: Services :: CodeGuruProfiler @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/codegurureviewer/pom.xml b/services/codegurureviewer/pom.xml index 122329d05868..d64d182bfecf 100644 --- a/services/codegurureviewer/pom.xml +++ b/services/codegurureviewer/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codegurureviewer AWS Java SDK :: Services :: CodeGuru Reviewer @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/codegurusecurity/pom.xml b/services/codegurusecurity/pom.xml index a99c6901f1e2..96b23ac7ab89 100644 --- a/services/codegurusecurity/pom.xml +++ b/services/codegurusecurity/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codegurusecurity AWS Java SDK :: Services :: Code Guru Security @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/codepipeline/pom.xml b/services/codepipeline/pom.xml index 7db12f65d7ae..447da37c41a6 100644 --- a/services/codepipeline/pom.xml +++ b/services/codepipeline/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codepipeline AWS Java SDK :: Services :: AWS CodePipeline @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/codestar/pom.xml b/services/codestar/pom.xml index 25de0f390ff6..2ee247dc77ee 100644 --- a/services/codestar/pom.xml +++ b/services/codestar/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codestar AWS Java SDK :: Services :: AWS CodeStar @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/codestarconnections/pom.xml b/services/codestarconnections/pom.xml index 8847905a13b4..2bc8995bebc6 100644 --- a/services/codestarconnections/pom.xml +++ b/services/codestarconnections/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codestarconnections AWS Java SDK :: Services :: CodeStar connections @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/codestarnotifications/pom.xml b/services/codestarnotifications/pom.xml index 9872072d8021..cf5f4f6b0570 100644 --- a/services/codestarnotifications/pom.xml +++ b/services/codestarnotifications/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT codestarnotifications AWS Java SDK :: Services :: Codestar Notifications @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cognitoidentity/pom.xml b/services/cognitoidentity/pom.xml index 2b7b9ec2c4d9..114eb7069923 100644 --- a/services/cognitoidentity/pom.xml +++ b/services/cognitoidentity/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cognitoidentity AWS Java SDK :: Services :: Amazon Cognito Identity @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cognitoidentityprovider/pom.xml b/services/cognitoidentityprovider/pom.xml index e1e19c67c5d0..33d573f4d3da 100644 --- a/services/cognitoidentityprovider/pom.xml +++ b/services/cognitoidentityprovider/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cognitoidentityprovider AWS Java SDK :: Services :: Amazon Cognito Identity Provider Service @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/cognitosync/pom.xml b/services/cognitosync/pom.xml index a0fb51961b91..6e024cdf4d21 100644 --- a/services/cognitosync/pom.xml +++ b/services/cognitosync/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT cognitosync AWS Java SDK :: Services :: Amazon Cognito Sync @@ -63,5 +63,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/comprehend/pom.xml b/services/comprehend/pom.xml index 551a1f0f6bdf..06c06dc08bc2 100644 --- a/services/comprehend/pom.xml +++ b/services/comprehend/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 comprehend @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/comprehendmedical/pom.xml b/services/comprehendmedical/pom.xml index a9b26e35af1a..14f8cdb4aa91 100644 --- a/services/comprehendmedical/pom.xml +++ b/services/comprehendmedical/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT comprehendmedical AWS Java SDK :: Services :: ComprehendMedical @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/computeoptimizer/pom.xml b/services/computeoptimizer/pom.xml index c054f067215a..9d246d906438 100644 --- a/services/computeoptimizer/pom.xml +++ b/services/computeoptimizer/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT computeoptimizer AWS Java SDK :: Services :: Compute Optimizer @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/config/pom.xml b/services/config/pom.xml index 79ec4a1d89a5..59aa83d38da2 100644 --- a/services/config/pom.xml +++ b/services/config/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT config AWS Java SDK :: Services :: AWS Config @@ -63,5 +63,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/connect/pom.xml b/services/connect/pom.xml index 2862a9f85a64..f61aceee2a3d 100644 --- a/services/connect/pom.xml +++ b/services/connect/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT connect AWS Java SDK :: Services :: Connect @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/connectcampaigns/pom.xml b/services/connectcampaigns/pom.xml index 31447d6683ef..17c63fd056b2 100644 --- a/services/connectcampaigns/pom.xml +++ b/services/connectcampaigns/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT connectcampaigns AWS Java SDK :: Services :: Connect Campaigns @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/connectcases/pom.xml b/services/connectcases/pom.xml index fb280a0c74c7..6d83bec1101f 100644 --- a/services/connectcases/pom.xml +++ b/services/connectcases/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT connectcases AWS Java SDK :: Services :: Connect Cases @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/connectcontactlens/pom.xml b/services/connectcontactlens/pom.xml index 33ecbdfe9aea..3848a9cde85b 100644 --- a/services/connectcontactlens/pom.xml +++ b/services/connectcontactlens/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT connectcontactlens AWS Java SDK :: Services :: Connect Contact Lens @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/connectparticipant/pom.xml b/services/connectparticipant/pom.xml index 8767339742c9..524dc9daedae 100644 --- a/services/connectparticipant/pom.xml +++ b/services/connectparticipant/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT connectparticipant AWS Java SDK :: Services :: ConnectParticipant @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/controltower/pom.xml b/services/controltower/pom.xml index f66aee1031b7..86a21cd0c434 100644 --- a/services/controltower/pom.xml +++ b/services/controltower/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT controltower AWS Java SDK :: Services :: Control Tower @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/costandusagereport/pom.xml b/services/costandusagereport/pom.xml index f3a8013845d5..6907d5114f62 100644 --- a/services/costandusagereport/pom.xml +++ b/services/costandusagereport/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT costandusagereport AWS Java SDK :: Services :: AWS Cost and Usage Report @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/costexplorer/pom.xml b/services/costexplorer/pom.xml index a9aa822b6950..b59a16ce695e 100644 --- a/services/costexplorer/pom.xml +++ b/services/costexplorer/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 costexplorer @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/customerprofiles/pom.xml b/services/customerprofiles/pom.xml index 79e32bc95658..d6899b0ff30c 100644 --- a/services/customerprofiles/pom.xml +++ b/services/customerprofiles/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT customerprofiles AWS Java SDK :: Services :: Customer Profiles @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/databasemigration/pom.xml b/services/databasemigration/pom.xml index 6bcf01fbc39d..f7e964860dfa 100644 --- a/services/databasemigration/pom.xml +++ b/services/databasemigration/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT databasemigration AWS Java SDK :: Services :: AWS Database Migration Service @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/databrew/pom.xml b/services/databrew/pom.xml index 4610fa10b1d6..f8ea0d23a03d 100644 --- a/services/databrew/pom.xml +++ b/services/databrew/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT databrew AWS Java SDK :: Services :: Data Brew @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/dataexchange/pom.xml b/services/dataexchange/pom.xml index 45f9232787e3..6fcd528b59b0 100644 --- a/services/dataexchange/pom.xml +++ b/services/dataexchange/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT dataexchange AWS Java SDK :: Services :: DataExchange @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/datapipeline/pom.xml b/services/datapipeline/pom.xml index fb40b405776d..90ba8f3024fb 100644 --- a/services/datapipeline/pom.xml +++ b/services/datapipeline/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT datapipeline AWS Java SDK :: Services :: AWS Data Pipeline @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/datasync/pom.xml b/services/datasync/pom.xml index e32312b2d120..2a2a94965a9f 100644 --- a/services/datasync/pom.xml +++ b/services/datasync/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT datasync AWS Java SDK :: Services :: DataSync @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/datazone/pom.xml b/services/datazone/pom.xml index b410c684b16d..12ec00119d21 100644 --- a/services/datazone/pom.xml +++ b/services/datazone/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT datazone AWS Java SDK :: Services :: Data Zone @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/dax/pom.xml b/services/dax/pom.xml index e5e6bae9536a..2e0f88fb2b6d 100644 --- a/services/dax/pom.xml +++ b/services/dax/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT dax AWS Java SDK :: Services :: Amazon DynamoDB Accelerator (DAX) @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/detective/pom.xml b/services/detective/pom.xml index 2c5c9a752321..3d8005273010 100644 --- a/services/detective/pom.xml +++ b/services/detective/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT detective AWS Java SDK :: Services :: Detective @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/devicefarm/pom.xml b/services/devicefarm/pom.xml index 1ff089bd30aa..8b4f4ef9f16e 100644 --- a/services/devicefarm/pom.xml +++ b/services/devicefarm/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT devicefarm AWS Java SDK :: Services :: AWS Device Farm @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/devopsguru/pom.xml b/services/devopsguru/pom.xml index 4447a210215a..2c882070cfc6 100644 --- a/services/devopsguru/pom.xml +++ b/services/devopsguru/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT devopsguru AWS Java SDK :: Services :: Dev Ops Guru @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/directconnect/pom.xml b/services/directconnect/pom.xml index 17663d83647c..b2f71699db0b 100644 --- a/services/directconnect/pom.xml +++ b/services/directconnect/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT directconnect AWS Java SDK :: Services :: AWS Direct Connect @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/directory/pom.xml b/services/directory/pom.xml index 75aa5c4784ba..f6a1795d8121 100644 --- a/services/directory/pom.xml +++ b/services/directory/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT directory AWS Java SDK :: Services :: AWS Directory Service @@ -63,5 +63,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/dlm/pom.xml b/services/dlm/pom.xml index d7e0591743da..3b85d84a9566 100644 --- a/services/dlm/pom.xml +++ b/services/dlm/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT dlm AWS Java SDK :: Services :: DLM @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/docdb/pom.xml b/services/docdb/pom.xml index 77f43477bcb5..7c358608ee3c 100644 --- a/services/docdb/pom.xml +++ b/services/docdb/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT docdb AWS Java SDK :: Services :: DocDB @@ -61,5 +61,10 @@ profiles ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java b/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java index 5dd3248fe31f..8db75eaf6bee 100644 --- a/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java +++ b/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java @@ -16,10 +16,13 @@ package software.amazon.awssdk.services.docdb.internal; import static software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute.AWS_CREDENTIALS; +import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME; import java.net.URI; import java.time.Clock; import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.CredentialUtils; import software.amazon.awssdk.auth.signer.Aws4Signer; import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams; @@ -37,9 +40,11 @@ import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.awssdk.protocols.query.AwsQueryProtocolFactory; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.docdb.model.DocDbRequest; +import software.amazon.awssdk.utils.CompletableFutureUtils; /** * Abstract pre-sign handler that follows the pre-signing scheme outlined in the 'RDS Presigned URL for Cross-Region Copying' @@ -144,12 +149,23 @@ private SdkHttpFullRequest presignRequest(SdkHttpFullRequest request, .signingRegion(Region.of(signingRegion)) .signingName(SERVICE_NAME) .signingClockOverride(signingOverrideClock) - .awsCredentials(attributes.getAttribute(AWS_CREDENTIALS)) + .awsCredentials(resolveCredentials(attributes)) .build(); return signer.presign(request, presignerParams); } + private AwsCredentials resolveCredentials(ExecutionAttributes attributes) { + return attributes.getOptionalAttribute(SELECTED_AUTH_SCHEME) + .map(selectedAuthScheme -> selectedAuthScheme.identity()) + .map(identityFuture -> CompletableFutureUtils.joinLikeSync(identityFuture)) + .filter(identity -> identity instanceof AwsCredentialsIdentity) + .map(identity -> { + AwsCredentialsIdentity awsCredentialsIdentity = (AwsCredentialsIdentity) identity; + return CredentialUtils.toCredentials(awsCredentialsIdentity); + }).orElse(attributes.getAttribute(AWS_CREDENTIALS)); + } + private URI createEndpoint(String regionName, String serviceName, ExecutionAttributes attributes) { Region region = Region.of(regionName); diff --git a/services/docdbelastic/pom.xml b/services/docdbelastic/pom.xml index 1bef1a2f984d..eb1bfb1662ab 100644 --- a/services/docdbelastic/pom.xml +++ b/services/docdbelastic/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT docdbelastic AWS Java SDK :: Services :: Doc DB Elastic @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/drs/pom.xml b/services/drs/pom.xml index f50b2c5948ac..d14910b6fc52 100644 --- a/services/drs/pom.xml +++ b/services/drs/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT drs AWS Java SDK :: Services :: Drs @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/dynamodb/pom.xml b/services/dynamodb/pom.xml index 0e4ca060a724..0646ae675ac0 100644 --- a/services/dynamodb/pom.xml +++ b/services/dynamodb/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT dynamodb AWS Java SDK :: Services :: Amazon DynamoDB @@ -68,5 +68,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ebs/pom.xml b/services/ebs/pom.xml index 117c10072c49..85cf8aa55014 100644 --- a/services/ebs/pom.xml +++ b/services/ebs/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ebs AWS Java SDK :: Services :: EBS @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ec2/pom.xml b/services/ec2/pom.xml index 1fc99844d610..e30a50146e2c 100644 --- a/services/ec2/pom.xml +++ b/services/ec2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ec2 AWS Java SDK :: Services :: Amazon EC2 @@ -80,5 +80,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ec2/src/main/java/software/amazon/awssdk/services/ec2/transform/internal/GeneratePreSignUrlInterceptor.java b/services/ec2/src/main/java/software/amazon/awssdk/services/ec2/transform/internal/GeneratePreSignUrlInterceptor.java index 02389a622594..baf011facda5 100644 --- a/services/ec2/src/main/java/software/amazon/awssdk/services/ec2/transform/internal/GeneratePreSignUrlInterceptor.java +++ b/services/ec2/src/main/java/software/amazon/awssdk/services/ec2/transform/internal/GeneratePreSignUrlInterceptor.java @@ -16,11 +16,14 @@ package software.amazon.awssdk.services.ec2.transform.internal; import static software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute.AWS_CREDENTIALS; +import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME; import java.net.URI; import java.time.Clock; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.annotations.SdkTestInternalApi; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.CredentialUtils; import software.amazon.awssdk.auth.signer.Aws4Signer; import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams; import software.amazon.awssdk.awscore.util.AwsHostNameUtils; @@ -34,11 +37,13 @@ import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.awssdk.protocols.query.AwsEc2ProtocolFactory; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.ec2.Ec2Client; import software.amazon.awssdk.services.ec2.model.CopySnapshotRequest; import software.amazon.awssdk.services.ec2.transform.CopySnapshotRequestMarshaller; +import software.amazon.awssdk.utils.CompletableFutureUtils; /** * ExecutionInterceptor that generates a pre-signed URL for copying encrypted snapshots @@ -133,11 +138,23 @@ private Aws4PresignerParams getPresignerParams(ExecutionAttributes attributes, S return Aws4PresignerParams.builder() .signingRegion(Region.of(signingRegion)) .signingName(signingName) - .awsCredentials(attributes.getAttribute(AWS_CREDENTIALS)) + .awsCredentials(resolveCredentials(attributes)) .signingClockOverride(testClock) .build(); } + // TODO(sra-identity-and-auth): add test case for SELECTED_AUTH_SCHEME case + private AwsCredentials resolveCredentials(ExecutionAttributes attributes) { + return attributes.getOptionalAttribute(SELECTED_AUTH_SCHEME) + .map(selectedAuthScheme -> selectedAuthScheme.identity()) + .map(identityFuture -> CompletableFutureUtils.joinLikeSync(identityFuture)) + .filter(identity -> identity instanceof AwsCredentialsIdentity) + .map(identity -> { + AwsCredentialsIdentity awsCredentialsIdentity = (AwsCredentialsIdentity) identity; + return CredentialUtils.toCredentials(awsCredentialsIdentity); + }).orElse(attributes.getAttribute(AWS_CREDENTIALS)); + } + /** * Generates a Request object for the pre-signed URL. */ diff --git a/services/ec2instanceconnect/pom.xml b/services/ec2instanceconnect/pom.xml index 539d1f253515..eee5eca2c0b6 100644 --- a/services/ec2instanceconnect/pom.xml +++ b/services/ec2instanceconnect/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ec2instanceconnect AWS Java SDK :: Services :: EC2 Instance Connect @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ecr/pom.xml b/services/ecr/pom.xml index 1f491810989e..4dd07f5dd598 100644 --- a/services/ecr/pom.xml +++ b/services/ecr/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ecr AWS Java SDK :: Services :: Amazon EC2 Container Registry @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ecrpublic/pom.xml b/services/ecrpublic/pom.xml index 029db0bdb3ec..589bf9e83db0 100644 --- a/services/ecrpublic/pom.xml +++ b/services/ecrpublic/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ecrpublic AWS Java SDK :: Services :: ECR PUBLIC @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ecs/pom.xml b/services/ecs/pom.xml index 54f551ee6174..a2748351e3c7 100644 --- a/services/ecs/pom.xml +++ b/services/ecs/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ecs AWS Java SDK :: Services :: Amazon EC2 Container Service @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/efs/pom.xml b/services/efs/pom.xml index 7a065df5ce2d..130f7ee33aef 100644 --- a/services/efs/pom.xml +++ b/services/efs/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT efs AWS Java SDK :: Services :: Amazon Elastic File System @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/eks/pom.xml b/services/eks/pom.xml index 00e70e0fabc3..83abfd8f224a 100644 --- a/services/eks/pom.xml +++ b/services/eks/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT eks AWS Java SDK :: Services :: EKS @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/elasticache/pom.xml b/services/elasticache/pom.xml index f45f06eddaed..174974126c19 100644 --- a/services/elasticache/pom.xml +++ b/services/elasticache/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT elasticache AWS Java SDK :: Services :: Amazon ElastiCache @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/elasticbeanstalk/pom.xml b/services/elasticbeanstalk/pom.xml index 1d0f85cee0e5..cdfe1930947f 100644 --- a/services/elasticbeanstalk/pom.xml +++ b/services/elasticbeanstalk/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT elasticbeanstalk AWS Java SDK :: Services :: AWS Elastic Beanstalk @@ -64,5 +64,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/elasticinference/pom.xml b/services/elasticinference/pom.xml index 8e0351bd5b13..358ae9d7b21f 100644 --- a/services/elasticinference/pom.xml +++ b/services/elasticinference/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT elasticinference AWS Java SDK :: Services :: Elastic Inference @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/elasticloadbalancing/pom.xml b/services/elasticloadbalancing/pom.xml index ae7a4f62707e..b292d13ac5fa 100644 --- a/services/elasticloadbalancing/pom.xml +++ b/services/elasticloadbalancing/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT elasticloadbalancing AWS Java SDK :: Services :: Elastic Load Balancing @@ -70,5 +70,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/elasticloadbalancingv2/pom.xml b/services/elasticloadbalancingv2/pom.xml index d1369da72b73..d0ef9da9c4e4 100644 --- a/services/elasticloadbalancingv2/pom.xml +++ b/services/elasticloadbalancingv2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT elasticloadbalancingv2 AWS Java SDK :: Services :: Elastic Load Balancing V2 @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/elasticsearch/pom.xml b/services/elasticsearch/pom.xml index 3e3d8f570046..52a2b1db7cf7 100644 --- a/services/elasticsearch/pom.xml +++ b/services/elasticsearch/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT elasticsearch AWS Java SDK :: Services :: Amazon Elasticsearch Service @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/elastictranscoder/pom.xml b/services/elastictranscoder/pom.xml index ae9c4a85d26d..18e20c414cdb 100644 --- a/services/elastictranscoder/pom.xml +++ b/services/elastictranscoder/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT elastictranscoder AWS Java SDK :: Services :: Amazon Elastic Transcoder @@ -71,5 +71,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/emr/pom.xml b/services/emr/pom.xml index 77d511786fe0..500f3ab3aef3 100644 --- a/services/emr/pom.xml +++ b/services/emr/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT emr AWS Java SDK :: Services :: Amazon EMR @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/emrcontainers/pom.xml b/services/emrcontainers/pom.xml index 435ccab123a5..af3c89d58100 100644 --- a/services/emrcontainers/pom.xml +++ b/services/emrcontainers/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT emrcontainers AWS Java SDK :: Services :: EMR Containers @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/emrserverless/pom.xml b/services/emrserverless/pom.xml index b0b28fe2b6fc..7de10d4684ad 100644 --- a/services/emrserverless/pom.xml +++ b/services/emrserverless/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT emrserverless AWS Java SDK :: Services :: EMR Serverless @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/entityresolution/pom.xml b/services/entityresolution/pom.xml index 787e2192ffba..322f6b60a794 100644 --- a/services/entityresolution/pom.xml +++ b/services/entityresolution/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT entityresolution AWS Java SDK :: Services :: Entity Resolution @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/eventbridge/pom.xml b/services/eventbridge/pom.xml index d2342a78266e..77fb8878081a 100644 --- a/services/eventbridge/pom.xml +++ b/services/eventbridge/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT eventbridge AWS Java SDK :: Services :: EventBridge @@ -56,7 +56,19 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + + software.amazon.awssdk auth-crt @@ -69,5 +81,11 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws-crt + ${awsjavasdk.version} + test + diff --git a/services/eventbridge/src/it/java/software/amazon/awssdk/services/eventbridge/EventBridgeMultiRegionEndpointIntegrationTest.java b/services/eventbridge/src/it/java/software/amazon/awssdk/services/eventbridge/EventBridgeMultiRegionEndpointIntegrationTest.java index 8a5a8377a772..568835ccc9cb 100644 --- a/services/eventbridge/src/it/java/software/amazon/awssdk/services/eventbridge/EventBridgeMultiRegionEndpointIntegrationTest.java +++ b/services/eventbridge/src/it/java/software/amazon/awssdk/services/eventbridge/EventBridgeMultiRegionEndpointIntegrationTest.java @@ -16,7 +16,10 @@ package software.amazon.awssdk.services.eventbridge; import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; +import java.io.UncheckedIOException; +import java.net.InetAddress; import java.net.URI; import java.time.Duration; import org.junit.jupiter.api.BeforeEach; @@ -176,6 +179,14 @@ private String getOrAwaitEndpointId(String endpointName) { .until(ep -> ep.state() != EndpointState.CREATING) .orFailAfter(Duration.ofMinutes(2)); assertThat(response.state()).isEqualTo(EndpointState.ACTIVE); + + log.info(() -> "Endpoint ID is active. Waiting until we can resolve the endpoint. This will take a some time if the " + + "endpoint was just created."); + URI endpointUri = URI.create(response.endpointUrl()); + Waiter.run(() -> invokeSafely(() -> InetAddress.getByName(endpointUri.getHost()))) + .ignoringException(UncheckedIOException.class) + .orFailAfter(Duration.ofMinutes(10)); + return response.endpointId(); } diff --git a/services/eventbridge/src/main/resources/codegen-resources/customization.config b/services/eventbridge/src/main/resources/codegen-resources/customization.config new file mode 100644 index 000000000000..dbe25a828440 --- /dev/null +++ b/services/eventbridge/src/main/resources/codegen-resources/customization.config @@ -0,0 +1,6 @@ +{ + "enableEndpointAuthSchemeParams": true, + "allowedEndpointAuthSchemeParams": [ + "EndpointId" + ] +} diff --git a/services/evidently/pom.xml b/services/evidently/pom.xml index e0fb324b0acc..a83ed394cb63 100644 --- a/services/evidently/pom.xml +++ b/services/evidently/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT evidently AWS Java SDK :: Services :: Evidently @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/finspace/pom.xml b/services/finspace/pom.xml index 74c858f0f884..2288f126de8d 100644 --- a/services/finspace/pom.xml +++ b/services/finspace/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT finspace AWS Java SDK :: Services :: Finspace @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/finspacedata/pom.xml b/services/finspacedata/pom.xml index 537ae3f29f20..8b090ef82f76 100644 --- a/services/finspacedata/pom.xml +++ b/services/finspacedata/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT finspacedata AWS Java SDK :: Services :: Finspace Data @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/firehose/pom.xml b/services/firehose/pom.xml index c908f00646c2..1fe1ef40f958 100644 --- a/services/firehose/pom.xml +++ b/services/firehose/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT firehose AWS Java SDK :: Services :: Amazon Kinesis Firehose @@ -58,5 +58,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/fis/pom.xml b/services/fis/pom.xml index 2c05425c0100..58f6bedd3377 100644 --- a/services/fis/pom.xml +++ b/services/fis/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT fis AWS Java SDK :: Services :: Fis @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/fms/pom.xml b/services/fms/pom.xml index 71ddc3373c56..638ab50c6b5b 100644 --- a/services/fms/pom.xml +++ b/services/fms/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT fms AWS Java SDK :: Services :: FMS @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/forecast/pom.xml b/services/forecast/pom.xml index 6dd236ed2bd8..9c0754a54ede 100644 --- a/services/forecast/pom.xml +++ b/services/forecast/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT forecast AWS Java SDK :: Services :: Forecast @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/forecastquery/pom.xml b/services/forecastquery/pom.xml index a9461511eba3..bb6d9a58c4c8 100644 --- a/services/forecastquery/pom.xml +++ b/services/forecastquery/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT forecastquery AWS Java SDK :: Services :: Forecastquery @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/frauddetector/pom.xml b/services/frauddetector/pom.xml index e72cde664c72..15033cfbc10a 100644 --- a/services/frauddetector/pom.xml +++ b/services/frauddetector/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT frauddetector AWS Java SDK :: Services :: FraudDetector @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/fsx/pom.xml b/services/fsx/pom.xml index 58a5022e11f5..524fc6b3acc8 100644 --- a/services/fsx/pom.xml +++ b/services/fsx/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT fsx AWS Java SDK :: Services :: FSx @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/gamelift/pom.xml b/services/gamelift/pom.xml index 7afe56b05f5c..8dd75a1fa02a 100644 --- a/services/gamelift/pom.xml +++ b/services/gamelift/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT gamelift AWS Java SDK :: Services :: AWS GameLift @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/gamesparks/pom.xml b/services/gamesparks/pom.xml index b96fdf3755ce..d8acaf575152 100644 --- a/services/gamesparks/pom.xml +++ b/services/gamesparks/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT gamesparks AWS Java SDK :: Services :: Game Sparks @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/glacier/pom.xml b/services/glacier/pom.xml index 4b246e56144a..5efbac6d1375 100644 --- a/services/glacier/pom.xml +++ b/services/glacier/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT glacier AWS Java SDK :: Services :: Amazon Glacier @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/globalaccelerator/pom.xml b/services/globalaccelerator/pom.xml index 3112a2b7867e..6691c7d08bf4 100644 --- a/services/globalaccelerator/pom.xml +++ b/services/globalaccelerator/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT globalaccelerator AWS Java SDK :: Services :: Global Accelerator @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/glue/pom.xml b/services/glue/pom.xml index 65273ec988a4..07ac0c3a9fdc 100644 --- a/services/glue/pom.xml +++ b/services/glue/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 glue @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/grafana/pom.xml b/services/grafana/pom.xml index 00a6783053e3..a5cc7d00bc7b 100644 --- a/services/grafana/pom.xml +++ b/services/grafana/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT grafana AWS Java SDK :: Services :: Grafana @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/greengrass/pom.xml b/services/greengrass/pom.xml index 0c87a2d1400e..5670f122b28e 100644 --- a/services/greengrass/pom.xml +++ b/services/greengrass/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT greengrass AWS Java SDK :: Services :: AWS Greengrass @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/greengrassv2/pom.xml b/services/greengrassv2/pom.xml index d2387c6b698b..b0cb4360989f 100644 --- a/services/greengrassv2/pom.xml +++ b/services/greengrassv2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT greengrassv2 AWS Java SDK :: Services :: Greengrass V2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/groundstation/pom.xml b/services/groundstation/pom.xml index c88635fa33cd..e2dd3e41616e 100644 --- a/services/groundstation/pom.xml +++ b/services/groundstation/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT groundstation AWS Java SDK :: Services :: GroundStation @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/guardduty/pom.xml b/services/guardduty/pom.xml index 6495698e513d..d510eb88372a 100644 --- a/services/guardduty/pom.xml +++ b/services/guardduty/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 guardduty @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/health/pom.xml b/services/health/pom.xml index 6ea0df6faac7..43da8d620a48 100644 --- a/services/health/pom.xml +++ b/services/health/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT health AWS Java SDK :: Services :: AWS Health APIs and Notifications @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/healthlake/pom.xml b/services/healthlake/pom.xml index 1a80d49f8ce9..98ab316afc27 100644 --- a/services/healthlake/pom.xml +++ b/services/healthlake/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT healthlake AWS Java SDK :: Services :: Health Lake @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/honeycode/pom.xml b/services/honeycode/pom.xml index 0bbe8496c467..eca12416c09a 100644 --- a/services/honeycode/pom.xml +++ b/services/honeycode/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT honeycode AWS Java SDK :: Services :: Honeycode @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iam/pom.xml b/services/iam/pom.xml index f31b1ee3ad22..618c44f5854d 100644 --- a/services/iam/pom.xml +++ b/services/iam/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iam AWS Java SDK :: Services :: AWS IAM @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/identitystore/pom.xml b/services/identitystore/pom.xml index 29c0dc50b9cb..b313fb4eab75 100644 --- a/services/identitystore/pom.xml +++ b/services/identitystore/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT identitystore AWS Java SDK :: Services :: Identitystore @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/imagebuilder/pom.xml b/services/imagebuilder/pom.xml index b23959d189aa..7fd7de9981b9 100644 --- a/services/imagebuilder/pom.xml +++ b/services/imagebuilder/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT imagebuilder AWS Java SDK :: Services :: Imagebuilder @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/inspector/pom.xml b/services/inspector/pom.xml index f0538e9218f7..0c25c197d677 100644 --- a/services/inspector/pom.xml +++ b/services/inspector/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT inspector AWS Java SDK :: Services :: Amazon Inspector Service @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/inspector2/pom.xml b/services/inspector2/pom.xml index 974f11cb0ed3..6f83c8a2faf5 100644 --- a/services/inspector2/pom.xml +++ b/services/inspector2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT inspector2 AWS Java SDK :: Services :: Inspector2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/internetmonitor/pom.xml b/services/internetmonitor/pom.xml index f8e0c3c82dab..ab0d18b4c479 100644 --- a/services/internetmonitor/pom.xml +++ b/services/internetmonitor/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT internetmonitor AWS Java SDK :: Services :: Internet Monitor @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iot/pom.xml b/services/iot/pom.xml index 64c0b0a64b5f..5c10c5d9021a 100644 --- a/services/iot/pom.xml +++ b/services/iot/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iot AWS Java SDK :: Services :: AWS IoT @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iot1clickdevices/pom.xml b/services/iot1clickdevices/pom.xml index 8fbace5e8a51..d7c9b4c34264 100644 --- a/services/iot1clickdevices/pom.xml +++ b/services/iot1clickdevices/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iot1clickdevices AWS Java SDK :: Services :: IoT 1Click Devices Service @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iot1clickprojects/pom.xml b/services/iot1clickprojects/pom.xml index 92ba2ee0955d..0a73858b516b 100644 --- a/services/iot1clickprojects/pom.xml +++ b/services/iot1clickprojects/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iot1clickprojects AWS Java SDK :: Services :: IoT 1Click Projects @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iotanalytics/pom.xml b/services/iotanalytics/pom.xml index 495dc5c8edbe..9d7d0d2bf8a2 100644 --- a/services/iotanalytics/pom.xml +++ b/services/iotanalytics/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iotanalytics AWS Java SDK :: Services :: IoTAnalytics @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iotdataplane/pom.xml b/services/iotdataplane/pom.xml index 632bf57ac6e8..4ecb970074d5 100644 --- a/services/iotdataplane/pom.xml +++ b/services/iotdataplane/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iotdataplane AWS Java SDK :: Services :: AWS IoT Data Plane @@ -64,6 +64,11 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iotdeviceadvisor/pom.xml b/services/iotdeviceadvisor/pom.xml index cfd13b4dc920..b6cdacab160d 100644 --- a/services/iotdeviceadvisor/pom.xml +++ b/services/iotdeviceadvisor/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iotdeviceadvisor AWS Java SDK :: Services :: Iot Device Advisor @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iotevents/pom.xml b/services/iotevents/pom.xml index 8fe68334b97f..c40a9df707fa 100644 --- a/services/iotevents/pom.xml +++ b/services/iotevents/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iotevents AWS Java SDK :: Services :: IoT Events @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ioteventsdata/pom.xml b/services/ioteventsdata/pom.xml index ab3a6806c19c..50887800f28e 100644 --- a/services/ioteventsdata/pom.xml +++ b/services/ioteventsdata/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ioteventsdata AWS Java SDK :: Services :: IoT Events Data @@ -61,5 +61,10 @@ junit-jupiter test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iotfleethub/pom.xml b/services/iotfleethub/pom.xml index f168060e35da..a0a7bd3e4310 100644 --- a/services/iotfleethub/pom.xml +++ b/services/iotfleethub/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iotfleethub AWS Java SDK :: Services :: Io T Fleet Hub @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iotfleetwise/pom.xml b/services/iotfleetwise/pom.xml index e400a59f1094..12a18d78eb36 100644 --- a/services/iotfleetwise/pom.xml +++ b/services/iotfleetwise/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iotfleetwise AWS Java SDK :: Services :: Io T Fleet Wise @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iotjobsdataplane/pom.xml b/services/iotjobsdataplane/pom.xml index 52084a643f23..62812d424516 100644 --- a/services/iotjobsdataplane/pom.xml +++ b/services/iotjobsdataplane/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iotjobsdataplane AWS Java SDK :: Services :: IoT Jobs Data Plane @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iotroborunner/pom.xml b/services/iotroborunner/pom.xml index 79f897d959a7..cb01b1fe0221 100644 --- a/services/iotroborunner/pom.xml +++ b/services/iotroborunner/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iotroborunner AWS Java SDK :: Services :: IoT Robo Runner @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iotsecuretunneling/pom.xml b/services/iotsecuretunneling/pom.xml index 2d9f51e153d2..620c480546b0 100644 --- a/services/iotsecuretunneling/pom.xml +++ b/services/iotsecuretunneling/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iotsecuretunneling AWS Java SDK :: Services :: IoTSecureTunneling @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iotsitewise/pom.xml b/services/iotsitewise/pom.xml index b4fe57f5d1c9..2a364444ed20 100644 --- a/services/iotsitewise/pom.xml +++ b/services/iotsitewise/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iotsitewise AWS Java SDK :: Services :: Io T Site Wise @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iotthingsgraph/pom.xml b/services/iotthingsgraph/pom.xml index a784cf2edbd0..30cd56125b3e 100644 --- a/services/iotthingsgraph/pom.xml +++ b/services/iotthingsgraph/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iotthingsgraph AWS Java SDK :: Services :: IoTThingsGraph @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iottwinmaker/pom.xml b/services/iottwinmaker/pom.xml index 9668167f301b..a668c3eb4cc2 100644 --- a/services/iottwinmaker/pom.xml +++ b/services/iottwinmaker/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iottwinmaker AWS Java SDK :: Services :: Io T Twin Maker @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/iotwireless/pom.xml b/services/iotwireless/pom.xml index e123df3bcc94..b5f8131c6e3f 100644 --- a/services/iotwireless/pom.xml +++ b/services/iotwireless/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT iotwireless AWS Java SDK :: Services :: IoT Wireless @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ivs/pom.xml b/services/ivs/pom.xml index f7385e8c91b3..3ee4fcca144b 100644 --- a/services/ivs/pom.xml +++ b/services/ivs/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ivs AWS Java SDK :: Services :: Ivs @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ivschat/pom.xml b/services/ivschat/pom.xml index 069d0e7492b1..9d57f8556767 100644 --- a/services/ivschat/pom.xml +++ b/services/ivschat/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ivschat AWS Java SDK :: Services :: Ivschat @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ivsrealtime/pom.xml b/services/ivsrealtime/pom.xml index 6c1c7d430da6..504dcf4926b6 100644 --- a/services/ivsrealtime/pom.xml +++ b/services/ivsrealtime/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ivsrealtime AWS Java SDK :: Services :: IVS Real Time @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kafka/pom.xml b/services/kafka/pom.xml index 3fb97d4c72d5..0d04593d1479 100644 --- a/services/kafka/pom.xml +++ b/services/kafka/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT kafka AWS Java SDK :: Services :: Kafka @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kafkaconnect/pom.xml b/services/kafkaconnect/pom.xml index a3edeeef45ad..83484a4586aa 100644 --- a/services/kafkaconnect/pom.xml +++ b/services/kafkaconnect/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT kafkaconnect AWS Java SDK :: Services :: Kafka Connect @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kendra/pom.xml b/services/kendra/pom.xml index c0acb4f1c1fe..9bf7ab41d727 100644 --- a/services/kendra/pom.xml +++ b/services/kendra/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT kendra AWS Java SDK :: Services :: Kendra @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kendraranking/pom.xml b/services/kendraranking/pom.xml index de74fa2b98da..fa217af37435 100644 --- a/services/kendraranking/pom.xml +++ b/services/kendraranking/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT kendraranking AWS Java SDK :: Services :: Kendra Ranking @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/keyspaces/pom.xml b/services/keyspaces/pom.xml index b49b905179d9..15b54ec3c55b 100644 --- a/services/keyspaces/pom.xml +++ b/services/keyspaces/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT keyspaces AWS Java SDK :: Services :: Keyspaces @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kinesis/pom.xml b/services/kinesis/pom.xml index ca173e7f36f6..fee64182fb73 100644 --- a/services/kinesis/pom.xml +++ b/services/kinesis/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT kinesis AWS Java SDK :: Services :: Amazon Kinesis @@ -72,5 +72,10 @@ commons-lang3 test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kinesisanalytics/pom.xml b/services/kinesisanalytics/pom.xml index 9895a0e0b5a6..5c828e95ea9d 100644 --- a/services/kinesisanalytics/pom.xml +++ b/services/kinesisanalytics/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT kinesisanalytics AWS Java SDK :: Services :: Amazon Kinesis Analytics @@ -59,5 +59,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kinesisanalyticsv2/pom.xml b/services/kinesisanalyticsv2/pom.xml index fef2e07a409c..9934e07aaa75 100644 --- a/services/kinesisanalyticsv2/pom.xml +++ b/services/kinesisanalyticsv2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT kinesisanalyticsv2 AWS Java SDK :: Services :: Kinesis Analytics V2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kinesisvideo/pom.xml b/services/kinesisvideo/pom.xml index 6515cb5e7b93..e2da71a3d002 100644 --- a/services/kinesisvideo/pom.xml +++ b/services/kinesisvideo/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 kinesisvideo @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kinesisvideoarchivedmedia/pom.xml b/services/kinesisvideoarchivedmedia/pom.xml index e6a75ee38e58..3d7d95590390 100644 --- a/services/kinesisvideoarchivedmedia/pom.xml +++ b/services/kinesisvideoarchivedmedia/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT kinesisvideoarchivedmedia AWS Java SDK :: Services :: Kinesis Video Archived Media @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kinesisvideomedia/pom.xml b/services/kinesisvideomedia/pom.xml index a59b7f256859..eb5819e85872 100644 --- a/services/kinesisvideomedia/pom.xml +++ b/services/kinesisvideomedia/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT kinesisvideomedia AWS Java SDK :: Services :: Kinesis Video Media @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kinesisvideosignaling/pom.xml b/services/kinesisvideosignaling/pom.xml index d76e72a58bfc..29c3b7980512 100644 --- a/services/kinesisvideosignaling/pom.xml +++ b/services/kinesisvideosignaling/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT kinesisvideosignaling AWS Java SDK :: Services :: Kinesis Video Signaling @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kinesisvideowebrtcstorage/pom.xml b/services/kinesisvideowebrtcstorage/pom.xml index 8c0c570e3e79..10b42c9b5375 100644 --- a/services/kinesisvideowebrtcstorage/pom.xml +++ b/services/kinesisvideowebrtcstorage/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT kinesisvideowebrtcstorage AWS Java SDK :: Services :: Kinesis Video Web RTC Storage @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/kms/pom.xml b/services/kms/pom.xml index 8bc17b6a41d7..61c32bae3f65 100644 --- a/services/kms/pom.xml +++ b/services/kms/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT kms AWS Java SDK :: Services :: AWS KMS @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/lakeformation/pom.xml b/services/lakeformation/pom.xml index 3823904239fa..947503bae727 100644 --- a/services/lakeformation/pom.xml +++ b/services/lakeformation/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT lakeformation AWS Java SDK :: Services :: LakeFormation @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/lambda/pom.xml b/services/lambda/pom.xml index 3ae7d8b0411d..0e23b993759c 100644 --- a/services/lambda/pom.xml +++ b/services/lambda/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT lambda AWS Java SDK :: Services :: AWS Lambda @@ -68,5 +68,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/lexmodelbuilding/pom.xml b/services/lexmodelbuilding/pom.xml index a81e3dfc1527..dfbe36a8d026 100644 --- a/services/lexmodelbuilding/pom.xml +++ b/services/lexmodelbuilding/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT lexmodelbuilding AWS Java SDK :: Services :: Amazon Lex Model Building @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/lexmodelsv2/pom.xml b/services/lexmodelsv2/pom.xml index d009f9de8df1..08e29b9e5c80 100644 --- a/services/lexmodelsv2/pom.xml +++ b/services/lexmodelsv2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT lexmodelsv2 AWS Java SDK :: Services :: Lex Models V2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/lexruntime/pom.xml b/services/lexruntime/pom.xml index a3f25816ba89..fe77e84eb9b2 100644 --- a/services/lexruntime/pom.xml +++ b/services/lexruntime/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT lexruntime AWS Java SDK :: Services :: Amazon Lex Runtime @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/lexruntimev2/pom.xml b/services/lexruntimev2/pom.xml index a9c867745af2..2e584fb2bb32 100644 --- a/services/lexruntimev2/pom.xml +++ b/services/lexruntimev2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT lexruntimev2 AWS Java SDK :: Services :: Lex Runtime V2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/licensemanager/pom.xml b/services/licensemanager/pom.xml index c846ee19303d..39f3f62b7b8f 100644 --- a/services/licensemanager/pom.xml +++ b/services/licensemanager/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT licensemanager AWS Java SDK :: Services :: License Manager @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/licensemanagerlinuxsubscriptions/pom.xml b/services/licensemanagerlinuxsubscriptions/pom.xml index f35ddeaaac64..bae9d7886c62 100644 --- a/services/licensemanagerlinuxsubscriptions/pom.xml +++ b/services/licensemanagerlinuxsubscriptions/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT licensemanagerlinuxsubscriptions AWS Java SDK :: Services :: License Manager Linux Subscriptions @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/licensemanagerusersubscriptions/pom.xml b/services/licensemanagerusersubscriptions/pom.xml index 9cc462d68d0a..4b92aa5b82ee 100644 --- a/services/licensemanagerusersubscriptions/pom.xml +++ b/services/licensemanagerusersubscriptions/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT licensemanagerusersubscriptions AWS Java SDK :: Services :: License Manager User Subscriptions @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/lightsail/pom.xml b/services/lightsail/pom.xml index 67d6714c392c..aaaa0039382b 100644 --- a/services/lightsail/pom.xml +++ b/services/lightsail/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT lightsail AWS Java SDK :: Services :: Amazon Lightsail @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/location/pom.xml b/services/location/pom.xml index 496ea18b4a8a..c7fe04357874 100644 --- a/services/location/pom.xml +++ b/services/location/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT location AWS Java SDK :: Services :: Location @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/lookoutequipment/pom.xml b/services/lookoutequipment/pom.xml index a5f2ca9e9324..d5532aeef3b5 100644 --- a/services/lookoutequipment/pom.xml +++ b/services/lookoutequipment/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT lookoutequipment AWS Java SDK :: Services :: Lookout Equipment @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/lookoutmetrics/pom.xml b/services/lookoutmetrics/pom.xml index 47502e6da3d8..d46dcfe6f859 100644 --- a/services/lookoutmetrics/pom.xml +++ b/services/lookoutmetrics/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT lookoutmetrics AWS Java SDK :: Services :: Lookout Metrics @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/lookoutvision/pom.xml b/services/lookoutvision/pom.xml index df1566a6d8f0..770c7740024f 100644 --- a/services/lookoutvision/pom.xml +++ b/services/lookoutvision/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT lookoutvision AWS Java SDK :: Services :: Lookout Vision @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/m2/pom.xml b/services/m2/pom.xml index 059e48ca1239..9b4487b0bfa1 100644 --- a/services/m2/pom.xml +++ b/services/m2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT m2 AWS Java SDK :: Services :: M2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/machinelearning/pom.xml b/services/machinelearning/pom.xml index 90a206d0dd72..d8f27d17aa09 100644 --- a/services/machinelearning/pom.xml +++ b/services/machinelearning/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT machinelearning AWS Java SDK :: Services :: Amazon Machine Learning @@ -64,5 +64,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/macie/pom.xml b/services/macie/pom.xml index 2716a866a52d..7c86fe2ee9cf 100644 --- a/services/macie/pom.xml +++ b/services/macie/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT macie AWS Java SDK :: Services :: Macie @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/macie2/pom.xml b/services/macie2/pom.xml index 573b53441ae1..d30c62f12728 100644 --- a/services/macie2/pom.xml +++ b/services/macie2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT macie2 AWS Java SDK :: Services :: Macie2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/managedblockchain/pom.xml b/services/managedblockchain/pom.xml index 22361bd30c73..ecc9a2024a17 100644 --- a/services/managedblockchain/pom.xml +++ b/services/managedblockchain/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT managedblockchain AWS Java SDK :: Services :: ManagedBlockchain @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/managedblockchainquery/pom.xml b/services/managedblockchainquery/pom.xml index 7b5b45f64790..be4a97bf0046 100644 --- a/services/managedblockchainquery/pom.xml +++ b/services/managedblockchainquery/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT managedblockchainquery AWS Java SDK :: Services :: Managed Blockchain Query @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/marketplacecatalog/pom.xml b/services/marketplacecatalog/pom.xml index c4d805b7ed78..c864fb5d9064 100644 --- a/services/marketplacecatalog/pom.xml +++ b/services/marketplacecatalog/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT marketplacecatalog AWS Java SDK :: Services :: Marketplace Catalog @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/marketplacecommerceanalytics/pom.xml b/services/marketplacecommerceanalytics/pom.xml index def3c1c4e550..08425b615d08 100644 --- a/services/marketplacecommerceanalytics/pom.xml +++ b/services/marketplacecommerceanalytics/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT marketplacecommerceanalytics AWS Java SDK :: Services :: AWS Marketplace Commerce Analytics @@ -76,5 +76,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/marketplaceentitlement/pom.xml b/services/marketplaceentitlement/pom.xml index ad59fbd3bbe2..3169e6185eb3 100644 --- a/services/marketplaceentitlement/pom.xml +++ b/services/marketplaceentitlement/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT marketplaceentitlement AWS Java SDK :: Services :: AWS Marketplace Entitlement @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/marketplacemetering/pom.xml b/services/marketplacemetering/pom.xml index 9cbc523b2572..3ea296dc3fa7 100644 --- a/services/marketplacemetering/pom.xml +++ b/services/marketplacemetering/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT marketplacemetering AWS Java SDK :: Services :: AWS Marketplace Metering Service @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mediaconnect/pom.xml b/services/mediaconnect/pom.xml index 868d716c0930..2076ffea743b 100644 --- a/services/mediaconnect/pom.xml +++ b/services/mediaconnect/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT mediaconnect AWS Java SDK :: Services :: MediaConnect @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mediaconvert/pom.xml b/services/mediaconvert/pom.xml index 21c99eb3209d..e0b4650ef165 100644 --- a/services/mediaconvert/pom.xml +++ b/services/mediaconvert/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 mediaconvert @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/medialive/pom.xml b/services/medialive/pom.xml index ff8ae35169d4..2a8f9b869a32 100644 --- a/services/medialive/pom.xml +++ b/services/medialive/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 medialive @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mediapackage/pom.xml b/services/mediapackage/pom.xml index e61d009bd370..e049885abd36 100644 --- a/services/mediapackage/pom.xml +++ b/services/mediapackage/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 mediapackage @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mediapackagev2/pom.xml b/services/mediapackagev2/pom.xml index 0cad2c4ac823..20780ca03c57 100644 --- a/services/mediapackagev2/pom.xml +++ b/services/mediapackagev2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT mediapackagev2 AWS Java SDK :: Services :: Media Package V2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mediapackagevod/pom.xml b/services/mediapackagevod/pom.xml index d87e5c5b3be4..a7034e7e6e74 100644 --- a/services/mediapackagevod/pom.xml +++ b/services/mediapackagevod/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT mediapackagevod AWS Java SDK :: Services :: MediaPackage Vod @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mediastore/pom.xml b/services/mediastore/pom.xml index c4ee8cb790a0..338951396029 100644 --- a/services/mediastore/pom.xml +++ b/services/mediastore/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 mediastore @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mediastoredata/pom.xml b/services/mediastoredata/pom.xml index 69e8d8179b34..2760c21ca34c 100644 --- a/services/mediastoredata/pom.xml +++ b/services/mediastoredata/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 mediastoredata @@ -74,5 +74,10 @@ commons-lang3 test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mediastoredata/src/it/java/software/amazon/awssdk/services/mediastoredata/MediaStoreDataIntegrationTestBase.java b/services/mediastoredata/src/it/java/software/amazon/awssdk/services/mediastoredata/MediaStoreDataIntegrationTestBase.java index 91c9994ae307..73bd3f81feea 100644 --- a/services/mediastoredata/src/it/java/software/amazon/awssdk/services/mediastoredata/MediaStoreDataIntegrationTestBase.java +++ b/services/mediastoredata/src/it/java/software/amazon/awssdk/services/mediastoredata/MediaStoreDataIntegrationTestBase.java @@ -39,6 +39,8 @@ import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.http.ContentStreamProvider; import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.services.mediastore.MediaStoreClient; import software.amazon.awssdk.services.mediastore.model.Container; import software.amazon.awssdk.services.mediastore.model.ContainerStatus; @@ -50,7 +52,7 @@ * Base class for MediaStoreData integration tests. Used for Transfer-Encoding and Request Compression testing. */ public class MediaStoreDataIntegrationTestBase extends AwsIntegrationTestBase { - protected static AwsCredentialsProvider credentialsProvider; + protected static IdentityProvider credentialsProvider; protected static MediaStoreClient mediaStoreClient; protected static URI uri; diff --git a/services/mediastoredata/src/it/java/software/amazon/awssdk/services/mediastoredata/TransferEncodingChunkedIntegrationTest.java b/services/mediastoredata/src/it/java/software/amazon/awssdk/services/mediastoredata/TransferEncodingChunkedIntegrationTest.java index b4137a14eea9..8ad6ca31e7e8 100644 --- a/services/mediastoredata/src/it/java/software/amazon/awssdk/services/mediastoredata/TransferEncodingChunkedIntegrationTest.java +++ b/services/mediastoredata/src/it/java/software/amazon/awssdk/services/mediastoredata/TransferEncodingChunkedIntegrationTest.java @@ -29,6 +29,9 @@ import software.amazon.awssdk.http.apache.ApacheHttpClient; import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.services.mediastore.model.Container; import software.amazon.awssdk.services.mediastoredata.model.DeleteObjectRequest; import software.amazon.awssdk.services.mediastoredata.model.ObjectNotFoundException; import software.amazon.awssdk.services.mediastoredata.model.PutObjectRequest; @@ -42,6 +45,8 @@ public class TransferEncodingChunkedIntegrationTest extends MediaStoreDataIntegr private static MediaStoreDataClient syncClientWithApache; private static MediaStoreDataClient syncClientWithUrlConnection; private static MediaStoreDataAsyncClient asyncClientWithNetty; + private static IdentityProvider credentialsProvider; + private static Container container; private static PutObjectRequest putObjectRequest; private static DeleteObjectRequest deleteObjectRequest; diff --git a/services/mediatailor/pom.xml b/services/mediatailor/pom.xml index 40cab1da510f..1a809de50dbd 100644 --- a/services/mediatailor/pom.xml +++ b/services/mediatailor/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT mediatailor AWS Java SDK :: Services :: MediaTailor @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/medicalimaging/pom.xml b/services/medicalimaging/pom.xml index 3bc692b11705..fe61c6d7520f 100644 --- a/services/medicalimaging/pom.xml +++ b/services/medicalimaging/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT medicalimaging AWS Java SDK :: Services :: Medical Imaging @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/memorydb/pom.xml b/services/memorydb/pom.xml index 78e8ccb7236b..cc9c07fbe4f8 100644 --- a/services/memorydb/pom.xml +++ b/services/memorydb/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT memorydb AWS Java SDK :: Services :: Memory DB @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mgn/pom.xml b/services/mgn/pom.xml index a9fe4f939534..e9de74b947a2 100644 --- a/services/mgn/pom.xml +++ b/services/mgn/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT mgn AWS Java SDK :: Services :: Mgn @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/migrationhub/pom.xml b/services/migrationhub/pom.xml index 3a708bcf611b..15d98d69a4ae 100644 --- a/services/migrationhub/pom.xml +++ b/services/migrationhub/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 migrationhub @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/migrationhubconfig/pom.xml b/services/migrationhubconfig/pom.xml index 3c3cedebe0b1..5f1ed5776fa5 100644 --- a/services/migrationhubconfig/pom.xml +++ b/services/migrationhubconfig/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT migrationhubconfig AWS Java SDK :: Services :: MigrationHub Config @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/migrationhuborchestrator/pom.xml b/services/migrationhuborchestrator/pom.xml index d5be6f10c3c8..b95ec42ccd50 100644 --- a/services/migrationhuborchestrator/pom.xml +++ b/services/migrationhuborchestrator/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT migrationhuborchestrator AWS Java SDK :: Services :: Migration Hub Orchestrator @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/migrationhubrefactorspaces/pom.xml b/services/migrationhubrefactorspaces/pom.xml index cd41299d20b8..5f8b204329b0 100644 --- a/services/migrationhubrefactorspaces/pom.xml +++ b/services/migrationhubrefactorspaces/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT migrationhubrefactorspaces AWS Java SDK :: Services :: Migration Hub Refactor Spaces @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/migrationhubstrategy/pom.xml b/services/migrationhubstrategy/pom.xml index 71523190e8bb..d56726ae3130 100644 --- a/services/migrationhubstrategy/pom.xml +++ b/services/migrationhubstrategy/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT migrationhubstrategy AWS Java SDK :: Services :: Migration Hub Strategy @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mobile/pom.xml b/services/mobile/pom.xml index c957fde65cfb..3c161d6c905a 100644 --- a/services/mobile/pom.xml +++ b/services/mobile/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 mobile @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mq/pom.xml b/services/mq/pom.xml index 1fd86bff1f91..3f23fc50e9a9 100644 --- a/services/mq/pom.xml +++ b/services/mq/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 mq @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mturk/pom.xml b/services/mturk/pom.xml index 9db1762f40cb..5783f7d6e56c 100644 --- a/services/mturk/pom.xml +++ b/services/mturk/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT mturk AWS Java SDK :: Services :: Amazon Mechanical Turk Requester @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/mwaa/pom.xml b/services/mwaa/pom.xml index b78ff9e4ed19..89879ed64ab8 100644 --- a/services/mwaa/pom.xml +++ b/services/mwaa/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT mwaa AWS Java SDK :: Services :: MWAA @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/neptune/pom.xml b/services/neptune/pom.xml index c91dee034f42..8bf87544a7b3 100644 --- a/services/neptune/pom.xml +++ b/services/neptune/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT neptune AWS Java SDK :: Services :: Neptune @@ -61,5 +61,10 @@ profiles ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java b/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java index ba04154891a3..caba12352862 100644 --- a/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java +++ b/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java @@ -16,10 +16,13 @@ package software.amazon.awssdk.services.neptune.internal; import static software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute.AWS_CREDENTIALS; +import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME; import java.net.URI; import java.time.Clock; import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.CredentialUtils; import software.amazon.awssdk.auth.signer.Aws4Signer; import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams; @@ -37,10 +40,11 @@ import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.awssdk.protocols.query.AwsQueryProtocolFactory; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.neptune.model.NeptuneRequest; - +import software.amazon.awssdk.utils.CompletableFutureUtils; /** @@ -146,12 +150,23 @@ private SdkHttpFullRequest presignRequest(SdkHttpFullRequest request, .signingRegion(Region.of(signingRegion)) .signingName(SERVICE_NAME) .signingClockOverride(signingOverrideClock) - .awsCredentials(attributes.getAttribute(AWS_CREDENTIALS)) + .awsCredentials(resolveCredentials(attributes)) .build(); return signer.presign(request, presignerParams); } + private AwsCredentials resolveCredentials(ExecutionAttributes attributes) { + return attributes.getOptionalAttribute(SELECTED_AUTH_SCHEME) + .map(selectedAuthScheme -> selectedAuthScheme.identity()) + .map(identityFuture -> CompletableFutureUtils.joinLikeSync(identityFuture)) + .filter(identity -> identity instanceof AwsCredentialsIdentity) + .map(identity -> { + AwsCredentialsIdentity awsCredentialsIdentity = (AwsCredentialsIdentity) identity; + return CredentialUtils.toCredentials(awsCredentialsIdentity); + }).orElse(attributes.getAttribute(AWS_CREDENTIALS)); + } + private URI createEndpoint(String regionName, String serviceName, ExecutionAttributes attributes) { Region region = Region.of(regionName); diff --git a/services/neptunedata/pom.xml b/services/neptunedata/pom.xml index 7c24cf90840c..b4ac72775f77 100644 --- a/services/neptunedata/pom.xml +++ b/services/neptunedata/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT neptunedata AWS Java SDK :: Services :: Neptunedata @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/networkfirewall/pom.xml b/services/networkfirewall/pom.xml index de9324ac61ea..cc0df553cfb8 100644 --- a/services/networkfirewall/pom.xml +++ b/services/networkfirewall/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT networkfirewall AWS Java SDK :: Services :: Network Firewall @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/networkmanager/pom.xml b/services/networkmanager/pom.xml index e3be00acd608..74a908723388 100644 --- a/services/networkmanager/pom.xml +++ b/services/networkmanager/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT networkmanager AWS Java SDK :: Services :: NetworkManager @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/nimble/pom.xml b/services/nimble/pom.xml index 220538f242be..ae8b44af3273 100644 --- a/services/nimble/pom.xml +++ b/services/nimble/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT nimble AWS Java SDK :: Services :: Nimble @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/oam/pom.xml b/services/oam/pom.xml index 6791cb4b4d91..8993eefce720 100644 --- a/services/oam/pom.xml +++ b/services/oam/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT oam AWS Java SDK :: Services :: OAM @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/omics/pom.xml b/services/omics/pom.xml index ea7173eea9c1..d902028aca2f 100644 --- a/services/omics/pom.xml +++ b/services/omics/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT omics AWS Java SDK :: Services :: Omics @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/opensearch/pom.xml b/services/opensearch/pom.xml index 9e0d7b536793..a207e5d5c70d 100644 --- a/services/opensearch/pom.xml +++ b/services/opensearch/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT opensearch AWS Java SDK :: Services :: Open Search @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/opensearchserverless/pom.xml b/services/opensearchserverless/pom.xml index cffa228698c1..b362857fa87d 100644 --- a/services/opensearchserverless/pom.xml +++ b/services/opensearchserverless/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT opensearchserverless AWS Java SDK :: Services :: Open Search Serverless @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/opsworks/pom.xml b/services/opsworks/pom.xml index a4b5e9d07f09..7b6156e90c0f 100644 --- a/services/opsworks/pom.xml +++ b/services/opsworks/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT opsworks AWS Java SDK :: Services :: AWS OpsWorks @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/opsworkscm/pom.xml b/services/opsworkscm/pom.xml index 5e584c621833..29761174526b 100644 --- a/services/opsworkscm/pom.xml +++ b/services/opsworkscm/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT opsworkscm AWS Java SDK :: Services :: AWS OpsWorks for Chef Automate @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/organizations/pom.xml b/services/organizations/pom.xml index a0a066f7baea..417f330e1379 100644 --- a/services/organizations/pom.xml +++ b/services/organizations/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT organizations AWS Java SDK :: Services :: AWS Organizations @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/osis/pom.xml b/services/osis/pom.xml index d7cbfc53d195..05c9de606291 100644 --- a/services/osis/pom.xml +++ b/services/osis/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT osis AWS Java SDK :: Services :: OSIS @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/outposts/pom.xml b/services/outposts/pom.xml index b238fff0abf5..337e0e567934 100644 --- a/services/outposts/pom.xml +++ b/services/outposts/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT outposts AWS Java SDK :: Services :: Outposts @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/panorama/pom.xml b/services/panorama/pom.xml index f159759733f9..edbe009096d9 100644 --- a/services/panorama/pom.xml +++ b/services/panorama/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT panorama AWS Java SDK :: Services :: Panorama @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/paymentcryptography/pom.xml b/services/paymentcryptography/pom.xml index 8db448474a23..eee41ae59954 100644 --- a/services/paymentcryptography/pom.xml +++ b/services/paymentcryptography/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT paymentcryptography AWS Java SDK :: Services :: Payment Cryptography @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/paymentcryptographydata/pom.xml b/services/paymentcryptographydata/pom.xml index e0cece5c1351..c0891c8e6b3b 100644 --- a/services/paymentcryptographydata/pom.xml +++ b/services/paymentcryptographydata/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT paymentcryptographydata AWS Java SDK :: Services :: Payment Cryptography Data @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/pcaconnectorad/pom.xml b/services/pcaconnectorad/pom.xml index c350a5911793..1256f3c7f910 100644 --- a/services/pcaconnectorad/pom.xml +++ b/services/pcaconnectorad/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT pcaconnectorad AWS Java SDK :: Services :: Pca Connector Ad @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/personalize/pom.xml b/services/personalize/pom.xml index d12e5130f0b6..46cc716d9c74 100644 --- a/services/personalize/pom.xml +++ b/services/personalize/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT personalize AWS Java SDK :: Services :: Personalize @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/personalizeevents/pom.xml b/services/personalizeevents/pom.xml index 2099f73d6f80..9a1cdf4cf6c9 100644 --- a/services/personalizeevents/pom.xml +++ b/services/personalizeevents/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT personalizeevents AWS Java SDK :: Services :: Personalize Events @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/personalizeruntime/pom.xml b/services/personalizeruntime/pom.xml index f06e967f99c4..fe4d6754badf 100644 --- a/services/personalizeruntime/pom.xml +++ b/services/personalizeruntime/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT personalizeruntime AWS Java SDK :: Services :: Personalize Runtime @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/pi/pom.xml b/services/pi/pom.xml index 704499b3d4bd..244fd1a44fc0 100644 --- a/services/pi/pom.xml +++ b/services/pi/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT pi AWS Java SDK :: Services :: PI @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/pinpoint/pom.xml b/services/pinpoint/pom.xml index 9a1e366ce2fe..76af77961076 100644 --- a/services/pinpoint/pom.xml +++ b/services/pinpoint/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT pinpoint AWS Java SDK :: Services :: Amazon Pinpoint @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/pinpointemail/pom.xml b/services/pinpointemail/pom.xml index 9b9645301f4d..952ba7397f63 100644 --- a/services/pinpointemail/pom.xml +++ b/services/pinpointemail/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT pinpointemail AWS Java SDK :: Services :: Pinpoint Email @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/pinpointsmsvoice/pom.xml b/services/pinpointsmsvoice/pom.xml index 3109fc55205a..980eefd7cb35 100644 --- a/services/pinpointsmsvoice/pom.xml +++ b/services/pinpointsmsvoice/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT pinpointsmsvoice AWS Java SDK :: Services :: Pinpoint SMS Voice @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/pinpointsmsvoicev2/pom.xml b/services/pinpointsmsvoicev2/pom.xml index cddc8a35180c..27a7dc60788a 100644 --- a/services/pinpointsmsvoicev2/pom.xml +++ b/services/pinpointsmsvoicev2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT pinpointsmsvoicev2 AWS Java SDK :: Services :: Pinpoint SMS Voice V2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/pipes/pom.xml b/services/pipes/pom.xml index e3827d7d220d..c801161ce86a 100644 --- a/services/pipes/pom.xml +++ b/services/pipes/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT pipes AWS Java SDK :: Services :: Pipes @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/polly/pom.xml b/services/polly/pom.xml index 787cf1aa7cca..afb1b4ba3978 100644 --- a/services/polly/pom.xml +++ b/services/polly/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT polly AWS Java SDK :: Services :: Amazon Polly @@ -61,5 +61,10 @@ profiles ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java b/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java index d939bdbc389d..a02a7a94be58 100644 --- a/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java +++ b/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java @@ -28,8 +28,8 @@ import java.util.function.Supplier; import java.util.stream.Stream; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.CredentialUtils; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.auth.signer.Aws4Signer; import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; @@ -48,15 +48,22 @@ import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileFileSystemSetting; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; +import software.amazon.awssdk.services.polly.auth.scheme.PollyAuthSchemeProvider; import software.amazon.awssdk.services.polly.internal.presigner.model.transform.SynthesizeSpeechRequestMarshaller; import software.amazon.awssdk.services.polly.model.PollyRequest; import software.amazon.awssdk.services.polly.presigner.PollyPresigner; import software.amazon.awssdk.services.polly.presigner.model.PresignedSynthesizeSpeechRequest; import software.amazon.awssdk.services.polly.presigner.model.SynthesizeSpeechPresignRequest; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.IoUtils; import software.amazon.awssdk.utils.Validate; @@ -72,7 +79,7 @@ public final class DefaultPollyPresigner implements PollyPresigner { private final Supplier profileFile; private final String profileName; private final Region region; - private final AwsCredentialsProvider credentialsProvider; + private final IdentityProvider credentialsProvider; private final URI endpointOverride; private final Boolean dualstackEnabled; private final Boolean fipsEnabled; @@ -108,18 +115,10 @@ private DefaultPollyPresigner(BuilderImpl builder) { .orElse(false); } - public Region region() { - return region; - } - - public AwsCredentialsProvider credentialsProvider() { + IdentityProvider credentialsProvider() { return credentialsProvider; } - public URI endpointOverride() { - return endpointOverride; - } - @Override public void close() { IoUtils.closeIfCloseable(credentialsProvider, null); @@ -198,23 +197,38 @@ private SdkHttpFullRequest presignRequest(PollyRequest requestToPresign, private ExecutionAttributes createExecutionAttributes(PresignRequest presignRequest, PollyRequest requestToPresign) { Instant signatureExpiration = Instant.now().plus(presignRequest.signatureDuration()); - AwsCredentials credentials = resolveCredentialsProvider(requestToPresign).resolveCredentials(); + AwsCredentialsIdentity credentials = resolveCredentials(resolveCredentialsProvider(requestToPresign)); Validate.validState(credentials != null, "Credential providers must never return null."); return new ExecutionAttributes() - .putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, credentials) + .putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, CredentialUtils.toCredentials(credentials)) .putAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, SIGNING_NAME) - .putAttribute(AwsExecutionAttribute.AWS_REGION, region()) - .putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, region()) + .putAttribute(AwsExecutionAttribute.AWS_REGION, region) + .putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, region) .putAttribute(SdkInternalExecutionAttribute.IS_FULL_DUPLEX, false) .putAttribute(SdkExecutionAttribute.CLIENT_TYPE, ClientType.SYNC) .putAttribute(SdkExecutionAttribute.SERVICE_NAME, SERVICE_NAME) - .putAttribute(PRESIGNER_EXPIRATION, signatureExpiration); + .putAttribute(PRESIGNER_EXPIRATION, signatureExpiration) + .putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER, PollyAuthSchemeProvider.defaultProvider()) + .putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES, authSchemes()) + .putAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS, + IdentityProviders.builder() + .putIdentityProvider(credentialsProvider()) + .build()); + } + + private Map> authSchemes() { + AwsV4AuthScheme awsV4AuthScheme = AwsV4AuthScheme.create(); + return Collections.singletonMap(awsV4AuthScheme.schemeId(), awsV4AuthScheme); } - private AwsCredentialsProvider resolveCredentialsProvider(PollyRequest request) { - return request.overrideConfiguration().flatMap(AwsRequestOverrideConfiguration::credentialsProvider) - .orElse(credentialsProvider()); + private IdentityProvider resolveCredentialsProvider(PollyRequest request) { + return request.overrideConfiguration().flatMap(AwsRequestOverrideConfiguration::credentialsIdentityProvider) + .orElse(credentialsProvider); + } + + private AwsCredentialsIdentity resolveCredentials(IdentityProvider credentialsProvider) { + return CompletableFutureUtils.joinLikeSync(credentialsProvider.resolveIdentity()); } private Presigner resolvePresigner(PollyRequest request) { @@ -240,12 +254,12 @@ private void applyEndpoint(SdkHttpFullRequest.Builder httpRequestBuilder) { } private URI resolveEndpoint() { - if (endpointOverride() != null) { - return endpointOverride(); + if (endpointOverride != null) { + return endpointOverride; } return new DefaultServiceEndpointBuilder(SERVICE_NAME, "https") - .withRegion(region()) + .withRegion(region) .withProfileFile(profileFile) .withProfileName(profileName) .withDualstackEnabled(dualstackEnabled) @@ -255,7 +269,7 @@ private URI resolveEndpoint() { public static class BuilderImpl implements PollyPresigner.Builder { private Region region; - private AwsCredentialsProvider credentialsProvider; + private IdentityProvider credentialsProvider; private URI endpointOverride; private Boolean dualstackEnabled; private Boolean fipsEnabled; @@ -268,6 +282,11 @@ public Builder region(Region region) { @Override public Builder credentialsProvider(AwsCredentialsProvider credentialsProvider) { + return credentialsProvider((IdentityProvider) credentialsProvider); + } + + @Override + public Builder credentialsProvider(IdentityProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; return this; } diff --git a/services/polly/src/main/java/software/amazon/awssdk/services/polly/presigner/PollyPresigner.java b/services/polly/src/main/java/software/amazon/awssdk/services/polly/presigner/PollyPresigner.java index 90602e481e86..61a24fa74ed9 100644 --- a/services/polly/src/main/java/software/amazon/awssdk/services/polly/presigner/PollyPresigner.java +++ b/services/polly/src/main/java/software/amazon/awssdk/services/polly/presigner/PollyPresigner.java @@ -22,6 +22,8 @@ import software.amazon.awssdk.awscore.presigner.PresignedRequest; import software.amazon.awssdk.awscore.presigner.SdkPresigner; import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.polly.internal.presigner.DefaultPollyPresigner; import software.amazon.awssdk.services.polly.model.PollyRequest; @@ -163,6 +165,9 @@ interface Builder extends SdkPresigner.Builder { @Override Builder credentialsProvider(AwsCredentialsProvider credentialsProvider); + @Override + Builder credentialsProvider(IdentityProvider credentialsProvider); + @Override Builder dualstackEnabled(Boolean dualstackEnabled); diff --git a/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java b/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java index 4d5cc4973ff3..30491ef287ea 100644 --- a/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java +++ b/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java @@ -27,10 +27,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.polly.model.OutputFormat; import software.amazon.awssdk.services.polly.model.SynthesizeSpeechRequest; @@ -48,7 +49,7 @@ class DefaultPollyPresignerTest { .text("Hello presigners!") .build(); - private AwsCredentialsProvider credentialsProvider; + private IdentityProvider credentialsProvider; @BeforeEach public void methodSetup() { @@ -57,7 +58,9 @@ public void methodSetup() { @Test void presign_requestLevelCredentials_honored() { - AwsCredentials requestCredentials = AwsBasicCredentials.create("akid2", "skid2"); + IdentityProvider requestCedentialsProvider = StaticCredentialsProvider.create( + AwsBasicCredentials.create("akid2", "skid2") + ); PollyPresigner presigner = DefaultPollyPresigner.builder() .region(Region.US_EAST_1) @@ -66,7 +69,7 @@ void presign_requestLevelCredentials_honored() { SynthesizeSpeechRequest synthesizeSpeechRequest = BASIC_SYNTHESIZE_SPEECH_REQUEST.toBuilder() .overrideConfiguration(AwsRequestOverrideConfiguration.builder() - .credentialsProvider(StaticCredentialsProvider.create(requestCredentials)).build()) + .credentialsProvider(requestCedentialsProvider).build()) .build(); SynthesizeSpeechPresignRequest presignRequest = SynthesizeSpeechPresignRequest.builder() @@ -186,6 +189,24 @@ void close_closesCustomCloseableCredentialsProvider() throws IOException { verify(mockCredentialsProvider).close(); } + @Test + void presigner_credentialsProviderSetAsAwsCredentialsProvider_delegatesCorrectly() { + AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")); + DefaultPollyPresigner presigner1 = (DefaultPollyPresigner) + DefaultPollyPresigner.builder() + .region(Region.US_EAST_1) + .credentialsProvider(credentialsProvider) + .build(); + + DefaultPollyPresigner presigner2 = (DefaultPollyPresigner) + DefaultPollyPresigner.builder() + .region(Region.US_EAST_1) + .credentialsProvider((IdentityProvider) credentialsProvider) + .build(); + + assertThat(presigner1.credentialsProvider()).isSameAs(presigner2.credentialsProvider()); + } + @Test void presigner_credentialsProviderSetToNullByBuilder_createsDefaultCredentialsProvider() { DefaultPollyPresigner presigner = (DefaultPollyPresigner) @@ -195,10 +216,10 @@ void presigner_credentialsProviderSetToNullByBuilder_createsDefaultCredentialsPr .build(); - AwsCredentialsProvider awsCredentialsProvider = presigner.credentialsProvider(); + IdentityProvider awsCredentialsProvider = presigner.credentialsProvider(); assertThat(awsCredentialsProvider).isNotNull(); } - private interface TestCredentialsProvider extends AwsCredentialsProvider, Closeable { + private interface TestCredentialsProvider extends IdentityProvider, Closeable { } } diff --git a/services/pom.xml b/services/pom.xml index a5f6238d6782..6610716c38f5 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -17,7 +17,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT services AWS Java SDK :: Services @@ -403,6 +403,21 @@ auth ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth + ${awsjavasdk.version} + + + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + software.amazon.awssdk http-client-spi @@ -443,6 +458,11 @@ endpoints-spi ${awsjavasdk.version} + + software.amazon.awssdk + profiles + ${awsjavasdk.version} + apache-client software.amazon.awssdk diff --git a/services/pricing/pom.xml b/services/pricing/pom.xml index c84f4d54a83a..985ab8453760 100644 --- a/services/pricing/pom.xml +++ b/services/pricing/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 pricing @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/privatenetworks/pom.xml b/services/privatenetworks/pom.xml index 8c572706e2b3..7ae933692458 100644 --- a/services/privatenetworks/pom.xml +++ b/services/privatenetworks/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT privatenetworks AWS Java SDK :: Services :: Private Networks @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/proton/pom.xml b/services/proton/pom.xml index e6446db96b1e..cbd668a7c7c8 100644 --- a/services/proton/pom.xml +++ b/services/proton/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT proton AWS Java SDK :: Services :: Proton @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/qldb/pom.xml b/services/qldb/pom.xml index 30697b06b46a..74a0b0ac722b 100644 --- a/services/qldb/pom.xml +++ b/services/qldb/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT qldb AWS Java SDK :: Services :: QLDB @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/qldbsession/pom.xml b/services/qldbsession/pom.xml index c9f31247db4d..f52023909e4b 100644 --- a/services/qldbsession/pom.xml +++ b/services/qldbsession/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT qldbsession AWS Java SDK :: Services :: QLDB Session @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/quicksight/pom.xml b/services/quicksight/pom.xml index 187c2e3b9711..11d4818777e4 100644 --- a/services/quicksight/pom.xml +++ b/services/quicksight/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT quicksight AWS Java SDK :: Services :: QuickSight @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ram/pom.xml b/services/ram/pom.xml index ca6c5ad909a0..17eb4dfd8d6e 100644 --- a/services/ram/pom.xml +++ b/services/ram/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ram AWS Java SDK :: Services :: RAM @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/rbin/pom.xml b/services/rbin/pom.xml index 152cbf9944ef..d8b6816a0850 100644 --- a/services/rbin/pom.xml +++ b/services/rbin/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT rbin AWS Java SDK :: Services :: Rbin @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/rds/pom.xml b/services/rds/pom.xml index 57fbcca3969b..df58856c0ee8 100644 --- a/services/rds/pom.xml +++ b/services/rds/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT rds AWS Java SDK :: Services :: Amazon RDS @@ -61,5 +61,10 @@ profiles ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/rds/src/main/java/software/amazon/awssdk/services/rds/DefaultRdsUtilities.java b/services/rds/src/main/java/software/amazon/awssdk/services/rds/DefaultRdsUtilities.java index 84ece242de73..bc31e1898efd 100644 --- a/services/rds/src/main/java/software/amazon/awssdk/services/rds/DefaultRdsUtilities.java +++ b/services/rds/src/main/java/software/amazon/awssdk/services/rds/DefaultRdsUtilities.java @@ -20,15 +20,19 @@ import java.time.Instant; import software.amazon.awssdk.annotations.Immutable; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.CredentialUtils; import software.amazon.awssdk.auth.signer.Aws4Signer; import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.rds.model.GenerateAuthenticationTokenRequest; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.StringUtils; @@ -42,7 +46,7 @@ final class DefaultRdsUtilities implements RdsUtilities { private final Aws4Signer signer = Aws4Signer.create(); private final Region region; - private final AwsCredentialsProvider credentialsProvider; + private final IdentityProvider credentialsProvider; private final Clock clock; DefaultRdsUtilities(DefaultBuilder builder) { @@ -83,7 +87,7 @@ public String generateAuthenticationToken(GenerateAuthenticationTokenRequest req Aws4PresignerParams presignRequest = Aws4PresignerParams.builder() .signingClockOverride(clock) .expirationTime(expirationTime) - .awsCredentials(resolveCredentials(request).resolveCredentials()) + .awsCredentials(resolveCredentials(request)) .signingName("rds-db") .signingRegion(resolveRegion(request)) .build(); @@ -111,13 +115,15 @@ private Region resolveRegion(GenerateAuthenticationTokenRequest request) { "or RdsUtilities object"); } - private AwsCredentialsProvider resolveCredentials(GenerateAuthenticationTokenRequest request) { - if (request.credentialsProvider() != null) { - return request.credentialsProvider(); + // TODO: update this to use AwsCredentialsIdentity when we migrate Signers to accept the new type. + private AwsCredentials resolveCredentials(GenerateAuthenticationTokenRequest request) { + if (request.credentialsIdentityProvider() != null) { + return CredentialUtils.toCredentials( + CompletableFutureUtils.joinLikeSync(request.credentialsIdentityProvider().resolveIdentity())); } if (this.credentialsProvider != null) { - return this.credentialsProvider; + return CredentialUtils.toCredentials(CompletableFutureUtils.joinLikeSync(this.credentialsProvider.resolveIdentity())); } throw new IllegalArgumentException("CredentialProvider should be provided either in GenerateAuthenticationTokenRequest " + @@ -127,13 +133,13 @@ private AwsCredentialsProvider resolveCredentials(GenerateAuthenticationTokenReq @SdkInternalApi static final class DefaultBuilder implements Builder { private Region region; - private AwsCredentialsProvider credentialsProvider; + private IdentityProvider credentialsProvider; DefaultBuilder() { } Builder clientConfiguration(SdkClientConfiguration clientConfiguration) { - this.credentialsProvider = clientConfiguration.option(AwsClientOption.CREDENTIALS_PROVIDER); + this.credentialsProvider = clientConfiguration.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER); this.region = clientConfiguration.option(AwsClientOption.AWS_REGION); return this; @@ -146,7 +152,7 @@ public Builder region(Region region) { } @Override - public Builder credentialsProvider(AwsCredentialsProvider credentialsProvider) { + public Builder credentialsProvider(IdentityProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; return this; } @@ -159,4 +165,4 @@ public RdsUtilities build() { return new DefaultRdsUtilities(this); } } -} \ No newline at end of file +} diff --git a/services/rds/src/main/java/software/amazon/awssdk/services/rds/RdsUtilities.java b/services/rds/src/main/java/software/amazon/awssdk/services/rds/RdsUtilities.java index 69ae4520c52a..76af31326a3b 100644 --- a/services/rds/src/main/java/software/amazon/awssdk/services/rds/RdsUtilities.java +++ b/services/rds/src/main/java/software/amazon/awssdk/services/rds/RdsUtilities.java @@ -18,6 +18,8 @@ import java.util.function.Consumer; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.rds.model.GenerateAuthenticationTokenRequest; @@ -95,7 +97,19 @@ interface Builder { * * @return This object for method chaining */ - Builder credentialsProvider(AwsCredentialsProvider credentialsProvider); + default Builder credentialsProvider(AwsCredentialsProvider credentialsProvider) { + return credentialsProvider((IdentityProvider) credentialsProvider); + } + + /** + * The default credentials provider to use when working with the methods in {@link RdsUtilities} class. + * + * @return This object for method chaining + */ + default Builder credentialsProvider(IdentityProvider credentialsProvider) { + throw new UnsupportedOperationException(); + } + /** * Create a {@link RdsUtilities} diff --git a/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java b/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java index cd2ad833f8db..46134f91dfbb 100644 --- a/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java +++ b/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java @@ -16,10 +16,13 @@ package software.amazon.awssdk.services.rds.internal; import static software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute.AWS_CREDENTIALS; +import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME; import java.net.URI; import java.time.Clock; import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.CredentialUtils; import software.amazon.awssdk.auth.signer.Aws4Signer; import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams; @@ -37,9 +40,11 @@ import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.awssdk.protocols.query.AwsQueryProtocolFactory; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.rds.model.RdsRequest; +import software.amazon.awssdk.utils.CompletableFutureUtils; /** * Abstract pre-sign handler that follows the pre-signing scheme outlined in the 'RDS Presigned URL for Cross-Region Copying' @@ -144,12 +149,23 @@ private SdkHttpFullRequest presignRequest(SdkHttpFullRequest request, .signingRegion(Region.of(signingRegion)) .signingName(SERVICE_NAME) .signingClockOverride(signingOverrideClock) - .awsCredentials(attributes.getAttribute(AWS_CREDENTIALS)) + .awsCredentials(resolveCredentials(attributes)) .build(); return signer.presign(request, presignerParams); } + private AwsCredentials resolveCredentials(ExecutionAttributes attributes) { + return attributes.getOptionalAttribute(SELECTED_AUTH_SCHEME) + .map(selectedAuthScheme -> selectedAuthScheme.identity()) + .map(identityFuture -> CompletableFutureUtils.joinLikeSync(identityFuture)) + .filter(identity -> identity instanceof AwsCredentialsIdentity) + .map(identity -> { + AwsCredentialsIdentity awsCredentialsIdentity = (AwsCredentialsIdentity) identity; + return CredentialUtils.toCredentials(awsCredentialsIdentity); + }).orElse(attributes.getAttribute(AWS_CREDENTIALS)); + } + private URI createEndpoint(String regionName, String serviceName, ExecutionAttributes attributes) { Region region = Region.of(regionName); diff --git a/services/rds/src/main/java/software/amazon/awssdk/services/rds/model/GenerateAuthenticationTokenRequest.java b/services/rds/src/main/java/software/amazon/awssdk/services/rds/model/GenerateAuthenticationTokenRequest.java index fb13d6f95353..9c7f79888b0e 100644 --- a/services/rds/src/main/java/software/amazon/awssdk/services/rds/model/GenerateAuthenticationTokenRequest.java +++ b/services/rds/src/main/java/software/amazon/awssdk/services/rds/model/GenerateAuthenticationTokenRequest.java @@ -18,6 +18,9 @@ import software.amazon.awssdk.annotations.NotThreadSafe; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.CredentialUtils; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.rds.RdsUtilities; import software.amazon.awssdk.utils.Validate; @@ -34,7 +37,7 @@ public final class GenerateAuthenticationTokenRequest implements private final int port; private final String username; private final Region region; - private final AwsCredentialsProvider credentialsProvider; + private final IdentityProvider credentialsProvider; private GenerateAuthenticationTokenRequest(BuilderImpl builder) { this.hostname = Validate.notEmpty(builder.hostname, "hostname"); @@ -75,9 +78,17 @@ public Region region() { /** * @return The credentials provider to sign the IAM auth request with. If specified, takes precedence over the value - * specified in {@link RdsUtilities.Builder#credentialsProvider(AwsCredentialsProvider)}} + * specified in {@link RdsUtilities.Builder#credentialsProvider}} */ public AwsCredentialsProvider credentialsProvider() { + return CredentialUtils.toCredentialsProvider(credentialsProvider); + } + + /** + * @return The credentials provider to sign the IAM auth request with. If specified, takes precedence over the value + * specified in {@link RdsUtilities.Builder#credentialsProvider(AwsCredentialsProvider)}} + */ + public IdentityProvider credentialsIdentityProvider() { return credentialsProvider; } @@ -130,11 +141,23 @@ public interface Builder extends CopyableBuilder) credentialsProvider); + } + + /** + * The credentials provider to sign the IAM auth request with. If specified, takes precedence over the value + * specified in {@link RdsUtilities.Builder#credentialsProvider}} + * + * @return This object for method chaining + */ + default Builder credentialsProvider(IdentityProvider credentialsProvider) { + throw new UnsupportedOperationException(); + } @Override GenerateAuthenticationTokenRequest build(); @@ -145,7 +168,7 @@ private static final class BuilderImpl implements Builder { private int port; private String username; private Region region; - private AwsCredentialsProvider credentialsProvider; + private IdentityProvider credentialsProvider; private BuilderImpl() { } @@ -183,7 +206,7 @@ public Builder region(Region region) { } @Override - public Builder credentialsProvider(AwsCredentialsProvider credentialsProvider) { + public Builder credentialsProvider(IdentityProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; return this; } diff --git a/services/rds/src/test/java/software/amazon/awssdk/services/rds/DefaultRdsUtilitiesTest.java b/services/rds/src/test/java/software/amazon/awssdk/services/rds/DefaultRdsUtilitiesTest.java index a8648196dadb..9830cc8f3b40 100644 --- a/services/rds/src/test/java/software/amazon/awssdk/services/rds/DefaultRdsUtilitiesTest.java +++ b/services/rds/src/test/java/software/amazon/awssdk/services/rds/DefaultRdsUtilitiesTest.java @@ -6,19 +6,23 @@ import java.time.Clock; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.function.Consumer; import org.junit.jupiter.api.Test; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.rds.DefaultRdsUtilities.DefaultBuilder; +import software.amazon.awssdk.services.rds.model.GenerateAuthenticationTokenRequest; public class DefaultRdsUtilitiesTest { private final ZoneId utcZone = ZoneId.of("UTC").normalized(); private final Clock fixedClock = Clock.fixed(ZonedDateTime.of(2016, 11, 7, 17, 39, 33, 0, utcZone).toInstant(), utcZone); @Test - public void testTokenGenerationWithBuilderDefaults() { + public void testTokenGenerationWithBuilderDefaultsUsingAwsCredentialsProvider() { AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( AwsBasicCredentials.create("access_key", "secret_key") ); @@ -26,6 +30,22 @@ public void testTokenGenerationWithBuilderDefaults() { .credentialsProvider(credentialsProvider) .region(Region.US_EAST_1); + testTokenGenerationWithBuilderDefaults(utilitiesBuilder); + } + + @Test + public void testTokenGenerationWithBuilderDefaultsUsingIdentityProvider() { + IdentityProvider credentialsProvider = StaticCredentialsProvider.create( + AwsBasicCredentials.create("access_key", "secret_key") + ); + DefaultBuilder utilitiesBuilder = (DefaultBuilder) RdsUtilities.builder() + .credentialsProvider(credentialsProvider) + .region(Region.US_EAST_1); + + testTokenGenerationWithBuilderDefaults(utilitiesBuilder); + } + + private void testTokenGenerationWithBuilderDefaults(DefaultBuilder utilitiesBuilder) { DefaultRdsUtilities rdsUtilities = new DefaultRdsUtilities(utilitiesBuilder, fixedClock); String authenticationToken = rdsUtilities.generateAuthenticationToken(builder -> { @@ -42,22 +62,42 @@ public void testTokenGenerationWithBuilderDefaults() { } @Test - public void testTokenGenerationWithOverriddenCredentials() { + public void testTokenGenerationWithOverriddenCredentialsUsingAwsCredentialsProvider() { AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( AwsBasicCredentials.create("foo", "bar") ); DefaultBuilder utilitiesBuilder = (DefaultBuilder) RdsUtilities.builder() .credentialsProvider(credentialsProvider) .region(Region.US_EAST_1); + testTokenGenerationWithOverriddenCredentials(utilitiesBuilder, builder -> { + builder.credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create("access_key", "secret_key"))); + }); + } + + @Test + public void testTokenGenerationWithOverriddenCredentialsUsingIdentityProvider() { + IdentityProvider credentialsProvider = StaticCredentialsProvider.create( + AwsBasicCredentials.create("foo", "bar") + ); + DefaultBuilder utilitiesBuilder = (DefaultBuilder) RdsUtilities.builder() + .credentialsProvider(credentialsProvider) + .region(Region.US_EAST_1); + testTokenGenerationWithOverriddenCredentials(utilitiesBuilder, builder -> { + builder.credentialsProvider((IdentityProvider) StaticCredentialsProvider.create( + AwsBasicCredentials.create("access_key", "secret_key"))); + }); + } + + private void testTokenGenerationWithOverriddenCredentials(DefaultBuilder utilitiesBuilder, + Consumer credsBuilder) { DefaultRdsUtilities rdsUtilities = new DefaultRdsUtilities(utilitiesBuilder, fixedClock); String authenticationToken = rdsUtilities.generateAuthenticationToken(builder -> { builder.username("mySQLUser") .hostname("host.us-east-1.amazonaws.com") .port(3306) - .credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create("access_key", "secret_key") - )); + .applyMutation(credsBuilder); }); String expectedToken = "host.us-east-1.amazonaws.com:3306/?DBUser=mySQLUser&Action=connect&" + @@ -69,7 +109,7 @@ public void testTokenGenerationWithOverriddenCredentials() { @Test public void testTokenGenerationWithOverriddenRegion() { - AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( + IdentityProvider credentialsProvider = StaticCredentialsProvider.create( AwsBasicCredentials.create("access_key", "secret_key") ); DefaultBuilder utilitiesBuilder = (DefaultBuilder) RdsUtilities.builder() @@ -94,7 +134,7 @@ public void testTokenGenerationWithOverriddenRegion() { @Test public void testMissingRegionThrowsException() { - AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( + IdentityProvider credentialsProvider = StaticCredentialsProvider.create( AwsBasicCredentials.create("access_key", "secret_key") ); DefaultBuilder utilitiesBuilder = (DefaultBuilder) RdsUtilities.builder() diff --git a/services/rdsdata/pom.xml b/services/rdsdata/pom.xml index 90506935bdc5..ac75a82cbf7f 100644 --- a/services/rdsdata/pom.xml +++ b/services/rdsdata/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT rdsdata AWS Java SDK :: Services :: RDS Data @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/redshift/pom.xml b/services/redshift/pom.xml index 7bba6f7c0868..91fd58e398b6 100644 --- a/services/redshift/pom.xml +++ b/services/redshift/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT redshift AWS Java SDK :: Services :: Amazon Redshift @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/redshiftdata/pom.xml b/services/redshiftdata/pom.xml index 607c0eafb2ac..58f17e3975c2 100644 --- a/services/redshiftdata/pom.xml +++ b/services/redshiftdata/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT redshiftdata AWS Java SDK :: Services :: Redshift Data @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/redshiftserverless/pom.xml b/services/redshiftserverless/pom.xml index f6e2b35bde5f..9527ddc491f1 100644 --- a/services/redshiftserverless/pom.xml +++ b/services/redshiftserverless/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT redshiftserverless AWS Java SDK :: Services :: Redshift Serverless @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/rekognition/pom.xml b/services/rekognition/pom.xml index 62c4d7bd9f00..660541b6a98e 100644 --- a/services/rekognition/pom.xml +++ b/services/rekognition/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT rekognition AWS Java SDK :: Services :: Amazon Rekognition @@ -56,6 +56,11 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/resiliencehub/pom.xml b/services/resiliencehub/pom.xml index c45fe1a5d3cb..0f487ddf4650 100644 --- a/services/resiliencehub/pom.xml +++ b/services/resiliencehub/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT resiliencehub AWS Java SDK :: Services :: Resiliencehub @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/resourceexplorer2/pom.xml b/services/resourceexplorer2/pom.xml index 23f8b648c9f2..4023c5e8518d 100644 --- a/services/resourceexplorer2/pom.xml +++ b/services/resourceexplorer2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT resourceexplorer2 AWS Java SDK :: Services :: Resource Explorer 2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/resourcegroups/pom.xml b/services/resourcegroups/pom.xml index 3a38be39d938..6ee140b7b23d 100644 --- a/services/resourcegroups/pom.xml +++ b/services/resourcegroups/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 resourcegroups @@ -58,5 +58,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/resourcegroupstaggingapi/pom.xml b/services/resourcegroupstaggingapi/pom.xml index 7f9c45666509..a4ca8cdf747b 100644 --- a/services/resourcegroupstaggingapi/pom.xml +++ b/services/resourcegroupstaggingapi/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT resourcegroupstaggingapi AWS Java SDK :: Services :: AWS Resource Groups Tagging API @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/robomaker/pom.xml b/services/robomaker/pom.xml index c3a93f6edec3..04dd4f0663ae 100644 --- a/services/robomaker/pom.xml +++ b/services/robomaker/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT robomaker AWS Java SDK :: Services :: RoboMaker @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/rolesanywhere/pom.xml b/services/rolesanywhere/pom.xml index 2b69d1353677..3f1f31555242 100644 --- a/services/rolesanywhere/pom.xml +++ b/services/rolesanywhere/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT rolesanywhere AWS Java SDK :: Services :: Roles Anywhere @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/route53/pom.xml b/services/route53/pom.xml index dcd1db016eaf..8ae760f91e2f 100644 --- a/services/route53/pom.xml +++ b/services/route53/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT route53 AWS Java SDK :: Services :: Amazon Route53 @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/route53domains/pom.xml b/services/route53domains/pom.xml index ed6fcb39e295..1316eb3984b8 100644 --- a/services/route53domains/pom.xml +++ b/services/route53domains/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT route53domains AWS Java SDK :: Services :: Amazon Route53 Domains @@ -58,6 +58,11 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/route53recoverycluster/pom.xml b/services/route53recoverycluster/pom.xml index f36e111d2071..833f79a8a282 100644 --- a/services/route53recoverycluster/pom.xml +++ b/services/route53recoverycluster/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT route53recoverycluster AWS Java SDK :: Services :: Route53 Recovery Cluster @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/route53recoverycontrolconfig/pom.xml b/services/route53recoverycontrolconfig/pom.xml index e6ee5429bab6..4558f8432610 100644 --- a/services/route53recoverycontrolconfig/pom.xml +++ b/services/route53recoverycontrolconfig/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT route53recoverycontrolconfig AWS Java SDK :: Services :: Route53 Recovery Control Config @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/route53recoveryreadiness/pom.xml b/services/route53recoveryreadiness/pom.xml index 4fe9c09b9d60..de38223873c1 100644 --- a/services/route53recoveryreadiness/pom.xml +++ b/services/route53recoveryreadiness/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT route53recoveryreadiness AWS Java SDK :: Services :: Route53 Recovery Readiness @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/route53resolver/pom.xml b/services/route53resolver/pom.xml index 3699e4d46194..3483430a986f 100644 --- a/services/route53resolver/pom.xml +++ b/services/route53resolver/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT route53resolver AWS Java SDK :: Services :: Route53Resolver @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/rum/pom.xml b/services/rum/pom.xml index 1e13391f6242..e6a304259d1f 100644 --- a/services/rum/pom.xml +++ b/services/rum/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT rum AWS Java SDK :: Services :: RUM @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/s3/pom.xml b/services/s3/pom.xml index c5f139e91369..b758ac2bb5c7 100644 --- a/services/s3/pom.xml +++ b/services/s3/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT s3 AWS Java SDK :: Services :: Amazon S3 @@ -80,6 +80,11 @@ crt-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth + ${awsjavasdk.version} + software.amazon.awssdk.crt aws-crt @@ -138,6 +143,12 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws-crt + ${awsjavasdk.version} + test + io.netty netty-transport @@ -164,5 +175,10 @@ test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/s3/S3PresignerIntegrationTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/s3/S3PresignerIntegrationTest.java index c95d47f8fe54..097fb013e95e 100644 --- a/services/s3/src/it/java/software/amazon/awssdk/services/s3/S3PresignerIntegrationTest.java +++ b/services/s3/src/it/java/software/amazon/awssdk/services/s3/S3PresignerIntegrationTest.java @@ -211,7 +211,7 @@ public void deleteObject_PresignedHttpRequestCanBeInvokedDirectlyBySdk() throws PresignedDeleteObjectRequest presigned = presigner.presignDeleteObject(r -> r.signatureDuration(Duration.ofMinutes(5)) .deleteObjectRequest(delo -> delo.bucket(testBucket) - .key(testGetObjectKey) + .key(objectKey) .requestPayer(RequestPayer.REQUESTER))); assertThat(presigned.isBrowserExecutable()).isFalse(); diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/s3/checksum/AsyncHttpChecksumIntegrationTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/s3/checksum/AsyncHttpChecksumIntegrationTest.java index 9d20673aec5b..1f296345290c 100644 --- a/services/s3/src/it/java/software/amazon/awssdk/services/s3/checksum/AsyncHttpChecksumIntegrationTest.java +++ b/services/s3/src/it/java/software/amazon/awssdk/services/s3/checksum/AsyncHttpChecksumIntegrationTest.java @@ -49,7 +49,6 @@ import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectResponse; import software.amazon.awssdk.services.s3.utils.CaptureChecksumValidationInterceptor; -import software.amazon.awssdk.services.s3.utils.ChecksumUtils; import software.amazon.awssdk.testutils.RandomTempFile; public class AsyncHttpChecksumIntegrationTest extends S3IntegrationTestBase { @@ -234,9 +233,16 @@ void asyncValidSignedTrailerChecksumCalculatedBySdkClient() { assertThat(response).isEqualTo("Hello world"); } + /** + * S3 clients by default don't do payload signing. But when http is used, payload signing is expected to be enforced. But + * payload signing is not currently supported in async path (for both pre/post SRA signers). + * However, this test passes, because of https://github + * .com/aws/aws-sdk-java-v2/blob/38e221bd815af31a6c6b91557499af155103c21a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/internal/AbstractAwsS3V4Signer.java#L279-L285. + * Keeping this test enabled, to ensure moving to SRA Identity & Auth, does not break current behavior. + * TODO: Update this test with right asserts when payload signing is supported in async. + */ @Test public void putObject_with_bufferCreatedFromEmptyString() { - s3HttpAsync.putObject(PutObjectRequest.builder() .bucket(BUCKET) .key(KEY) @@ -256,6 +262,14 @@ public void putObject_with_bufferCreatedFromEmptyString() { assertThat(response).isEqualTo(""); } + /** + * S3 clients by default don't do payload signing. But when http is used, payload signing is expected to be enforced. But + * payload signing is not currently supported in async path (for both pre/post SRA signers). + * However, this test passes, because of https://github + * .com/aws/aws-sdk-java-v2/blob/38e221bd815af31a6c6b91557499af155103c21a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/internal/AbstractAwsS3V4Signer.java#L279-L285. + * Keeping this test enabled, to ensure moving to SRA Identity & Auth, does not break current behavior. + * TODO: Update this test with right asserts when payload signing is supported in async. + */ @Test public void putObject_with_bufferCreatedFromZeroCapacityByteBuffer() { ByteBuffer content = ByteBuffer.allocate(0); diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java index 78dc144f6613..bd9558e1f87b 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java @@ -20,6 +20,8 @@ import java.util.function.Consumer; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration; import software.amazon.awssdk.services.s3.crt.S3CrtRetryConfiguration; @@ -34,6 +36,29 @@ @SdkPublicApi public interface S3CrtAsyncClientBuilder extends SdkBuilder { + /** + * Configure the credentials that should be used to authenticate with S3. + * + *

The default provider will attempt to identify the credentials automatically using the following checks: + *

    + *
  1. Java System Properties - aws.accessKeyId and aws.secretKey
  2. + *
  3. Environment Variables - AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
  4. + *
  5. Credential profiles file at the default location (~/.aws/credentials) shared by all AWS SDKs and the AWS CLI
  6. + *
  7. Credentials delivered through the Amazon EC2 container service if AWS_CONTAINER_CREDENTIALS_RELATIVE_URI + * environment variable is set and security manager has permission to access the variable.
  8. + *
  9. Instance profile credentials delivered through the Amazon EC2 metadata service
  10. + *
+ * + *

If the credentials are not found in any of the locations above, an exception will be thrown at {@link #build()} + * time. + *

+ * + * @param credentialsProvider the credentials to use + * @return This builder for method chaining. + */ + default S3CrtAsyncClientBuilder credentialsProvider(AwsCredentialsProvider credentialsProvider) { + return credentialsProvider((IdentityProvider) credentialsProvider); + } /** * Configure the credentials that should be used to authenticate with S3. @@ -55,7 +80,9 @@ public interface S3CrtAsyncClientBuilder extends SdkBuilder credentialsProvider) { + throw new UnsupportedOperationException(); + } /** * Configure the region with which the SDK should communicate. diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/CrtCredentialsProviderAdapter.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/CrtCredentialsProviderAdapter.java index 9ce93b028a93..d807b61235f8 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/CrtCredentialsProviderAdapter.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/CrtCredentialsProviderAdapter.java @@ -19,12 +19,14 @@ import java.nio.charset.StandardCharsets; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; -import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; import software.amazon.awssdk.crt.auth.credentials.Credentials; import software.amazon.awssdk.crt.auth.credentials.CredentialsProvider; import software.amazon.awssdk.crt.auth.credentials.DelegateCredentialsProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.SdkAutoCloseable; /** @@ -32,10 +34,10 @@ */ @SdkInternalApi public final class CrtCredentialsProviderAdapter implements SdkAutoCloseable { - private final AwsCredentialsProvider credentialsProvider; + private final IdentityProvider credentialsProvider; private final CredentialsProvider crtCredentials; - public CrtCredentialsProviderAdapter(AwsCredentialsProvider credentialsProvider) { + public CrtCredentialsProviderAdapter(IdentityProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; this.crtCredentials = new DelegateCredentialsProvider.DelegateCredentialsProviderBuilder() .withHandler(() -> { @@ -44,18 +46,17 @@ public CrtCredentialsProviderAdapter(AwsCredentialsProvider credentialsProvider) return Credentials.createAnonymousCredentials(); } - AwsCredentials sdkCredentials = credentialsProvider.resolveCredentials(); + AwsCredentialsIdentity sdkCredentials = + CompletableFutureUtils.joinLikeSync(credentialsProvider.resolveIdentity()); byte[] accessKey = sdkCredentials.accessKeyId().getBytes(StandardCharsets.UTF_8); byte[] secreteKey = sdkCredentials.secretAccessKey().getBytes(StandardCharsets.UTF_8); byte[] sessionTokens = null; - if (sdkCredentials instanceof AwsSessionCredentials) { + if (sdkCredentials instanceof AwsSessionCredentialsIdentity) { sessionTokens = - ((AwsSessionCredentials) sdkCredentials).sessionToken().getBytes(StandardCharsets.UTF_8); + ((AwsSessionCredentialsIdentity) sdkCredentials).sessionToken().getBytes(StandardCharsets.UTF_8); } - return new Credentials(accessKey, - secreteKey, - sessionTokens); + return new Credentials(accessKey, secreteKey, sessionTokens); }).build(); } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClient.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClient.java index 0ebc219faf86..6528f8236301 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClient.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClient.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.services.s3.internal.crt; +import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.AUTH_SCHEMES; import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SDK_HTTP_EXECUTION_ATTRIBUTES; import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.HTTP_CHECKSUM; import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.OPERATION_NAME; @@ -28,7 +29,6 @@ import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.annotations.SdkTestInternalApi; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; import software.amazon.awssdk.awscore.AwsRequest; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; @@ -48,6 +48,8 @@ import software.amazon.awssdk.crt.io.ExponentialBackoffRetryOptions; import software.amazon.awssdk.crt.io.StandardRetryOptions; import software.amazon.awssdk.http.SdkHttpExecutionAttributes; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.DelegatingS3AsyncClient; import software.amazon.awssdk.services.s3.S3AsyncClient; @@ -101,7 +103,8 @@ public CompletableFuture copyObject(CopyObjectRequest copyOb private static S3AsyncClient initializeS3AsyncClient(DefaultS3CrtClientBuilder builder) { ClientOverrideConfiguration.Builder overrideConfigurationBuilder = ClientOverrideConfiguration.builder() - // Disable checksum, retry policy and signer because they are handled in crt + // Disable checksum for streaming operations, retry policy and signer because they are + // handled in crt .putAdvancedOption(SdkAdvancedClientOption.SIGNER, new NoOpSigner()) .putExecutionAttribute(SdkExecutionAttribute.HTTP_RESPONSE_CHECKSUM_VALIDATION, ChecksumValidation.FORCE_SKIP) @@ -114,7 +117,8 @@ private static S3AsyncClient initializeS3AsyncClient(DefaultS3CrtClientBuilder b } return S3AsyncClient.builder() - // Disable checksum, it is handled in CRT + // Disable checksum for streaming operations, it is handled in CRT. Checksumming for non-streaming + // operations is still handled in HttpChecksumStage .serviceConfiguration(S3Configuration.builder() .checksumValidationEnabled(false) .build()) @@ -162,7 +166,7 @@ private static S3CrtAsyncHttpClient.Builder initializeS3CrtAsyncHttpClient(Defau public static final class DefaultS3CrtClientBuilder implements S3CrtAsyncClientBuilder { private Long readBufferSizeInBytes; - private AwsCredentialsProvider credentialsProvider; + private IdentityProvider credentialsProvider; private Region region; private Long minimalPartSizeInBytes; private Double targetThroughputInGbps; @@ -178,40 +182,9 @@ public static final class DefaultS3CrtClientBuilder implements S3CrtAsyncClientB private boolean crossRegionAccessEnabled; private Long thresholdInBytes; - public AwsCredentialsProvider credentialsProvider() { - return credentialsProvider; - } - - public Region region() { - return region; - } - - public Long minimumPartSizeInBytes() { - return minimalPartSizeInBytes; - } - - public Double targetThroughputInGbps() { - return targetThroughputInGbps; - } - - public Integer maxConcurrency() { - return maxConcurrency; - } - - public URI endpointOverride() { - return endpointOverride; - } - - public Long readBufferSizeInBytes() { - return readBufferSizeInBytes; - } - - public boolean crossRegionAccessEnabled() { - return crossRegionAccessEnabled; - } - @Override - public S3CrtAsyncClientBuilder credentialsProvider(AwsCredentialsProvider credentialsProvider) { + public S3CrtAsyncClientBuilder credentialsProvider( + IdentityProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; return this; } @@ -310,6 +283,15 @@ public S3CrtAsyncClient build() { } private static final class AttachHttpAttributesExecutionInterceptor implements ExecutionInterceptor { + + @Override + public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + // Hack to disable new SRA path because we still rely on HttpChecksumStage to perform checksum for + // non-streaming operation. + // TODO: remove this once CRT supports checksum for default requests + executionAttributes.putAttribute(AUTH_SCHEMES, null); + } + @Override public void afterMarshalling(Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { @@ -339,7 +321,7 @@ public void afterMarshalling(Context.AfterMarshalling context, private static void disableChecksumForPutAndGet(Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { if (context.request() instanceof PutObjectRequest || context.request() instanceof GetObjectRequest) { - // TODO: is there a better way to disable SDK flexible checksum implementation + // TODO: we can remove this once we are fully on SRA signing AND CRT supports checksum for default requests // Clear HTTP_CHECKSUM and RESOLVED_CHECKSUM_SPECS to disable SDK flexible checksum implementation. executionAttributes.putAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM, null); executionAttributes.putAttribute(SdkInternalExecutionAttribute.RESOLVED_CHECKSUM_SPECS, null); @@ -365,7 +347,7 @@ private static void validateOverrideConfiguration(SdkRequest request) { } // TODO: support request-level credential override - if (overrideConfiguration.credentialsProvider().isPresent()) { + if (overrideConfiguration.credentialsIdentityProvider().isPresent()) { throw new UnsupportedOperationException("Request-level credentials override is not supported"); } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3NativeClientConfiguration.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3NativeClientConfiguration.java index 1f41a81ad949..06ecd4a69658 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3NativeClientConfiguration.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3NativeClientConfiguration.java @@ -21,7 +21,6 @@ import java.net.URI; import java.time.Duration; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.crt.auth.credentials.CredentialsProvider; import software.amazon.awssdk.crt.http.HttpMonitoringOptions; @@ -31,6 +30,8 @@ import software.amazon.awssdk.crt.io.TlsCipherPreference; import software.amazon.awssdk.crt.io.TlsContext; import software.amazon.awssdk.crt.io.TlsContextOptions; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration; import software.amazon.awssdk.utils.Logger; @@ -189,7 +190,7 @@ public void close() { public static final class Builder { private Long readBufferSizeInBytes; private String signingRegion; - private AwsCredentialsProvider credentialsProvider; + private IdentityProvider credentialsProvider; private Long partSizeInBytes; private Double targetThroughputInGbps; private Integer maxConcurrency; @@ -208,7 +209,7 @@ public Builder signingRegion(String signingRegion) { return this; } - public Builder credentialsProvider(AwsCredentialsProvider credentialsProvider) { + public Builder credentialsProvider(IdentityProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; return this; } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/DisablePayloadSigningInterceptor.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/DisablePayloadSigningInterceptor.java new file mode 100644 index 000000000000..0fab4dc487ee --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/DisablePayloadSigningInterceptor.java @@ -0,0 +1,35 @@ +/* + * 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.s3.internal.handlers; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; + +/** + * Disables payload signing for all S3 operations. + * + *

TODO(sra-identity-auth): After S3's migration to the SRA, we should use signer properties in the auth scheme resolver. + */ +@SdkInternalApi +public class DisablePayloadSigningInterceptor implements ExecutionInterceptor { + @Override + public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + executionAttributes.putAttributeIfAbsent(S3SignerExecutionAttribute.ENABLE_PAYLOAD_SIGNING, false); + } +} diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java index ed9fed5f9103..451f870e5987 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java @@ -24,6 +24,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -61,13 +62,18 @@ import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.metrics.NoOpMetricCollector; import software.amazon.awssdk.protocols.xml.AwsS3ProtocolFactory; import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.auth.scheme.S3AuthSchemeProvider; +import software.amazon.awssdk.services.s3.auth.scheme.internal.S3AuthSchemeInterceptor; import software.amazon.awssdk.services.s3.endpoints.S3ClientContextParams; import software.amazon.awssdk.services.s3.endpoints.S3EndpointProvider; -import software.amazon.awssdk.services.s3.endpoints.internal.S3EndpointAuthSchemeInterceptor; import software.amazon.awssdk.services.s3.endpoints.internal.S3RequestSetEndpointInterceptor; import software.amazon.awssdk.services.s3.endpoints.internal.S3ResolveEndpointInterceptor; import software.amazon.awssdk.services.s3.internal.endpoints.UseGlobalEndpointResolver; @@ -203,8 +209,8 @@ private List initializeInterceptors() { List s3Interceptors = interceptorFactory.getInterceptors("software/amazon/awssdk/services/s3/execution.interceptors"); List additionalInterceptors = new ArrayList<>(); + additionalInterceptors.add(new S3AuthSchemeInterceptor()); additionalInterceptors.add(new S3ResolveEndpointInterceptor()); - additionalInterceptors.add(new S3EndpointAuthSchemeInterceptor()); additionalInterceptors.add(new S3RequestSetEndpointInterceptor()); s3Interceptors = mergeLists(s3Interceptors, additionalInterceptors); return mergeLists(interceptorFactory.getGlobalInterceptors(), s3Interceptors); @@ -364,7 +370,13 @@ private ExecutionContext invokeInterceptorsAndCreateExecutionContext(PresignRequ .putAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED, serviceConfiguration.dualstackEnabled()) .putAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER, S3EndpointProvider.defaultProvider()) .putAttribute(AwsExecutionAttribute.USE_GLOBAL_ENDPOINT, useGlobalEndpointResolver.resolve(region())) - .putAttribute(SdkInternalExecutionAttribute.CLIENT_CONTEXT_PARAMS, clientContextParams); + .putAttribute(SdkInternalExecutionAttribute.CLIENT_CONTEXT_PARAMS, clientContextParams) + .putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER, S3AuthSchemeProvider.defaultProvider()) + .putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES, authSchemes()) + .putAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS, + IdentityProviders.builder() + .putIdentityProvider(credentialsProvider()) + .build()); ExecutionInterceptorChain executionInterceptorChain = new ExecutionInterceptorChain(clientInterceptors); @@ -392,6 +404,15 @@ private ExecutionContext invokeInterceptorsAndCreateExecutionContext(PresignRequ .build(); } + private Map> authSchemes() { + Map> schemes = new HashMap<>(2); + AwsV4AuthScheme awsV4AuthScheme = AwsV4AuthScheme.create(); + schemes.put(awsV4AuthScheme.schemeId(), awsV4AuthScheme); + AwsV4aAuthScheme awsV4aAuthScheme = AwsV4aAuthScheme.create(); + schemes.put(awsV4aAuthScheme.schemeId(), awsV4aAuthScheme); + return Collections.unmodifiableMap(schemes); + } + /** * Call the before-marshalling interceptor hooks. */ diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultSdkPresigner.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultSdkPresigner.java index b6df6e29fe7b..921e7101a410 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultSdkPresigner.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultSdkPresigner.java @@ -23,6 +23,8 @@ import software.amazon.awssdk.awscore.endpoint.DualstackEnabledProvider; import software.amazon.awssdk.awscore.endpoint.FipsEnabledProvider; import software.amazon.awssdk.awscore.presigner.SdkPresigner; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileFileSystemSetting; import software.amazon.awssdk.regions.Region; @@ -42,7 +44,7 @@ public abstract class DefaultSdkPresigner implements SdkPresigner { private final String profileName; private final Region region; private final URI endpointOverride; - private final AwsCredentialsProvider credentialsProvider; + private final IdentityProvider credentialsProvider; private final Boolean dualstackEnabled; private final boolean fipsEnabled; @@ -88,7 +90,7 @@ protected Region region() { return region; } - protected AwsCredentialsProvider credentialsProvider() { + protected IdentityProvider credentialsProvider() { return credentialsProvider; } @@ -116,7 +118,7 @@ public void close() { public abstract static class Builder> implements SdkPresigner.Builder { private Region region; - private AwsCredentialsProvider credentialsProvider; + private IdentityProvider credentialsProvider; private Boolean dualstackEnabled; private Boolean fipsEnabled; private URI endpointOverride; @@ -132,6 +134,11 @@ public B region(Region region) { @Override public B credentialsProvider(AwsCredentialsProvider credentialsProvider) { + return credentialsProvider((IdentityProvider) credentialsProvider); + } + + @Override + public B credentialsProvider(IdentityProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; return thisBuilder(); } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/S3Presigner.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/S3Presigner.java index 81a55e7bece1..522a4401e0ca 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/S3Presigner.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/S3Presigner.java @@ -28,6 +28,8 @@ import software.amazon.awssdk.awscore.presigner.SdkPresigner; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; import software.amazon.awssdk.services.s3.S3Configuration; @@ -586,6 +588,9 @@ interface Builder extends SdkPresigner.Builder { @Override Builder credentialsProvider(AwsCredentialsProvider credentialsProvider); + @Override + Builder credentialsProvider(IdentityProvider credentialsProvider); + @Override Builder dualstackEnabled(Boolean dualstackEnabled); diff --git a/services/s3/src/main/resources/codegen-resources/customization.config b/services/s3/src/main/resources/codegen-resources/customization.config index f33272a2a636..02ee8a5b6bb2 100644 --- a/services/s3/src/main/resources/codegen-resources/customization.config +++ b/services/s3/src/main/resources/codegen-resources/customization.config @@ -256,9 +256,11 @@ "software.amazon.awssdk.services.s3.internal.handlers.EnableTrailingChecksumInterceptor", "software.amazon.awssdk.services.s3.internal.handlers.ExceptionTranslationInterceptor", "software.amazon.awssdk.services.s3.internal.handlers.GetObjectInterceptor", - "software.amazon.awssdk.services.s3.internal.handlers.CopySourceInterceptor" + "software.amazon.awssdk.services.s3.internal.handlers.CopySourceInterceptor", + "software.amazon.awssdk.services.s3.internal.handlers.DisablePayloadSigningInterceptor" ], "requiredTraitValidationEnabled": true, + "enableEndpointAuthSchemeParams": true, "customClientContextParams":{ "CrossRegionAccessEnabled":{ "documentation":"Enables cross-region bucket access for this client", diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/PayloadSigningDisabledTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/PayloadSigningDisabledTest.java new file mode 100644 index 000000000000..0ac8d0eec1bb --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/PayloadSigningDisabledTest.java @@ -0,0 +1,118 @@ +/* + * 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.s3; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; + +/** + * Ensure that payload signing is disabled for S3 operations. + */ +public class PayloadSigningDisabledTest { + private static final AwsCredentialsProvider CREDENTIALS = () -> AwsBasicCredentials.create("akid", "skid"); + private static final ClientOverrideConfiguration ENABLE_PAYLOAD_SIGNING_CONFIG = + ClientOverrideConfiguration.builder() + .putExecutionAttribute(S3SignerExecutionAttribute.ENABLE_PAYLOAD_SIGNING, true) + .build(); + + @Test + public void syncPayloadSigningIsDisabled() { + try (MockSyncHttpClient httpClient = new MockSyncHttpClient(); + S3Client s3 = S3Client.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS) + .httpClient(httpClient) + .build()) { + httpClient.stubNextResponse(HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .build()); + + s3.createBucket(r -> r.bucket("foo")); + + assertThat(httpClient.getLastRequest().firstMatchingHeader("x-amz-content-sha256")) + .hasValue("UNSIGNED-PAYLOAD"); + } + } + + @Test + public void asyncPayloadSigningIsDisabled() { + try (MockAsyncHttpClient httpClient = new MockAsyncHttpClient(); + S3AsyncClient s3 = S3AsyncClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS) + .httpClient(httpClient) + .build()) { + httpClient.stubNextResponse(HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .build()); + + s3.createBucket(r -> r.bucket("foo")).join(); + + assertThat(httpClient.getLastRequest().firstMatchingHeader("x-amz-content-sha256")) + .hasValue("UNSIGNED-PAYLOAD"); + } + } + + @Test + public void syncPayloadSigningCanBeEnabled() { + try (MockSyncHttpClient httpClient = new MockSyncHttpClient(); + S3Client s3 = S3Client.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS) + .httpClient(httpClient) + .overrideConfiguration(ENABLE_PAYLOAD_SIGNING_CONFIG) + .build()) { + httpClient.stubNextResponse(HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .build()); + + s3.createBucket(r -> r.bucket("foo")); + + assertThat(httpClient.getLastRequest().firstMatchingHeader("x-amz-content-sha256")) + .hasValue("a40ef303139635de59992f34c1c7da763f89200f2d55b71016f7c156527d63a0"); + } + } + + @Test + public void asyncPayloadSigningCanBeEnabled() { + try (MockAsyncHttpClient httpClient = new MockAsyncHttpClient(); + S3AsyncClient s3 = S3AsyncClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS) + .httpClient(httpClient) + .overrideConfiguration(ENABLE_PAYLOAD_SIGNING_CONFIG) + .build()) { + httpClient.stubNextResponse(HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .build()); + + s3.createBucket(r -> r.bucket("foo")).join(); + + assertThat(httpClient.getLastRequest().firstMatchingHeader("x-amz-content-sha256")) + .hasValue("a40ef303139635de59992f34c1c7da763f89200f2d55b71016f7c156527d63a0"); + } + } +} diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/CrtCredentialProviderAdapterTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/CrtCredentialProviderAdapterTest.java index a4da9d11f18b..ecdaeec905a7 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/CrtCredentialProviderAdapterTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/CrtCredentialProviderAdapterTest.java @@ -20,24 +20,25 @@ import static org.mockito.Mockito.when; import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; import software.amazon.awssdk.auth.credentials.HttpCredentialsProvider; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.crt.auth.credentials.Credentials; import software.amazon.awssdk.crt.auth.credentials.CredentialsProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.utils.SdkAutoCloseable; public class CrtCredentialProviderAdapterTest { @Test void crtCredentials_withSession_shouldConvert() { - AwsCredentialsProvider awsCredentialsProvider = StaticCredentialsProvider + IdentityProvider awsCredentialsProvider = StaticCredentialsProvider .create(AwsSessionCredentials.create("foo", "bar", "session")); CredentialsProvider crtCredentialsProvider = new CrtCredentialsProviderAdapter(awsCredentialsProvider) @@ -52,7 +53,7 @@ void crtCredentials_withSession_shouldConvert() { @Test void crtCredentials_withoutSession_shouldConvert() { - AwsCredentialsProvider awsCredentialsProvider = StaticCredentialsProvider + IdentityProvider awsCredentialsProvider = StaticCredentialsProvider .create(AwsBasicCredentials.create("foo", "bar")); CredentialsProvider crtCredentialsProvider = new CrtCredentialsProviderAdapter(awsCredentialsProvider) @@ -67,19 +68,9 @@ void crtCredentials_withoutSession_shouldConvert() { @Test void crtCredentials_provideAwsCredentials_shouldInvokeResolveAndClose() { - AwsCredentialsProvider awsCredentialsProvider = Mockito.mock(HttpCredentialsProvider.class); - AwsCredentials credentials = new AwsCredentials() { - @Override - public String accessKeyId() { - return "foo"; - } - - @Override - public String secretAccessKey() { - return "bar"; - } - }; - when(awsCredentialsProvider.resolveCredentials()).thenReturn(credentials); + IdentityProvider awsCredentialsProvider = Mockito.mock(HttpCredentialsProvider.class); + AwsCredentialsIdentity credentials = AwsCredentialsIdentity.create("foo", "bar"); + when(awsCredentialsProvider.resolveIdentity()).thenAnswer(invocation -> CompletableFuture.completedFuture(credentials)); CrtCredentialsProviderAdapter adapter = new CrtCredentialsProviderAdapter(awsCredentialsProvider); CredentialsProvider crtCredentialsProvider = adapter.crtCredentials(); @@ -87,7 +78,7 @@ public String secretAccessKey() { Credentials crtCredentials = crtCredentialsProvider.getCredentials().join(); assertThat(crtCredentials.getAccessKeyId()).isEqualTo("foo".getBytes(StandardCharsets.UTF_8)); assertThat(crtCredentials.getSecretAccessKey()).isEqualTo("bar".getBytes(StandardCharsets.UTF_8)); - verify(awsCredentialsProvider).resolveCredentials(); + verify(awsCredentialsProvider).resolveIdentity(); adapter.close(); verify((SdkAutoCloseable) awsCredentialsProvider).close(); @@ -95,7 +86,7 @@ public String secretAccessKey() { @Test void crtCredentials_anonymousCredentialsProvider_shouldWork() { - AwsCredentialsProvider awsCredentialsProvider = AnonymousCredentialsProvider.create(); + IdentityProvider awsCredentialsProvider = AnonymousCredentialsProvider.create(); CrtCredentialsProviderAdapter adapter = new CrtCredentialsProviderAdapter(awsCredentialsProvider); CredentialsProvider crtCredentialsProvider = adapter.crtCredentials(); @@ -104,9 +95,5 @@ void crtCredentials_anonymousCredentialsProvider_shouldWork() { assertThat(crtCredentials.getAccessKeyId()).isNull(); assertThat(crtCredentials.getSecretAccessKey()).isNull(); - } - - - } diff --git a/services/s3control/pom.xml b/services/s3control/pom.xml index 48dbfe3097c9..9360c7ea656b 100644 --- a/services/s3control/pom.xml +++ b/services/s3control/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT s3control AWS Java SDK :: Services :: Amazon S3 Control @@ -94,5 +94,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/s3outposts/pom.xml b/services/s3outposts/pom.xml index b1447047e23f..e929a6618d3d 100644 --- a/services/s3outposts/pom.xml +++ b/services/s3outposts/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT s3outposts AWS Java SDK :: Services :: S3 Outposts @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sagemaker/pom.xml b/services/sagemaker/pom.xml index fd284032827e..15ab5064240d 100644 --- a/services/sagemaker/pom.xml +++ b/services/sagemaker/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 sagemaker @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sagemakera2iruntime/pom.xml b/services/sagemakera2iruntime/pom.xml index 387161a69b8c..562559637bd1 100644 --- a/services/sagemakera2iruntime/pom.xml +++ b/services/sagemakera2iruntime/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sagemakera2iruntime AWS Java SDK :: Services :: SageMaker A2I Runtime @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sagemakeredge/pom.xml b/services/sagemakeredge/pom.xml index fd553eae78b9..d1bad02f34a1 100644 --- a/services/sagemakeredge/pom.xml +++ b/services/sagemakeredge/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sagemakeredge AWS Java SDK :: Services :: Sagemaker Edge @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sagemakerfeaturestoreruntime/pom.xml b/services/sagemakerfeaturestoreruntime/pom.xml index c8a89fbf65f0..00b21eda0b7e 100644 --- a/services/sagemakerfeaturestoreruntime/pom.xml +++ b/services/sagemakerfeaturestoreruntime/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sagemakerfeaturestoreruntime AWS Java SDK :: Services :: Sage Maker Feature Store Runtime @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sagemakergeospatial/pom.xml b/services/sagemakergeospatial/pom.xml index f86d37af1ddf..06a79086bd8a 100644 --- a/services/sagemakergeospatial/pom.xml +++ b/services/sagemakergeospatial/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sagemakergeospatial AWS Java SDK :: Services :: Sage Maker Geospatial @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sagemakermetrics/pom.xml b/services/sagemakermetrics/pom.xml index acebbd9ffedc..12ceb5d63f60 100644 --- a/services/sagemakermetrics/pom.xml +++ b/services/sagemakermetrics/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sagemakermetrics AWS Java SDK :: Services :: Sage Maker Metrics @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sagemakerruntime/pom.xml b/services/sagemakerruntime/pom.xml index 9f1988758f38..c1786be82be5 100644 --- a/services/sagemakerruntime/pom.xml +++ b/services/sagemakerruntime/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sagemakerruntime AWS Java SDK :: Services :: SageMaker Runtime @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/savingsplans/pom.xml b/services/savingsplans/pom.xml index 525534642c08..4391a5aa9014 100644 --- a/services/savingsplans/pom.xml +++ b/services/savingsplans/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT savingsplans AWS Java SDK :: Services :: Savingsplans @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/scheduler/pom.xml b/services/scheduler/pom.xml index 53fb322bf3d4..4957ed88f8f2 100644 --- a/services/scheduler/pom.xml +++ b/services/scheduler/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT scheduler AWS Java SDK :: Services :: Scheduler @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/schemas/pom.xml b/services/schemas/pom.xml index dffe1451c4d2..45c1ee4fd7fb 100644 --- a/services/schemas/pom.xml +++ b/services/schemas/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT schemas AWS Java SDK :: Services :: Schemas @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/secretsmanager/pom.xml b/services/secretsmanager/pom.xml index d72b202babf0..708e462e084d 100644 --- a/services/secretsmanager/pom.xml +++ b/services/secretsmanager/pom.xml @@ -22,7 +22,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT secretsmanager AWS Java SDK :: Services :: AWS Secrets Manager @@ -58,6 +58,11 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/securityhub/pom.xml b/services/securityhub/pom.xml index 9df15be90c04..732c375d7840 100644 --- a/services/securityhub/pom.xml +++ b/services/securityhub/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT securityhub AWS Java SDK :: Services :: SecurityHub @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/securitylake/pom.xml b/services/securitylake/pom.xml index 77cb5cc4b0ed..f198e6b87c74 100644 --- a/services/securitylake/pom.xml +++ b/services/securitylake/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT securitylake AWS Java SDK :: Services :: Security Lake @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/serverlessapplicationrepository/pom.xml b/services/serverlessapplicationrepository/pom.xml index 160da7e3ab5c..e3b5d592b036 100644 --- a/services/serverlessapplicationrepository/pom.xml +++ b/services/serverlessapplicationrepository/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 serverlessapplicationrepository @@ -58,5 +58,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/servicecatalog/pom.xml b/services/servicecatalog/pom.xml index bfac999e8d17..9de2cbf041ce 100644 --- a/services/servicecatalog/pom.xml +++ b/services/servicecatalog/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT servicecatalog AWS Java SDK :: Services :: AWS Service Catalog @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/servicecatalogappregistry/pom.xml b/services/servicecatalogappregistry/pom.xml index 8325f3d39029..0caa3ec593c6 100644 --- a/services/servicecatalogappregistry/pom.xml +++ b/services/servicecatalogappregistry/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT servicecatalogappregistry AWS Java SDK :: Services :: Service Catalog App Registry @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/servicediscovery/pom.xml b/services/servicediscovery/pom.xml index 0d52859fca94..e624a36fe345 100644 --- a/services/servicediscovery/pom.xml +++ b/services/servicediscovery/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 servicediscovery @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/servicequotas/pom.xml b/services/servicequotas/pom.xml index 2fa54e0592f6..6bd6ad3ff0c0 100644 --- a/services/servicequotas/pom.xml +++ b/services/servicequotas/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT servicequotas AWS Java SDK :: Services :: Service Quotas @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ses/pom.xml b/services/ses/pom.xml index 712f1567f33f..04f5c640ccb6 100644 --- a/services/ses/pom.xml +++ b/services/ses/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ses AWS Java SDK :: Services :: Amazon SES @@ -62,5 +62,10 @@ commons-io test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sesv2/pom.xml b/services/sesv2/pom.xml index ebf701050e46..21efbe2efacb 100644 --- a/services/sesv2/pom.xml +++ b/services/sesv2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sesv2 AWS Java SDK :: Services :: SESv2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sfn/pom.xml b/services/sfn/pom.xml index 76ccb8998626..dcbbb8326476 100644 --- a/services/sfn/pom.xml +++ b/services/sfn/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sfn AWS Java SDK :: Services :: AWS Step Functions @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/shield/pom.xml b/services/shield/pom.xml index 236d04fa1d24..30cb1db944f1 100644 --- a/services/shield/pom.xml +++ b/services/shield/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT shield AWS Java SDK :: Services :: AWS Shield @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/signer/pom.xml b/services/signer/pom.xml index 79eca199087e..8dbcca6f61c9 100644 --- a/services/signer/pom.xml +++ b/services/signer/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT signer AWS Java SDK :: Services :: Signer @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/simspaceweaver/pom.xml b/services/simspaceweaver/pom.xml index 8d9f08f0c24e..e8e1cc825a28 100644 --- a/services/simspaceweaver/pom.xml +++ b/services/simspaceweaver/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT simspaceweaver AWS Java SDK :: Services :: Sim Space Weaver @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sms/pom.xml b/services/sms/pom.xml index 18367a8993f2..fc751aba02e1 100644 --- a/services/sms/pom.xml +++ b/services/sms/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sms AWS Java SDK :: Services :: AWS Server Migration @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/snowball/pom.xml b/services/snowball/pom.xml index 16eb3e8272f8..c678c88c377f 100644 --- a/services/snowball/pom.xml +++ b/services/snowball/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT snowball AWS Java SDK :: Services :: Amazon Snowball @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/snowdevicemanagement/pom.xml b/services/snowdevicemanagement/pom.xml index 77893c0b36e5..7b4283f296ff 100644 --- a/services/snowdevicemanagement/pom.xml +++ b/services/snowdevicemanagement/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT snowdevicemanagement AWS Java SDK :: Services :: Snow Device Management @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sns/pom.xml b/services/sns/pom.xml index eacc4dcfca63..3594c6d6f2e4 100644 --- a/services/sns/pom.xml +++ b/services/sns/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sns AWS Java SDK :: Services :: Amazon SNS @@ -62,5 +62,10 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sqs/pom.xml b/services/sqs/pom.xml index 9ac45b7e2b74..3e284b3b034c 100644 --- a/services/sqs/pom.xml +++ b/services/sqs/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sqs AWS Java SDK :: Services :: Amazon SQS @@ -80,5 +80,10 @@ ${assertj.version} test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ssm/pom.xml b/services/ssm/pom.xml index 7a5cf30a5f87..0d30dd7acb84 100644 --- a/services/ssm/pom.xml +++ b/services/ssm/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ssm AWS Java SDK :: Services :: AWS Simple Systems Management (SSM) @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ssmcontacts/pom.xml b/services/ssmcontacts/pom.xml index 096cc9c22488..ca4e2c434163 100644 --- a/services/ssmcontacts/pom.xml +++ b/services/ssmcontacts/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ssmcontacts AWS Java SDK :: Services :: SSM Contacts @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ssmincidents/pom.xml b/services/ssmincidents/pom.xml index 63707c812db2..346520079546 100644 --- a/services/ssmincidents/pom.xml +++ b/services/ssmincidents/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ssmincidents AWS Java SDK :: Services :: SSM Incidents @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ssmsap/pom.xml b/services/ssmsap/pom.xml index 9f1081e9a39f..19bf8a4537bc 100644 --- a/services/ssmsap/pom.xml +++ b/services/ssmsap/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ssmsap AWS Java SDK :: Services :: Ssm Sap @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sso/pom.xml b/services/sso/pom.xml index a5010373a0de..4e30770cb370 100644 --- a/services/sso/pom.xml +++ b/services/sso/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sso AWS Java SDK :: Services :: SSO @@ -84,5 +84,10 @@ mockito-junit-jupiter test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ssoadmin/pom.xml b/services/ssoadmin/pom.xml index d47a2325c54a..54009aab1952 100644 --- a/services/ssoadmin/pom.xml +++ b/services/ssoadmin/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ssoadmin AWS Java SDK :: Services :: SSO Admin @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/ssooidc/pom.xml b/services/ssooidc/pom.xml index 61dc867ae0c1..c141d117b579 100644 --- a/services/ssooidc/pom.xml +++ b/services/ssooidc/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ssooidc AWS Java SDK :: Services :: SSO OIDC @@ -92,5 +92,10 @@ commons-lang3 test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/storagegateway/pom.xml b/services/storagegateway/pom.xml index f45e3ba3c6cf..cfa7412530d0 100644 --- a/services/storagegateway/pom.xml +++ b/services/storagegateway/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT storagegateway AWS Java SDK :: Services :: AWS Storage Gateway @@ -58,5 +58,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/sts/pom.xml b/services/sts/pom.xml index 7810c11fadc5..416a990898ca 100644 --- a/services/sts/pom.xml +++ b/services/sts/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT sts AWS Java SDK :: Services :: AWS STS @@ -89,5 +89,10 @@ junit-vintage-engine test + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/support/pom.xml b/services/support/pom.xml index c5fde9cf223b..9fdf68b83db2 100644 --- a/services/support/pom.xml +++ b/services/support/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT support AWS Java SDK :: Services :: AWS Support @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/supportapp/pom.xml b/services/supportapp/pom.xml index c2b08b7466a8..7bb1409b3dc3 100644 --- a/services/supportapp/pom.xml +++ b/services/supportapp/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT supportapp AWS Java SDK :: Services :: Support App @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/swf/pom.xml b/services/swf/pom.xml index 46e9f47eb93f..af9d43830562 100644 --- a/services/swf/pom.xml +++ b/services/swf/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT swf AWS Java SDK :: Services :: Amazon SWF @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/synthetics/pom.xml b/services/synthetics/pom.xml index eb2350503156..c221d7eacdf2 100644 --- a/services/synthetics/pom.xml +++ b/services/synthetics/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT synthetics AWS Java SDK :: Services :: Synthetics @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/textract/pom.xml b/services/textract/pom.xml index 1f37e83c2fb0..d55620edf996 100644 --- a/services/textract/pom.xml +++ b/services/textract/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT textract AWS Java SDK :: Services :: Textract @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/timestreamquery/pom.xml b/services/timestreamquery/pom.xml index 7dbe6b6ce04c..1147ccf813aa 100644 --- a/services/timestreamquery/pom.xml +++ b/services/timestreamquery/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT timestreamquery AWS Java SDK :: Services :: Timestream Query @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/timestreamwrite/pom.xml b/services/timestreamwrite/pom.xml index 09d26c455c99..c3b6a0143862 100644 --- a/services/timestreamwrite/pom.xml +++ b/services/timestreamwrite/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT timestreamwrite AWS Java SDK :: Services :: Timestream Write @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/tnb/pom.xml b/services/tnb/pom.xml index 1ce73485f297..673bb520456d 100644 --- a/services/tnb/pom.xml +++ b/services/tnb/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT tnb AWS Java SDK :: Services :: Tnb @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/transcribe/pom.xml b/services/transcribe/pom.xml index 862619786977..be513cb29f40 100644 --- a/services/transcribe/pom.xml +++ b/services/transcribe/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT transcribe AWS Java SDK :: Services :: Transcribe @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/transcribestreaming/pom.xml b/services/transcribestreaming/pom.xml index 833f0de14c55..27eff6706de4 100644 --- a/services/transcribestreaming/pom.xml +++ b/services/transcribestreaming/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT transcribestreaming AWS Java SDK :: Services :: AWS Transcribe Streaming @@ -43,6 +43,23 @@ + + + org.apache.maven.plugins + maven-dependency-plugin + + + + software.amazon.awssdk:http-auth-aws-eventstream + + + + software.amazon.awssdk:http-auth + + + @@ -57,5 +74,15 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-aws-eventstream + ${awsjavasdk.version} + diff --git a/services/transfer/pom.xml b/services/transfer/pom.xml index 2606628276d2..4f559e8fa2b9 100644 --- a/services/transfer/pom.xml +++ b/services/transfer/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT transfer AWS Java SDK :: Services :: Transfer @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/translate/pom.xml b/services/translate/pom.xml index 52d201968cea..0eed3c8fc3a0 100644 --- a/services/translate/pom.xml +++ b/services/translate/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 translate @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/verifiedpermissions/pom.xml b/services/verifiedpermissions/pom.xml index c04f56badb9d..d7651eec23f3 100644 --- a/services/verifiedpermissions/pom.xml +++ b/services/verifiedpermissions/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT verifiedpermissions AWS Java SDK :: Services :: Verified Permissions @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/voiceid/pom.xml b/services/voiceid/pom.xml index 97c3aa2a8e95..8888e6abb593 100644 --- a/services/voiceid/pom.xml +++ b/services/voiceid/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT voiceid AWS Java SDK :: Services :: Voice ID @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/vpclattice/pom.xml b/services/vpclattice/pom.xml index 2657a16f549f..0eaebd362638 100644 --- a/services/vpclattice/pom.xml +++ b/services/vpclattice/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT vpclattice AWS Java SDK :: Services :: VPC Lattice @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/waf/pom.xml b/services/waf/pom.xml index 50a27b570787..aa05cb7108e1 100644 --- a/services/waf/pom.xml +++ b/services/waf/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT waf AWS Java SDK :: Services :: AWS WAF @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/wafv2/pom.xml b/services/wafv2/pom.xml index 545972a6ee86..297ab7a09dc5 100644 --- a/services/wafv2/pom.xml +++ b/services/wafv2/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT wafv2 AWS Java SDK :: Services :: WAFV2 @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/wellarchitected/pom.xml b/services/wellarchitected/pom.xml index 943f404b7323..03f08dd8213e 100644 --- a/services/wellarchitected/pom.xml +++ b/services/wellarchitected/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT wellarchitected AWS Java SDK :: Services :: Well Architected @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/wisdom/pom.xml b/services/wisdom/pom.xml index 11b76acd901b..694769657fca 100644 --- a/services/wisdom/pom.xml +++ b/services/wisdom/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT wisdom AWS Java SDK :: Services :: Wisdom @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/workdocs/pom.xml b/services/workdocs/pom.xml index 234f70836725..c3cdaec866a1 100644 --- a/services/workdocs/pom.xml +++ b/services/workdocs/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT workdocs AWS Java SDK :: Services :: Amazon WorkDocs @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/worklink/pom.xml b/services/worklink/pom.xml index 9ecf4d6822fb..a5e71f4546cc 100644 --- a/services/worklink/pom.xml +++ b/services/worklink/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT worklink AWS Java SDK :: Services :: WorkLink @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/workmail/pom.xml b/services/workmail/pom.xml index a541d02fe601..b9afa7dfe5bb 100644 --- a/services/workmail/pom.xml +++ b/services/workmail/pom.xml @@ -20,7 +20,7 @@ services software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 workmail @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/workmailmessageflow/pom.xml b/services/workmailmessageflow/pom.xml index 444c943b0deb..6b19a0358f4a 100644 --- a/services/workmailmessageflow/pom.xml +++ b/services/workmailmessageflow/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT workmailmessageflow AWS Java SDK :: Services :: WorkMailMessageFlow @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/workspaces/pom.xml b/services/workspaces/pom.xml index 81ac65056956..a083e8681e6b 100644 --- a/services/workspaces/pom.xml +++ b/services/workspaces/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT workspaces AWS Java SDK :: Services :: Amazon WorkSpaces @@ -57,5 +57,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/workspacesweb/pom.xml b/services/workspacesweb/pom.xml index e2e7215df501..4aecfaa6e795 100644 --- a/services/workspacesweb/pom.xml +++ b/services/workspacesweb/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT workspacesweb AWS Java SDK :: Services :: Work Spaces Web @@ -56,5 +56,10 @@ aws-json-protocol ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/services/xray/pom.xml b/services/xray/pom.xml index d49fad391f7a..e92a616ba45c 100644 --- a/services/xray/pom.xml +++ b/services/xray/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk services - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT xray AWS Java SDK :: Services :: AWS X-Ray @@ -56,5 +56,10 @@ protocol-core ${awsjavasdk.version} + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + diff --git a/test/auth-tests/pom.xml b/test/auth-tests/pom.xml index d63a58552862..14af9f42ec38 100644 --- a/test/auth-tests/pom.xml +++ b/test/auth-tests/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/test/codegen-generated-classes-test/pom.xml b/test/codegen-generated-classes-test/pom.xml index 6a6bf8945706..69e1c734a5de 100644 --- a/test/codegen-generated-classes-test/pom.xml +++ b/test/codegen-generated-classes-test/pom.xml @@ -21,7 +21,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml @@ -95,6 +95,26 @@ auth ${awsjavasdk.version} + + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + software.amazon.awssdk regions @@ -226,6 +246,12 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws-eventstream + ${awsjavasdk.version} + test + diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/query/customization.config b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/query/customization.config index b5c73436bb3f..1b02b0cb4f5c 100644 --- a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/query/customization.config +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/query/customization.config @@ -1,3 +1,4 @@ { - "skipEndpointTestGeneration": true + "skipEndpointTestGeneration": true, + "useSraAuth": true } \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncRequestBodyFlexibleChecksumInTrailerTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncRequestBodyFlexibleChecksumInTrailerTest.java index 3b7b815e3209..d419b062fa1d 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncRequestBodyFlexibleChecksumInTrailerTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncRequestBodyFlexibleChecksumInTrailerTest.java @@ -26,6 +26,8 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute.ENABLE_CHUNKED_ENCODING; +import static software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute.ENABLE_PAYLOAD_SIGNING; import static software.amazon.awssdk.http.Header.CONTENT_LENGTH; import static software.amazon.awssdk.http.Header.CONTENT_TYPE; @@ -84,12 +86,24 @@ public void setupClient() { .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid"))) .region(Region.US_EAST_1) .endpointOverride(URI.create("http://localhost:" + wireMock.port())) + .overrideConfiguration( + // TODO(sra-identity-and-auth): we should remove these + // overrides once we set up codegen to set chunk-encoding to true + // for requests that are streaming and checksum-enabled + o -> o.putExecutionAttribute(ENABLE_CHUNKED_ENCODING, true) + .putExecutionAttribute(ENABLE_PAYLOAD_SIGNING, false)) .build(); asyncClient = ProtocolRestJsonAsyncClient.builder() .credentialsProvider(AnonymousCredentialsProvider.create()) .region(Region.US_EAST_1) .endpointOverride(URI.create("http://localhost:" + wireMock.port())) + .overrideConfiguration( + // TODO(sra-identity-and-auth): we should remove these + // overrides once we set up codegen to set chunk-encoding to true + // for requests that are streaming and checksum-enabled + o -> o.putExecutionAttribute(ENABLE_CHUNKED_ENCODING, true) + .putExecutionAttribute(ENABLE_PAYLOAD_SIGNING, false)) .build(); } @@ -259,4 +273,4 @@ private void verifyHeadersForPutRequest(String contentLength, String decodedCont } -} \ No newline at end of file +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncSignerOverrideTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncSignerOverrideTest.java index 24db96223a66..f1d6b9257d02 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncSignerOverrideTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncSignerOverrideTest.java @@ -18,6 +18,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.SIGNER; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -57,4 +58,9 @@ public void test_signerOverriddenForStreamingInput_takesPrecedence() { verify(mockSigner).sign(any(SdkHttpFullRequest.class), any(ExecutionAttributes.class)); } + + // TODO(sra-identity-and-auth): Add test for SRA way of overriding signer to assert that overridden signer is used. + // At that point, rename this class to SignerOverrideTest, not specific to AsyncSignerOverride (which was for operation + // level codegen changes). + } diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/CodegenServiceClientConfigurationTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/CodegenServiceClientConfigurationTest.java new file mode 100644 index 000000000000..dec91ee2a147 --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/CodegenServiceClientConfigurationTest.java @@ -0,0 +1,217 @@ +/* + * 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; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.BiConsumer; +import java.util.function.Function; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.awssdk.awscore.AwsServiceClientConfiguration; +import software.amazon.awssdk.awscore.client.config.AwsClientOption; +import software.amazon.awssdk.core.client.config.ClientOption; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonServiceClientConfiguration; +import software.amazon.awssdk.services.protocolrestjson.auth.scheme.ProtocolRestJsonAuthSchemeProvider; +import software.amazon.awssdk.services.protocolrestjson.internal.ProtocolRestJsonServiceClientConfigurationBuilder; + +public class CodegenServiceClientConfigurationTest { + private static final EndpointProvider MOCK_ENDPOINT_PROVIDER = mock(EndpointProvider.class); + private static final IdentityProvider MOCK_IDENTITY_PROVIDER = mock(IdentityProvider.class); + private static final ProtocolRestJsonAuthSchemeProvider MOCK_AUTH_SCHEME_PROVIDER = + mock(ProtocolRestJsonAuthSchemeProvider.class); + private static final ScheduledExecutorService MOCK_SCHEDULED_EXECUTOR_SERVICE = mock(ScheduledExecutorService.class); + private static final Signer MOCK_SIGNER = mock(Signer.class); + + @ParameterizedTest + @MethodSource("testCases") + void externalInternalTransforms_preserves_propertyValues(TestCase testCase) { + ProtocolRestJsonServiceClientConfigurationBuilder.BuilderInternal builder = + ProtocolRestJsonServiceClientConfigurationBuilder.builder(SdkClientConfiguration.builder()); + + // Verify that initially the value is null for properties with direct mapping. + if (testCase.hasDirectMapping) { + assertThat(testCase.getter.apply(builder)).isNull(); + } + + // Set the value + testCase.setter.accept(builder, testCase.value); + + // Assert that we can retrieve the same value + assertThat(testCase.getter.apply(builder)).isEqualTo(testCase.value); + + // Build the ServiceConfiguration instance + ProtocolRestJsonServiceClientConfiguration config = builder.build(); + + // Assert that we can retrieve the same value + assertThat(testCase.dataGetter.apply(config)).isEqualTo(testCase.value); + + // Validate round trip + SdkClientConfiguration clientConfig = builder.buildSdkClientConfiguration(); + + // Build a new builder with the created client config + ProtocolRestJsonServiceClientConfigurationBuilder.BuilderInternal anotherBuilder = + ProtocolRestJsonServiceClientConfigurationBuilder.builder(clientConfig.toBuilder()); + + // Assert that we can retrieve the same value + if (testCase.hasDirectMapping) { + assertThat(testCase.getter.apply(anotherBuilder)).isEqualTo(testCase.value); + } + } + + public static List> testCases() throws Exception { + return Arrays.asList( + TestCase.builder() + .option(AwsClientOption.AWS_REGION) + .value(Region.US_WEST_2) + .setter(ProtocolRestJsonServiceClientConfiguration.Builder::region) + .getter(ProtocolRestJsonServiceClientConfiguration.Builder::region) + .dataGetter(AwsServiceClientConfiguration::region) + .build(), + TestCase.builder() + .option(SdkClientOption.ENDPOINT) + .value(new URI("http://localhost:8080")) + .setter(ProtocolRestJsonServiceClientConfiguration.Builder::endpointOverride) + .getter(ProtocolRestJsonServiceClientConfiguration.Builder::endpointOverride) + .dataGetter(x -> x.endpointOverride().orElse(null)) + .build(), + TestCase.builder() + .option(SdkClientOption.ENDPOINT_PROVIDER) + .value(MOCK_ENDPOINT_PROVIDER) + .setter(ProtocolRestJsonServiceClientConfiguration.Builder::endpointProvider) + .getter(ProtocolRestJsonServiceClientConfiguration.Builder::endpointProvider) + .dataGetter(x -> x.endpointProvider().orElse(null)) + .build(), + TestCase.>builder() + .option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER) + .value(MOCK_IDENTITY_PROVIDER) + .setter(ProtocolRestJsonServiceClientConfiguration.Builder::credentialsProvider) + .getter(ProtocolRestJsonServiceClientConfiguration.Builder::credentialsProvider) + .dataGetter(AwsServiceClientConfiguration::credentialsProvider) + .build(), + TestCase.builder() + .option(SdkClientOption.AUTH_SCHEME_PROVIDER) + .value(MOCK_AUTH_SCHEME_PROVIDER) + .setter((b, p) -> b.authSchemeProvider((ProtocolRestJsonAuthSchemeProvider) p)) + .getter(ProtocolRestJsonServiceClientConfiguration.Builder::authSchemeProvider) + .dataGetter(ProtocolRestJsonServiceClientConfiguration::authSchemeProvider) + .build(), + // Override configuration gets tricky + TestCase.builder() + .option(SdkClientOption.SCHEDULED_EXECUTOR_SERVICE) + .value(MOCK_SCHEDULED_EXECUTOR_SERVICE) + .setter((b, p) -> b.overrideConfiguration( + ClientOverrideConfiguration.builder() + .scheduledExecutorService(p) + .build())) + .getter(b -> b.overrideConfiguration().scheduledExecutorService().orElse(null)) + .dataGetter(d -> d.overrideConfiguration().scheduledExecutorService().orElse(null)) + .withoutDirectMapping() + .build(), + TestCase.builder() + .option(SdkAdvancedClientOption.SIGNER) + .value(MOCK_SIGNER) + .setter((b, p) -> b.overrideConfiguration( + ClientOverrideConfiguration.builder() + .putAdvancedOption(SdkAdvancedClientOption.SIGNER, p) + .build())) + .getter(b -> b.overrideConfiguration().advancedOption(SdkAdvancedClientOption.SIGNER).orElse(null)) + .dataGetter(d -> d.overrideConfiguration().advancedOption(SdkAdvancedClientOption.SIGNER).orElse(null)) + .withoutDirectMapping() + .build() + ); + } + + static class TestCase { + private final ClientOption option; + private final T value; + private final BiConsumer setter; + private final Function getter; + private Function dataGetter; + private final boolean hasDirectMapping; + + public TestCase(Builder builder) { + this.option = builder.option; + this.value = builder.value; + this.setter = builder.setter; + this.getter = builder.getter; + this.dataGetter = builder.dataGetter; + this.hasDirectMapping = builder.hasDirectMapping; + } + + public static Builder builder() { + return new Builder(); + } + + static class Builder { + private ClientOption option; + private T value; + private BiConsumer setter; + private Function getter; + private Function dataGetter; + private boolean hasDirectMapping = true; + + Builder option(ClientOption option) { + this.option = option; + return this; + } + + Builder value(T value) { + this.value = value; + return this; + } + + Builder setter(BiConsumer setter) { + this.setter = setter; + return this; + } + + Builder getter(Function getter) { + this.getter = getter; + return this; + } + + Builder dataGetter(Function dataGetter) { + this.dataGetter = dataGetter; + return this; + } + + Builder withoutDirectMapping() { + this.hasDirectMapping = false; + return this; + } + + TestCase build() { + return new TestCase<>(this); + } + } + } +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HttpChecksumInHeaderTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HttpChecksumInHeaderTest.java index 8ba47dee79cf..c8028f2c6335 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HttpChecksumInHeaderTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HttpChecksumInHeaderTest.java @@ -21,9 +21,6 @@ import io.reactivex.Flowable; import java.io.IOException; import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -106,8 +103,6 @@ public void sync_json_nonStreaming_unsignedPayload_with_Sha1_in_header() { assertThat(getSyncRequest().firstMatchingHeader("Content-MD5")).isNotPresent(); //Note that content will be of form "{"stringMember":"Hello world"}" assertThat(getSyncRequest().firstMatchingHeader("x-amz-checksum-sha1")).hasValue("M68rRwFal7o7B3KEMt3m0w39TaA="); - // Assertion to make sure signer was not executed - assertThat(getSyncRequest().firstMatchingHeader("x-amz-content-sha256")).isNotPresent(); } @Test @@ -118,8 +113,6 @@ public void aync_json_nonStreaming_unsignedPayload_with_Sha1_in_header() { assertThat(getAsyncRequest().firstMatchingHeader("Content-MD5")).isNotPresent(); //Note that content will be of form "{"stringMember":"Hello world"}" assertThat(getAsyncRequest().firstMatchingHeader("x-amz-checksum-sha1")).hasValue("M68rRwFal7o7B3KEMt3m0w39TaA="); - // Assertion to make sure signer was not executed - assertThat(getAsyncRequest().firstMatchingHeader("x-amz-content-sha256")).isNotPresent(); } @Test @@ -129,27 +122,19 @@ public void sync_xml_nonStreaming_unsignedPayload_with_Sha1_in_header() { .checksumAlgorithm(software.amazon.awssdk.services.protocolrestxml.model.ChecksumAlgorithm.SHA1).build()); assertThat(getSyncRequest().firstMatchingHeader("Content-MD5")).isNotPresent(); //Note that content will be of form "Hello world" - assertThat(getSyncRequest().firstMatchingHeader("x-amz-checksum-sha1")).hasValue("FB/utBbwFLbIIt5ul3Ojuy5dKgU="); - // Assertion to make sure signer was not executed - assertThat(getSyncRequest().firstMatchingHeader("x-amz-content-sha256")).isNotPresent(); + // TODO(sra-identity-and-auth): Uncomment once sra is set to true + // assertThat(getSyncRequest().firstMatchingHeader("x-amz-checksum-sha1")).hasValue("FB/utBbwFLbIIt5ul3Ojuy5dKgU="); } @Test public void sync_xml_nonStreaming_unsignedEmptyPayload_with_Sha1_in_header() { // jsonClient.flexibleCheckSumOperationWithShaChecksum(r -> r.stringMember("Hello world")); xmlClient.operationWithChecksumNonStreaming(r -> r.checksumAlgorithm(software.amazon.awssdk.services.protocolrestxml.model.ChecksumAlgorithm.SHA1).build()); + assertThat(getSyncRequest().firstMatchingHeader("Content-MD5")).isNotPresent(); //Note that content will be of form "Hello world" - assertThat(getSyncRequest().firstMatchingHeader("x-amz-checksum-sha1")).isNotPresent(); - - - Map> requestHeaders = getSyncRequest().headers(); - - boolean disjoint = Collections.disjoint(VALID_CHECKSUM_HEADERS, requestHeaders.keySet()); - assertThat(disjoint).isTrue(); - - // Assertion to make sure signer was not executed - assertThat(getSyncRequest().firstMatchingHeader("x-amz-content-sha256")).isNotPresent(); + // TODO(sra-identity-and-auth): Uncomment once sra is set to true + // assertThat(getSyncRequest().firstMatchingHeader("x-amz-checksum-sha1")).hasValue("2jmj7l5rSw0yVb/vlWAYkK/YBwk="); } @Test @@ -161,8 +146,6 @@ public void aync_xml_nonStreaming_unsignedPayload_with_Sha1_in_header() { assertThat(getAsyncRequest().firstMatchingHeader("Content-MD5")).isNotPresent(); //Note that content will be of form Hello world" assertThat(getAsyncRequest().firstMatchingHeader("x-amz-checksum-sha1")).hasValue("FB/utBbwFLbIIt5ul3Ojuy5dKgU="); - // Assertion to make sure signer was not executed - assertThat(getAsyncRequest().firstMatchingHeader("x-amz-content-sha256")).isNotPresent(); } @Test @@ -171,17 +154,10 @@ public void aync_xml_nonStreaming_unsignedEmptyPayload_with_Sha1_in_header() { xmlAsyncClient.operationWithChecksumNonStreaming(r -> r.checksumAlgorithm(software.amazon.awssdk.services.protocolrestxml.model.ChecksumAlgorithm.SHA1).build()).join(); - - Map> requestHeaders = getAsyncRequest().headers(); - - boolean disjoint = Collections.disjoint(VALID_CHECKSUM_HEADERS, requestHeaders.keySet()); - assertThat(disjoint).isTrue(); - assertThat(getAsyncRequest().firstMatchingHeader("Content-MD5")).isNotPresent(); //Note that content will be of form Hello world" - assertThat(getAsyncRequest().firstMatchingHeader("x-amz-checksum-sha1")).isNotPresent(); - // Assertion to make sure signer was not executed - assertThat(getAsyncRequest().firstMatchingHeader("x-amz-content-sha256")).isNotPresent(); + // TODO(sra-identity-and-auth): Uncomment once sra is set to true + // assertThat(getAsyncRequest().firstMatchingHeader("x-amz-checksum-sha1")).hasValue("2jmj7l5rSw0yVb/vlWAYkK/YBwk="); } private SdkHttpRequest getSyncRequest() { @@ -209,4 +185,4 @@ private SdkHttpRequest getAsyncRequest() { return clientBuilder.credentialsProvider(AnonymousCredentialsProvider.create()) .region(Region.US_WEST_2); } -} \ No newline at end of file +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HttpChecksumRequiredTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HttpChecksumRequiredTest.java index 6ec2cc2421c1..cc4f6ad7a357 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HttpChecksumRequiredTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HttpChecksumRequiredTest.java @@ -159,6 +159,7 @@ public void syncJsonSupportsOperationWithRequestChecksumRequired() { public void syncJsonSupportsOperationWithCustomRequestChecksum() { jsonClient.operationWithCustomRequestChecksum(r -> r.stringMember("foo").checksumAlgorithm(ChecksumAlgorithm.CRC32)); assertThat(getSyncRequest().firstMatchingHeader("Content-MD5")).isNotPresent(); + assertThat(getSyncRequest().firstMatchingHeader("x-amz-checksum-crc32")).hasValue("rzlTOg=="); } private SdkHttpRequest getSyncRequest() { diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HttpChecksumValidationTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HttpChecksumValidationTest.java index eec9de313529..27b495c72008 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HttpChecksumValidationTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HttpChecksumValidationTest.java @@ -29,6 +29,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; +import static software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute.ENABLE_CHUNKED_ENCODING; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.junit.WireMockRule; @@ -73,7 +74,14 @@ public void setupClient() { .credentialsProvider(AnonymousCredentialsProvider.create()) .region(Region.US_EAST_1) .endpointOverride(URI.create("http://localhost:" + wireMock.port())) - .overrideConfiguration(o -> o.addExecutionInterceptor(new CaptureChecksumValidationInterceptor())) + .overrideConfiguration( + // TODO(sra-identity-and-auth): we should remove these + // overrides once we set up codegen to set chunk-encoding to true + // for requests that are streaming and checksum-enabled + o -> o.addExecutionInterceptor(new CaptureChecksumValidationInterceptor()) + .putExecutionAttribute( + ENABLE_CHUNKED_ENCODING, true + )) .build(); asyncClient = ProtocolRestJsonAsyncClient.builder() @@ -125,7 +133,13 @@ public void syncClientValidateStreamingResponseZeroByte() { public void syncClientValidateNonStreamingResponse() { String expectedChecksum = "lzlLIA=="; stubWithCRC32AndSha256Checksum("{\"stringMember\":\"Hello world\"}", expectedChecksum, "crc32"); - client.operationWithChecksumNonStreaming(r -> r.stringMember("Hello world").checksumAlgorithm(ChecksumAlgorithm.CRC32)); + client.operationWithChecksumNonStreaming( + r -> r.stringMember("Hello world").checksumAlgorithm(ChecksumAlgorithm.CRC32) + // TODO(sra-identity-and-auth): we should remove these + // overrides once we set up codegen to set chunk-encoding to true + // for requests that are streaming and checksum-enabled + .overrideConfiguration(c -> c.putExecutionAttribute(ENABLE_CHUNKED_ENCODING, false)) + ); verify(postRequestedFor(urlEqualTo("/")).withHeader("x-amz-checksum-crc32", equalTo(expectedChecksum))); OperationWithChecksumNonStreamingResponse operationWithChecksumNonStreamingResponse = client.operationWithChecksumNonStreaming(o -> o.checksumMode(ChecksumMode.ENABLED)); @@ -138,7 +152,13 @@ public void syncClientValidateNonStreamingResponse() { public void syncClientValidateNonStreamingResponseZeroByte() { String expectedChecksum = "o6a/Qw=="; stubWithCRC32AndSha256Checksum("{}", expectedChecksum, "crc32"); - client.operationWithChecksumNonStreaming(r -> r.checksumAlgorithm(ChecksumAlgorithm.CRC32)); + client.operationWithChecksumNonStreaming( + r -> r.checksumAlgorithm(ChecksumAlgorithm.CRC32) + // TODO(sra-identity-and-auth): we should remove these + // overrides once we set up codegen to set chunk-encoding to true + // for requests that are streaming and checksum-enabled + .overrideConfiguration(c -> c.putExecutionAttribute(ENABLE_CHUNKED_ENCODING, false)) + ); verify(postRequestedFor(urlEqualTo("/")).withHeader("x-amz-checksum-crc32", equalTo(expectedChecksum))); OperationWithChecksumNonStreamingResponse operationWithChecksumNonStreamingResponse = client.operationWithChecksumNonStreaming(o -> o.checksumMode(ChecksumMode.ENABLED)); @@ -240,7 +260,12 @@ public void asyncClientValidateNonStreamingResponse() { String expectedChecksum = "lzlLIA=="; stubWithCRC32AndSha256Checksum("{\"stringMember\":\"Hello world\"}", expectedChecksum, "crc32"); OperationWithChecksumNonStreamingResponse response = - asyncClient.operationWithChecksumNonStreaming(o -> o.checksumMode(ChecksumMode.ENABLED)).join(); + asyncClient.operationWithChecksumNonStreaming( + o -> o.checksumMode(ChecksumMode.ENABLED) + // TODO(sra-identity-and-auth): we should remove these + // overrides once we set up codegen to set chunk-encoding to true + // for requests that are streaming and checksum-enabled + .overrideConfiguration(c -> c.putExecutionAttribute(ENABLE_CHUNKED_ENCODING, false))).join(); assertThat(response.stringMember()).isEqualTo("Hello world"); assertThat(CaptureChecksumValidationInterceptor.checksumValidation).isEqualTo(ChecksumValidation.VALIDATED); assertThat(CaptureChecksumValidationInterceptor.expectedAlgorithm).isEqualTo(Algorithm.CRC32); @@ -251,7 +276,12 @@ public void asyncClientValidateNonStreamingResponseZeroByte() { String expectedChecksum = "o6a/Qw=="; stubWithCRC32AndSha256Checksum("{}", expectedChecksum, "crc32"); OperationWithChecksumNonStreamingResponse operationWithChecksumNonStreamingResponse = - asyncClient.operationWithChecksumNonStreaming(o -> o.checksumMode(ChecksumMode.ENABLED)).join(); + asyncClient.operationWithChecksumNonStreaming( + o -> o.checksumMode(ChecksumMode.ENABLED) + // TODO(sra-identity-and-auth): we should remove these + // overrides once we set up codegen to set chunk-encoding to true + // for requests that are streaming and checksum-enabled + .overrideConfiguration(c -> c.putExecutionAttribute(ENABLE_CHUNKED_ENCODING, false))).join(); assertThat(operationWithChecksumNonStreamingResponse.stringMember()).isNull(); assertThat(CaptureChecksumValidationInterceptor.checksumValidation).isEqualTo(ChecksumValidation.VALIDATED); assertThat(CaptureChecksumValidationInterceptor.expectedAlgorithm).isEqualTo(Algorithm.CRC32); @@ -370,4 +400,4 @@ public void onExecutionFailure(Context.FailedExecution context, ExecutionAttribu executionAttributes.getOptionalAttribute(SdkExecutionAttribute.HTTP_RESPONSE_CHECKSUM_VALIDATION).orElse(null); } } -} \ No newline at end of file +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/NoneAuthTypeRequestTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/NoneAuthTypeRequestTest.java index e914d348ad2e..f11901ef1fa6 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/NoneAuthTypeRequestTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/NoneAuthTypeRequestTest.java @@ -17,6 +17,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import io.reactivex.Flowable; import java.io.IOException; @@ -24,9 +28,8 @@ import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.awscore.client.builder.AwsAsyncClientBuilder; import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; import software.amazon.awssdk.awscore.client.builder.AwsSyncClientBuilder; @@ -48,8 +51,10 @@ /** * Verify that the "authtype" C2J trait for request type is honored for each requests. */ +// TODO(sra-identity-auth): Verify these tests pass for useSraAuht=true public class NoneAuthTypeRequestTest { + private AwsCredentialsProvider credentialsProvider; private SdkHttpClient httpClient; private SdkAsyncHttpClient httpAsyncClient; private ProtocolRestJsonClient jsonClient; @@ -57,11 +62,14 @@ public class NoneAuthTypeRequestTest { private ProtocolRestXmlClient xmlClient; private ProtocolRestXmlAsyncClient xmlAsyncClient; - @Before public void setup() throws IOException { - httpClient = Mockito.mock(SdkHttpClient.class); - httpAsyncClient = Mockito.mock(SdkAsyncHttpClient.class); + credentialsProvider = mock(AwsCredentialsProvider.class); + when(credentialsProvider.resolveIdentity()).thenAnswer( + invocationOnMock -> CompletableFuture.completedFuture(AwsBasicCredentials.create("123", "12344"))); + + httpClient = mock(SdkHttpClient.class); + httpAsyncClient = mock(SdkAsyncHttpClient.class); jsonClient = initializeSync(ProtocolRestJsonClient.builder()).build(); jsonAsyncClient = initializeAsync(ProtocolRestJsonAsyncClient.builder()).build(); xmlClient = initializeSync(ProtocolRestXmlClient.builder()).build(); @@ -72,13 +80,13 @@ public void setup() throws IOException { .putHeader("Content-Length", "0") .build(); - ExecutableHttpRequest request = Mockito.mock(ExecutableHttpRequest.class); + ExecutableHttpRequest request = mock(ExecutableHttpRequest.class); - Mockito.when(request.call()).thenReturn(HttpExecuteResponse.builder() + when(request.call()).thenReturn(HttpExecuteResponse.builder() .response(successfulHttpResponse) .build()); - Mockito.when(httpClient.prepareRequest(any())).thenReturn(request); - Mockito.when(httpAsyncClient.execute(any())).thenAnswer(invocation -> { + when(httpClient.prepareRequest(any())).thenReturn(request); + when(httpAsyncClient.execute(any())).thenAnswer(invocation -> { AsyncExecuteRequest asyncExecuteRequest = invocation.getArgument(0, AsyncExecuteRequest.class); asyncExecuteRequest.responseHandler().onHeaders(successfulHttpResponse); asyncExecuteRequest.responseHandler().onStream(Flowable.empty()); @@ -90,68 +98,76 @@ public void setup() throws IOException { public void sync_json_authorization_is_absent_for_noneAuthType() { jsonClient.operationWithNoneAuthType(o -> o.booleanMember(true)); assertThat(getSyncRequest().firstMatchingHeader("Authorization")).isNotPresent(); + verify(credentialsProvider, times(0)).resolveIdentity(); } @Test public void sync_json_authorization_is_present_for_defaultAuth() { jsonClient.jsonValuesOperation(); assertThat(getSyncRequest().firstMatchingHeader("Authorization")).isPresent(); + verify(credentialsProvider, times(1)).resolveIdentity(); } @Test public void async_json_authorization_is_absent_for_noneAuthType() { jsonAsyncClient.operationWithNoneAuthType(o -> o.booleanMember(true)); assertThat(getAsyncRequest().firstMatchingHeader("Authorization")).isNotPresent(); + verify(credentialsProvider, times(0)).resolveIdentity(); } @Test public void async_json_authorization_is_present_for_defaultAuth() { jsonAsyncClient.jsonValuesOperation(); assertThat(getAsyncRequest().firstMatchingHeader("Authorization")).isPresent(); + verify(credentialsProvider, times(1)).resolveIdentity(); } @Test public void sync_xml_authorization_is_absent_for_noneAuthType() { xmlClient.operationWithNoneAuthType(o -> o.booleanMember(true)); assertThat(getSyncRequest().firstMatchingHeader("Authorization")).isNotPresent(); + verify(credentialsProvider, times(0)).resolveIdentity(); } @Test public void sync_xml_authorization_is_present_for_defaultAuth() { xmlClient.jsonValuesOperation(json -> json.jsonValueMember("one")); assertThat(getSyncRequest().firstMatchingHeader("Authorization")).isPresent(); + verify(credentialsProvider, times(1)).resolveIdentity(); } @Test public void async_xml_authorization_is_absent_for_noneAuthType() { xmlAsyncClient.operationWithNoneAuthType(o -> o.booleanMember(true)); assertThat(getAsyncRequest().firstMatchingHeader("Authorization")).isNotPresent(); + verify(credentialsProvider, times(0)).resolveIdentity(); } @Test public void async_xml_authorization_is_present_for_defaultAuth() { xmlAsyncClient.jsonValuesOperation(json -> json.jsonValueMember("one")); assertThat(getAsyncRequest().firstMatchingHeader("Authorization")).isPresent(); + verify(credentialsProvider, times(1)).resolveIdentity(); } private SdkHttpRequest getSyncRequest() { ArgumentCaptor captor = ArgumentCaptor.forClass(HttpExecuteRequest.class); - Mockito.verify(httpClient).prepareRequest(captor.capture()); + verify(httpClient).prepareRequest(captor.capture()); return captor.getValue().httpRequest(); } private SdkHttpRequest getAsyncRequest() { ArgumentCaptor captor = ArgumentCaptor.forClass(AsyncExecuteRequest.class); - Mockito.verify(httpAsyncClient).execute(captor.capture()); + verify(httpAsyncClient).execute(captor.capture()); return captor.getValue().request(); } private & AwsClientBuilder> T initializeSync(T syncClientBuilder) { - return initialize(syncClientBuilder.httpClient(httpClient).credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("123", "12344")))); + return initialize(syncClientBuilder.httpClient(httpClient).credentialsProvider(credentialsProvider)); } private & AwsClientBuilder> T initializeAsync(T asyncClientBuilder) { - return initialize(asyncClientBuilder.httpClient(httpAsyncClient)); + return initialize(asyncClientBuilder.httpClient(httpAsyncClient).credentialsProvider(credentialsProvider)); } private > T initialize(T clientBuilder) { diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/OverrideConfigurationPluginsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/OverrideConfigurationPluginsTest.java new file mode 100644 index 000000000000..2184416cdc95 --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/OverrideConfigurationPluginsTest.java @@ -0,0 +1,142 @@ +/* + * 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; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.SdkPlugin; +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.regions.Region; +import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersAsyncClient; +import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersAsyncClientBuilder; +import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersClient; +import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersClientBuilder; +import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersServiceClientConfiguration; +import software.amazon.awssdk.utils.Validate; + +public class OverrideConfigurationPluginsTest { + private CapturingInterceptor interceptor; + + @BeforeEach + void setup() { + this.interceptor = new CapturingInterceptor(); + } + + @Test + void sync_pluginsClientOverrideConfiguration_isAddedToRequest() { + RestJsonEndpointProvidersClient syncClient = syncClientBuilder() + .addPlugin(config -> config.overrideConfiguration(c -> c.addExecutionInterceptor(interceptor) + .putHeader("K1", "V1"))) + .build(); + assertThatThrownBy(() -> syncClient.allTypes(r -> { + })) + .hasMessageContaining("boom!"); + + assertThat(interceptor.context.httpRequest().headers()).containsEntry("K1", singletonList("V1")); + } + + @Test + void sync_pluginsRequestOverrideConfiguration_isAddedToRequest() { + RestJsonEndpointProvidersClient syncClient = syncClientBuilder().build(); + SdkPlugin plugin = config -> config.overrideConfiguration(c -> c.putHeader("K1", "V1")); + assertThatThrownBy(() -> syncClient.allTypes(r -> r.overrideConfiguration(c -> c.addPlugin(plugin)))) + .hasMessageContaining("boom!"); + + assertThat(interceptor.context.httpRequest().headers()).containsEntry("K1", singletonList("V1")); + } + + @Test + void async_pluginsClientOverrideConfiguration_isAddedToRequest() { + RestJsonEndpointProvidersAsyncClient syncClient = asyncClientBuilder() + .addPlugin(config -> config.overrideConfiguration(c -> c.addExecutionInterceptor(interceptor) + .putHeader("K1", "V1"))) + .build(); + assertThatThrownBy(() -> syncClient.allTypes(r -> { + }).join()) + .hasMessageContaining("boom!"); + + assertThat(interceptor.context.httpRequest().headers()).containsEntry("K1", singletonList("V1")); + } + + @Test + void async_pluginsRequestOverrideConfiguration_isAddedToRequest() { + RestJsonEndpointProvidersAsyncClient syncClient = asyncClientBuilder().build(); + SdkPlugin plugin = config -> config.overrideConfiguration(c -> c.putHeader("K1", "V1")); + assertThatThrownBy(() -> syncClient.allTypes(r -> r.overrideConfiguration(c -> c.addPlugin(plugin))) + .join()) + .hasMessageContaining("boom!"); + + assertThat(interceptor.context.httpRequest().headers()).containsEntry("K1", singletonList("V1")); + } + + private RestJsonEndpointProvidersClientBuilder syncClientBuilder() { + return RestJsonEndpointProvidersClient + .builder() + .addPlugin(c -> { + RestJsonEndpointProvidersServiceClientConfiguration.Builder config = + Validate.isInstanceOf(RestJsonEndpointProvidersServiceClientConfiguration.Builder.class, c, "\uD83E\uDD14"); + config.region(Region.US_WEST_2) + .credentialsProvider( + StaticCredentialsProvider.create( + AwsBasicCredentials.create("akid", "skid"))) + .overrideConfiguration(oc -> oc.addExecutionInterceptor(interceptor)); + }); + } + + private RestJsonEndpointProvidersAsyncClientBuilder asyncClientBuilder() { + return RestJsonEndpointProvidersAsyncClient + .builder() + .addPlugin(c -> { + RestJsonEndpointProvidersServiceClientConfiguration.Builder config = + Validate.isInstanceOf(RestJsonEndpointProvidersServiceClientConfiguration.Builder.class, c, "\uD83E\uDD14"); + config.region(Region.US_WEST_2) + .credentialsProvider( + StaticCredentialsProvider.create( + AwsBasicCredentials.create("akid", "skid"))) + .overrideConfiguration(oc -> oc.addExecutionInterceptor(interceptor)); + }); + } + + public static class CapturingInterceptor implements ExecutionInterceptor { + private Context.BeforeTransmission context; + private ExecutionAttributes executionAttributes; + + @Override + public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { + this.context = context; + this.executionAttributes = executionAttributes; + throw new RuntimeException("boom!"); + } + + public ExecutionAttributes executionAttributes() { + return executionAttributes; + } + + public class CaptureCompletedException extends RuntimeException { + CaptureCompletedException(String message) { + super(message); + } + } + } +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/OverrideConfigurationTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/OverrideConfigurationTest.java new file mode 100644 index 000000000000..84e62ecb9276 --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/OverrideConfigurationTest.java @@ -0,0 +1,131 @@ +/* + * 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; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +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.SdkHttpRequest; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.endpointproviders.EndpointInterceptorTests; +import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersAsyncClient; +import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersAsyncClientBuilder; +import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersClient; +import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersClientBuilder; + +public class OverrideConfigurationTest { + private CapturingInterceptor interceptor; + + @BeforeEach + public void setup() { + this.interceptor = new CapturingInterceptor(); + } + + @Test + public void sync_clientOverrideConfiguration_isAddedToRequest() { + RestJsonEndpointProvidersClient syncClient = syncClientBuilder() + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor) + .putHeader("K1", "V1")) + .build(); + assertThatThrownBy(() -> syncClient.allTypes(r -> {})) + .hasMessageContaining("stop"); + + assertThat(interceptor.context.httpRequest().headers()).containsEntry("K1", singletonList("V1")); + } + + @Test + public void sync_requestOverrideConfiguration_isAddedToRequest() { + RestJsonEndpointProvidersClient syncClient = syncClientBuilder().build(); + assertThatThrownBy(() -> syncClient.allTypes(r -> r.overrideConfiguration(c -> c.putHeader("K1", "V1") + .putRawQueryParameter("K2", "V2")))) + .hasMessageContaining("stop"); + + assertThat(interceptor.context.httpRequest().headers()).containsEntry("K1", singletonList("V1")); + assertThat(interceptor.context.httpRequest().rawQueryParameters()).containsEntry("K2", singletonList("V2")); + } + + @Test + public void async_clientOverrideConfiguration_isAddedToRequest() { + RestJsonEndpointProvidersAsyncClient syncClient = asyncClientBuilder() + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor) + .putHeader("K1", "V1")) + .build(); + assertThatThrownBy(() -> syncClient.allTypes(r -> {}).join()) + .hasMessageContaining("stop"); + + assertThat(interceptor.context.httpRequest().headers()).containsEntry("K1", singletonList("V1")); + } + + @Test + public void async_requestOverrideConfiguration_isAddedToRequest() { + RestJsonEndpointProvidersAsyncClient syncClient = asyncClientBuilder().build(); + assertThatThrownBy(() -> syncClient.allTypes(r -> r.overrideConfiguration(c -> c.putHeader("K1", "V1") + .putRawQueryParameter("K2", "V2"))) + .join()) + .hasMessageContaining("stop"); + + assertThat(interceptor.context.httpRequest().headers()).containsEntry("K1", singletonList("V1")); + assertThat(interceptor.context.httpRequest().rawQueryParameters()).containsEntry("K2", singletonList("V2")); + } + + private RestJsonEndpointProvidersClientBuilder syncClientBuilder() { + return RestJsonEndpointProvidersClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider( + StaticCredentialsProvider.create( + AwsBasicCredentials.create("akid", "skid"))) + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor)); + } + + private RestJsonEndpointProvidersAsyncClientBuilder asyncClientBuilder() { + return RestJsonEndpointProvidersAsyncClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider( + StaticCredentialsProvider.create( + AwsBasicCredentials.create("akid", "skid"))) + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor)); + } + + public static class CapturingInterceptor implements ExecutionInterceptor { + private Context.BeforeTransmission context; + private ExecutionAttributes executionAttributes; + + @Override + public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { + this.context = context; + this.executionAttributes = executionAttributes; + throw new RuntimeException("stop"); + } + + public ExecutionAttributes executionAttributes() { + return executionAttributes; + } + + public class CaptureCompletedException extends RuntimeException { + CaptureCompletedException(String message) { + super(message); + } + } + } +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/ProfileFileConfigurationTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/ProfileFileConfigurationTest.java index 2516badaaf0e..1a00668c006c 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/ProfileFileConfigurationTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/ProfileFileConfigurationTest.java @@ -40,6 +40,7 @@ import software.amazon.awssdk.utils.StringInputStream; public class ProfileFileConfigurationTest { + @Test public void profileIsHonoredForCredentialsAndRegion() { EnvironmentVariableHelper.run(env -> { @@ -101,4 +102,8 @@ private ProfileFile profileFile(String content) { .type(ProfileFile.Type.CONFIGURATION) .build(); } + + // TODO(sra-identity-and-auth): Should add test for the same using SRA way, to assert the identity in SignRequest and + // region SignerProperty are per profile. + // To do this, need ability to inject AuthScheme which uses mock HttpSigner. This is pending https://i.amazon.com/SMITHY-1450 } diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SraIdentityResolutionTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SraIdentityResolutionTest.java new file mode 100644 index 000000000000..c1e83b2d9df9 --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SraIdentityResolutionTest.java @@ -0,0 +1,71 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.concurrent.CompletableFuture; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.ResolveIdentityRequest; +import software.amazon.awssdk.services.protocolquery.ProtocolQueryClient; + +@RunWith(MockitoJUnitRunner.class) +public class SraIdentityResolutionTest { + + @Mock + private AwsCredentialsProvider credsProvider; + + @Test + public void testIdentityPropertyBasedResolutionIsUsedAndNotAnotherIdentityResolution() { + SdkHttpClient mockClient = mock(SdkHttpClient.class); + when(mockClient.prepareRequest(any())).thenThrow(new RuntimeException("boom")); + + when(credsProvider.identityType()).thenReturn(AwsCredentialsIdentity.class); + when(credsProvider.resolveIdentity(any(ResolveIdentityRequest.class))) + .thenReturn(CompletableFuture.completedFuture(AwsBasicCredentials.create("akid1", "skid2"))); + + ProtocolQueryClient syncClient = ProtocolQueryClient + .builder() + .httpClient(mockClient) + .credentialsProvider(credsProvider) + // Below is necessary to create the test case where, addCredentialsToExecutionAttributes was getting called before + .overrideConfiguration(ClientOverrideConfiguration.builder().build()) + .build(); + + assertThatThrownBy(() -> syncClient.allTypes(r -> {})).hasMessageContaining("boom"); + verify(credsProvider, times(2)).identityType(); + + // This asserts that the identity used is the one from resolveIdentity() called by SRA AuthSchemeInterceptor and not from + // from another call like from AwsCredentialsAuthorizationStrategy.addCredentialsToExecutionAttributes, asserted by + // combination of times(1) and verifyNoMoreInteractions. + verify(credsProvider, times(1)).resolveIdentity(any(ResolveIdentityRequest.class)); + verifyNoMoreInteractions(credsProvider); + } +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SraIdentityResolutionUsingPluginsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SraIdentityResolutionUsingPluginsTest.java new file mode 100644 index 000000000000..96cef695d64c --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SraIdentityResolutionUsingPluginsTest.java @@ -0,0 +1,93 @@ +/* + * 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; + +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.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.concurrent.CompletableFuture; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkServiceClientConfiguration; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.ResolveIdentityRequest; +import software.amazon.awssdk.services.protocolquery.ProtocolQueryClient; +import software.amazon.awssdk.services.protocolquery.ProtocolQueryServiceClientConfiguration; +import software.amazon.awssdk.utils.Validate; + +@RunWith(MockitoJUnitRunner.class) +public class SraIdentityResolutionUsingPluginsTest { + + @Mock + private AwsCredentialsProvider credentialsProvider; + + @Test + public void testIdentityBasedPluginsResolutionIsUsedAndNotAnotherIdentityResolution() { + SdkHttpClient mockClient = mock(SdkHttpClient.class); + when(mockClient.prepareRequest(any())).thenThrow(new RuntimeException("boom")); + + when(credentialsProvider.identityType()).thenReturn(AwsCredentialsIdentity.class); + when(credentialsProvider.resolveIdentity(any(ResolveIdentityRequest.class))) + .thenReturn(CompletableFuture.completedFuture(AwsBasicCredentials.create("akid1", "skid2"))); + + ProtocolQueryClient syncClient = ProtocolQueryClient + .builder() + .httpClient(mockClient) + .addPlugin(new TestPlugin(credentialsProvider)) + // Below is necessary to create the test case where, addCredentialsToExecutionAttributes was getting called before + .overrideConfiguration(ClientOverrideConfiguration.builder().build()) + .build(); + + assertThatThrownBy(() -> syncClient.allTypes(r -> {})).hasMessageContaining("boom"); + verify(credentialsProvider, times(3)).identityType(); + + // This asserts that the identity used is the one from resolveIdentity() called by SRA AuthSchemeInterceptor and not + // from another call like from AwsCredentialsAuthorizationStrategy.addCredentialsToExecutionAttributes, asserted by + // combination of times(1) and verifyNoMoreInteractions. + verify(credentialsProvider, times(1)).resolveIdentity(any(ResolveIdentityRequest.class)); + verifyNoMoreInteractions(credentialsProvider); + } + + static class TestPlugin implements SdkPlugin { + private final AwsCredentialsProvider credentialsProvider; + + TestPlugin(AwsCredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + } + + @Override + public void configureClient(SdkServiceClientConfiguration.Builder config) { + ProtocolQueryServiceClientConfiguration.Builder builder = + Validate.isInstanceOf(ProtocolQueryServiceClientConfiguration.Builder.class, + config, + "Expecting an instance of " + + ProtocolQueryServiceClientConfiguration.class); + builder.credentialsProvider(credentialsProvider); + } + } +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SraIdentityResolutionUsingRequestPluginsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SraIdentityResolutionUsingRequestPluginsTest.java new file mode 100644 index 000000000000..550e9198d4ad --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SraIdentityResolutionUsingRequestPluginsTest.java @@ -0,0 +1,94 @@ +/* + * 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; + +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.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.concurrent.CompletableFuture; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkServiceClientConfiguration; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.ResolveIdentityRequest; +import software.amazon.awssdk.services.protocolquery.ProtocolQueryClient; +import software.amazon.awssdk.services.protocolquery.ProtocolQueryServiceClientConfiguration; +import software.amazon.awssdk.services.protocolquery.model.AllTypesRequest; +import software.amazon.awssdk.utils.Validate; + +@RunWith(MockitoJUnitRunner.class) +public class SraIdentityResolutionUsingRequestPluginsTest { + + @Mock + private AwsCredentialsProvider credentialsProvider; + + @Test + public void testIdentityBasedPluginsResolutionIsUsedAndNotAnotherIdentityResolution() { + SdkHttpClient mockClient = mock(SdkHttpClient.class); + when(mockClient.prepareRequest(any())).thenThrow(new RuntimeException("boom")); + + when(credentialsProvider.identityType()).thenReturn(AwsCredentialsIdentity.class); + when(credentialsProvider.resolveIdentity(any(ResolveIdentityRequest.class))) + .thenReturn(CompletableFuture.completedFuture(AwsBasicCredentials.create("akid1", "skid2"))); + + ProtocolQueryClient syncClient = ProtocolQueryClient + .builder() + .httpClient(mockClient) + .build(); + + AllTypesRequest request = AllTypesRequest.builder() + .overrideConfiguration(c -> c.addPlugin(new TestPlugin(credentialsProvider))) + .build(); + assertThatThrownBy(() -> syncClient.allTypes(request)) + .hasMessageContaining("boom"); + + verify(credentialsProvider, times(2)).identityType(); + // This asserts that the identity used is the one from resolveIdentity() called by SRA AuthSchemeInterceptor and not + // from another call like from AwsCredentialsAuthorizationStrategy.addCredentialsToExecutionAttributes, asserted by + // combination of times(1) and verifyNoMoreInteractions. + verify(credentialsProvider, times(1)).resolveIdentity(any(ResolveIdentityRequest.class)); + verifyNoMoreInteractions(credentialsProvider); + } + + static class TestPlugin implements SdkPlugin { + private final AwsCredentialsProvider credentialsProvider; + + TestPlugin(AwsCredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + } + + @Override + public void configureClient(SdkServiceClientConfiguration.Builder config) { + ProtocolQueryServiceClientConfiguration.Builder builder = + Validate.isInstanceOf(ProtocolQueryServiceClientConfiguration.Builder.class, + config, + "Expecting an instance of " + + ProtocolQueryServiceClientConfiguration.class); + builder.credentialsProvider(credentialsProvider); + } + } +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SyncHttpChecksumInTrailerTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SyncHttpChecksumInTrailerTest.java index 366e21cc0c2c..c115c18a8066 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SyncHttpChecksumInTrailerTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SyncHttpChecksumInTrailerTest.java @@ -26,6 +26,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute.ENABLE_CHUNKED_ENCODING; import static software.amazon.awssdk.http.Header.CONTENT_LENGTH; import static software.amazon.awssdk.http.Header.CONTENT_TYPE; @@ -64,8 +65,13 @@ public void setupClient() { .credentialsProvider(AnonymousCredentialsProvider.create()) .region(Region.US_EAST_1) .endpointOverride(URI.create("http://localhost:" + wireMock.port())) + // TODO(sra-identity-and-auth): we should remove these + // overrides once we set up codegen to set chunk-encoding to true + // for requests that are streaming and checksum-enabled + .overrideConfiguration(c -> c.putExecutionAttribute( + ENABLE_CHUNKED_ENCODING, true + )) .build(); - } @Test diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/bearerauth/ClientBuilderTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/bearerauth/ClientBuilderTest.java index a0c673044a4c..f60762bee1a4 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/bearerauth/ClientBuilderTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/bearerauth/ClientBuilderTest.java @@ -17,20 +17,21 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; import java.lang.reflect.Method; import org.junit.jupiter.api.Test; import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; -import software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner; import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider; +import software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.regions.Region; public class ClientBuilderTest { @@ -39,7 +40,7 @@ public void syncClient_includesDefaultProvider_includesDefaultSigner() { DefaultBearerauthClientBuilder builder = new DefaultBearerauthClientBuilder(); SdkClientConfiguration config = getSyncConfig(builder); - assertThat(config.option(AwsClientOption.TOKEN_PROVIDER)) + assertThat(config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER)) .isInstanceOf(DefaultAwsTokenProvider.class); assertThat(config.option(SdkAdvancedClientOption.TOKEN_SIGNER)) .isInstanceOf(BearerTokenSigner.class); @@ -53,10 +54,21 @@ public void syncClient_customTokenProviderSet_presentInFinalConfig() { builder.tokenProvider(mockProvider); SdkClientConfiguration config = getSyncConfig(builder); - assertThat(config.option(AwsClientOption.TOKEN_PROVIDER)) + assertThat(config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER)) .isSameAs(mockProvider); } + @Test + public void syncClient_customTokenIdentityProviderSet_presentInFinalConfig() { + DefaultBearerauthClientBuilder builder = new DefaultBearerauthClientBuilder(); + + IdentityProvider mockProvider = mock(IdentityProvider.class); + builder.tokenProvider(mockProvider); + SdkClientConfiguration config = getSyncConfig(builder); + + assertThat(config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER)) + .isSameAs(mockProvider); + } @Test public void syncClient_customSignerSet_presentInFinalConfig() { @@ -76,7 +88,7 @@ public void asyncClient_includesDefaultProvider_includesDefaultSigner() { DefaultBearerauthAsyncClientBuilder builder = new DefaultBearerauthAsyncClientBuilder(); SdkClientConfiguration config = getAsyncConfig(builder); - assertThat(config.option(AwsClientOption.TOKEN_PROVIDER)) + assertThat(config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER)) .isInstanceOf(DefaultAwsTokenProvider.class); assertThat(config.option(SdkAdvancedClientOption.TOKEN_SIGNER)) .isInstanceOf(BearerTokenSigner.class); @@ -90,7 +102,19 @@ public void asyncClient_customTokenProviderSet_presentInFinalConfig() { builder.tokenProvider(mockProvider); SdkClientConfiguration config = getAsyncConfig(builder); - assertThat(config.option(AwsClientOption.TOKEN_PROVIDER)) + assertThat(config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER)) + .isSameAs(mockProvider); + } + + @Test + public void asyncClient_customTokenIdentityProviderSet_presentInFinalConfig() { + DefaultBearerauthAsyncClientBuilder builder = new DefaultBearerauthAsyncClientBuilder(); + + IdentityProvider mockProvider = mock(IdentityProvider.class); + builder.tokenProvider(mockProvider); + SdkClientConfiguration config = getAsyncConfig(builder); + + assertThat(config.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER)) .isSameAs(mockProvider); } diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/AuthSchemeUtilsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/AuthSchemeUtilsTest.java index 1aa5c76161c6..e373f0084973 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/AuthSchemeUtilsTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/AuthSchemeUtilsTest.java @@ -23,14 +23,10 @@ import java.util.Arrays; import java.util.Collections; import org.junit.Test; -import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; import software.amazon.awssdk.core.exception.SdkClientException; -import software.amazon.awssdk.core.interceptor.ExecutionAttributes; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.regions.RegionScope; import software.amazon.awssdk.services.restjsonendpointproviders.endpoints.internal.AuthSchemeUtils; public class AuthSchemeUtilsTest { @@ -61,107 +57,4 @@ public void chooseAuthScheme_multipleSchemesKnown_choosesFirst() { assertThat(AuthSchemeUtils.chooseAuthScheme(Arrays.asList(sigv4, sigv4a))).isEqualTo(sigv4); } - - @Test - public void setSigningParams_typeUnknown_throws() { - EndpointAuthScheme sigv5 = mock(EndpointAuthScheme.class); - assertThatThrownBy(() -> AuthSchemeUtils.setSigningParams(new ExecutionAttributes(), sigv5)) - .isInstanceOf(SdkClientException.class) - .hasMessageContaining("Don't know how to set signing params for auth scheme"); - } - - @Test - public void setSigningParams_sigv4_setsParamsCorrectly() { - EndpointAuthScheme sigv4 = SigV4AuthScheme.builder() - .signingName("myservice") - .disableDoubleEncoding(true) - .signingRegion("us-west-2") - .build(); - - ExecutionAttributes attrs = new ExecutionAttributes(); - - AuthSchemeUtils.setSigningParams(attrs, sigv4); - - assertThat(attrs.getAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME)).isEqualTo("myservice"); - assertThat(attrs.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION)).isEqualTo(Region.of("us-west-2")); - assertThat(attrs.getAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE)).isEqualTo(false); - } - - @Test - public void setSigningParams_sigv4_paramsAreNull_doesNotOverrideAttrs() { - EndpointAuthScheme sigv4 = SigV4AuthScheme.builder() - .build(); - - ExecutionAttributes attrs = new ExecutionAttributes(); - attrs.putAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, "myservice"); - attrs.putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, Region.of("us-west-2")); - // disableDoubleEncoding has a default value - - AuthSchemeUtils.setSigningParams(attrs, sigv4); - - assertThat(attrs.getAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME)).isEqualTo("myservice"); - assertThat(attrs.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION)).isEqualTo(Region.of("us-west-2")); - assertThat(attrs.getAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE)).isEqualTo(true); - } - - @Test - public void setSigningParams_sigv4a_setsParamsCorrectly() { - EndpointAuthScheme sigv4 = SigV4aAuthScheme.builder() - .signingName("myservice") - .disableDoubleEncoding(true) - .addSigningRegion("*") - .build(); - - ExecutionAttributes attrs = new ExecutionAttributes(); - - AuthSchemeUtils.setSigningParams(attrs, sigv4); - - assertThat(attrs.getAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME)).isEqualTo("myservice"); - assertThat(attrs.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION_SCOPE)).isEqualTo(RegionScope.GLOBAL); - assertThat(attrs.getAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE)).isEqualTo(false); - } - - @Test - public void setSigningParams_sigv4a_throwsIfRegionSetEmpty() { - EndpointAuthScheme sigv4 = SigV4aAuthScheme.builder() - .build(); - - ExecutionAttributes attrs = new ExecutionAttributes(); - - assertThatThrownBy(() -> AuthSchemeUtils.setSigningParams(attrs, sigv4)) - .isInstanceOf(SdkClientException.class) - .hasMessageContaining("Signing region set is empty"); - } - - @Test - public void setSigningParams_sigv4a_throwsIfRegionSetHasMultiple() { - EndpointAuthScheme sigv4 = SigV4aAuthScheme.builder() - .addSigningRegion("a") - .addSigningRegion("b") - .build(); - - ExecutionAttributes attrs = new ExecutionAttributes(); - - assertThatThrownBy(() -> AuthSchemeUtils.setSigningParams(attrs, sigv4)) - .isInstanceOf(SdkClientException.class) - .hasMessageContaining("Don't know how to set scope of > 1 region"); - } - - @Test - public void setSigningParams_sigv4a_signingNameNull_doesNotOverrideAttrs() { - EndpointAuthScheme sigv4a = SigV4aAuthScheme.builder() - .addSigningRegion("*") - .build(); - - ExecutionAttributes attrs = new ExecutionAttributes(); - attrs.putAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, "myservice"); - // utils validates that the region list is not empty - // disableDoubleEncoding has a default value - - AuthSchemeUtils.setSigningParams(attrs, sigv4a); - - assertThat(attrs.getAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME)).isEqualTo("myservice"); - assertThat(attrs.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION_SCOPE)).isEqualTo(RegionScope.GLOBAL); - assertThat(attrs.getAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE)).isEqualTo(true); - } } diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/AwsEndpointProviderUtilsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/AwsEndpointProviderUtilsTest.java index f61593d07b47..1380003380e9 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/AwsEndpointProviderUtilsTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/AwsEndpointProviderUtilsTest.java @@ -25,7 +25,6 @@ import java.util.Map; import org.junit.Test; import software.amazon.awssdk.awscore.AwsExecutionAttribute; -import software.amazon.awssdk.awscore.AwsRequest; import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; @@ -38,7 +37,6 @@ import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.SdkHttpRequest; import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.protocolquery.model.AllTypesRequest; import software.amazon.awssdk.services.restjsonendpointproviders.endpoints.internal.AwsEndpointProviderUtils; import software.amazon.awssdk.services.restjsonendpointproviders.endpoints.internal.Identifier; import software.amazon.awssdk.services.restjsonendpointproviders.endpoints.internal.Value; @@ -279,47 +277,6 @@ public void setUri_withTrailingSlashNoPath_combinesPathsCorrectly() { .isEqualTo("https://override.example.com//a"); } - @Test - public void setHeaders_existingValuesOnOverride_combinesWithNewValues() { - AwsRequest request = AllTypesRequest.builder() - .overrideConfiguration(o -> o.putHeader("foo", Arrays.asList("a", "b"))) - .build(); - - Map> newHeaders = MapUtils.of("foo", Arrays.asList("c")); - AwsRequest newRequest = AwsEndpointProviderUtils.addHeaders(request, newHeaders); - - Map> expectedHeaders = MapUtils.of("foo", Arrays.asList("a", "b", "c")); - - assertThat(newRequest.overrideConfiguration().get().headers()).isEqualTo(expectedHeaders); - } - - @Test - public void setHeaders_noExistingValues_setCorrectly() { - AwsRequest request = AllTypesRequest.builder() - .overrideConfiguration(o -> {}) - .build(); - - Map> newHeaders = MapUtils.of("foo", Arrays.asList("a")); - AwsRequest newRequest = AwsEndpointProviderUtils.addHeaders(request, newHeaders); - - Map> expectedHeaders = MapUtils.of("foo", Arrays.asList("a")); - - assertThat(newRequest.overrideConfiguration().get().headers()).isEqualTo(expectedHeaders); - } - - @Test - public void setHeaders_noExistingOverrideConfig_createsOverrideConfig() { - AwsRequest request = AllTypesRequest.builder() - .build(); - - Map> newHeaders = MapUtils.of("foo", Arrays.asList("a")); - AwsRequest newRequest = AwsEndpointProviderUtils.addHeaders(request, newHeaders); - - Map> expectedHeaders = MapUtils.of("foo", Arrays.asList("a")); - - assertThat(newRequest.overrideConfiguration().get().headers()).isEqualTo(expectedHeaders); - } - @Test public void addHostPrefix_prefixIsNull_returnsUnModified() { URI url = URI.create("https://foo.aws"); diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointAuthSchemeInterceptorTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointAuthSchemeInterceptorTest.java deleted file mode 100644 index de9db3445131..000000000000 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointAuthSchemeInterceptorTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.endpointproviders; - - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; - -import java.util.Arrays; -import org.junit.Test; -import software.amazon.awssdk.auth.signer.Aws4Signer; -import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; -import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; -import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; -import software.amazon.awssdk.core.SdkRequest; -import software.amazon.awssdk.core.interceptor.ExecutionAttributes; -import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; -import software.amazon.awssdk.core.interceptor.InterceptorContext; -import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; -import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; -import software.amazon.awssdk.core.signer.Signer; -import software.amazon.awssdk.endpoints.Endpoint; -import software.amazon.awssdk.services.protocolquery.model.AllTypesRequest; -import software.amazon.awssdk.services.restjsonendpointproviders.endpoints.internal.RestJsonEndpointProvidersEndpointAuthSchemeInterceptor; - -public class EndpointAuthSchemeInterceptorTest { - private static final ExecutionInterceptor INTERCEPTOR = new RestJsonEndpointProvidersEndpointAuthSchemeInterceptor(); - - @Test - public void modifyRequest_sigV4Scheme_overridesCorrectSigner() { - SigV4AuthScheme scheme = SigV4AuthScheme.builder().build(); - - SdkRequest request = AllTypesRequest.builder().build(); - - Endpoint endpoint = Endpoint.builder() - .putAttribute(AwsEndpointAttribute.AUTH_SCHEMES, Arrays.asList(scheme)) - .build(); - - ExecutionAttributes attrs = new ExecutionAttributes(); - attrs.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, endpoint); - - SdkRequest modified = INTERCEPTOR.modifyRequest(InterceptorContext.builder().request(request).build(), attrs); - - assertThat(modified.overrideConfiguration().flatMap(o -> o.signer()).get()).isInstanceOf(Aws4Signer.class); - } - - @Test - public void modifyRequest_sigV4aScheme_overridesCorrectSigner() { - SigV4aAuthScheme scheme = SigV4aAuthScheme.builder() - .addSigningRegion("*") - .build(); - - SdkRequest request = AllTypesRequest.builder().build(); - - Endpoint endpoint = Endpoint.builder() - .putAttribute(AwsEndpointAttribute.AUTH_SCHEMES, Arrays.asList(scheme)) - .build(); - - ExecutionAttributes attrs = new ExecutionAttributes(); - attrs.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, endpoint); - - // Will throw since Crt is not on the classpath, which is fine for this test. - assertThatThrownBy(() -> INTERCEPTOR.modifyRequest(InterceptorContext.builder().request(request).build(), attrs)) - .hasMessageContaining("AwsCrtV4aSigner"); - } - - @Test - public void modifyRequest_signerOverriddenOnRequest_doesNotModify() { - Signer overrideSigner = mock(Signer.class); - - SigV4AuthScheme scheme = SigV4AuthScheme.builder().build(); - - SdkRequest request = AllTypesRequest.builder() - .overrideConfiguration(o -> o.signer(overrideSigner)) - .build(); - - Endpoint endpoint = Endpoint.builder() - .putAttribute(AwsEndpointAttribute.AUTH_SCHEMES, Arrays.asList(scheme)) - .build(); - - ExecutionAttributes attrs = new ExecutionAttributes(); - attrs.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, endpoint); - - SdkRequest modified = INTERCEPTOR.modifyRequest(InterceptorContext.builder().request(request).build(), attrs); - - assertThat(modified.overrideConfiguration().flatMap(o -> o.signer()).get()).isSameAs(overrideSigner); - } - - @Test - public void modifyRequest_signerOverriddenClient_doesNotModify() { - SigV4AuthScheme scheme = SigV4AuthScheme.builder().build(); - - SdkRequest request = AllTypesRequest.builder() - .build(); - - Endpoint endpoint = Endpoint.builder() - .putAttribute(AwsEndpointAttribute.AUTH_SCHEMES, Arrays.asList(scheme)) - .build(); - - ExecutionAttributes attrs = new ExecutionAttributes(); - attrs.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, endpoint); - attrs.putAttribute(SdkExecutionAttribute.SIGNER_OVERRIDDEN, true); - - SdkRequest modified = INTERCEPTOR.modifyRequest(InterceptorContext.builder().request(request).build(), attrs); - - assertThat(modified.overrideConfiguration().flatMap(o -> o.signer()).orElse(null)).isNull(); - } -} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointInterceptorTests.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointInterceptorTests.java index 4ab603f270bb..6ca37d6d3f73 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointInterceptorTests.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointInterceptorTests.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.concurrent.CompletableFuture; import org.junit.Test; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; @@ -27,14 +28,23 @@ import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.endpoints.Endpoint; -import software.amazon.awssdk.endpoints.EndpointProvider; -import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.BaseSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersAsyncClient; import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersAsyncClientBuilder; import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersClient; import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersClientBuilder; import software.amazon.awssdk.services.restjsonendpointproviders.endpoints.RestJsonEndpointProvidersEndpointProvider; +import software.amazon.awssdk.utils.CompletableFutureUtils; public class EndpointInterceptorTests { @@ -98,12 +108,395 @@ public void async_clientContextParamsSetOnBuilder_includedInExecutionAttributes( assertThat(endpoint).isNotNull(); } + @Test + public void sync_endpointProviderReturnsHeaders_includedInHttpRequest() { + RestJsonEndpointProvidersEndpointProvider defaultProvider = RestJsonEndpointProvidersEndpointProvider.defaultProvider(); + + CapturingInterceptor interceptor = new CapturingInterceptor(); + RestJsonEndpointProvidersClient client = syncClientBuilder() + .overrideConfiguration(o -> o.addExecutionInterceptor(interceptor)) + .endpointProvider(r -> defaultProvider.resolveEndpoint(r) + .thenApply(e -> e.toBuilder() + .putHeader("TestHeader", "TestValue") + .build())) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> {})) + .hasMessageContaining("stop"); + + assertThat(interceptor.context.httpRequest().matchingHeaders("TestHeader")).containsExactly("TestValue"); + } + + @Test + public void async_endpointProviderReturnsHeaders_includedInHttpRequest() { + RestJsonEndpointProvidersEndpointProvider defaultProvider = RestJsonEndpointProvidersEndpointProvider.defaultProvider(); + + CapturingInterceptor interceptor = new CapturingInterceptor(); + RestJsonEndpointProvidersAsyncClient client = asyncClientBuilder() + .overrideConfiguration(o -> o.addExecutionInterceptor(interceptor)) + .endpointProvider(r -> defaultProvider.resolveEndpoint(r) + .thenApply(e -> e.toBuilder() + .putHeader("TestHeader", "TestValue") + .build())) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> {}).join()) + .hasMessageContaining("stop"); + + assertThat(interceptor.context.httpRequest().matchingHeaders("TestHeader")).containsExactly("TestValue"); + } + + @Test + public void sync_endpointProviderReturnsHeaders_appendedToExistingRequest() { + RestJsonEndpointProvidersEndpointProvider defaultProvider = RestJsonEndpointProvidersEndpointProvider.defaultProvider(); + + CapturingInterceptor interceptor = new CapturingInterceptor(); + RestJsonEndpointProvidersClient client = syncClientBuilder() + .overrideConfiguration(o -> o.addExecutionInterceptor(interceptor)) + .endpointProvider(r -> defaultProvider.resolveEndpoint(r) + .thenApply(e -> e.toBuilder() + .putHeader("TestHeader", "TestValue") + .build())) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> r.overrideConfiguration(c -> c.putHeader("TestHeader", + "TestValue0")))) + .hasMessageContaining("stop"); + + assertThat(interceptor.context.httpRequest().matchingHeaders("TestHeader")).containsExactly("TestValue", "TestValue0"); + } + + @Test + public void async_endpointProviderReturnsHeaders_appendedToExistingRequest() { + RestJsonEndpointProvidersEndpointProvider defaultProvider = RestJsonEndpointProvidersEndpointProvider.defaultProvider(); + + CapturingInterceptor interceptor = new CapturingInterceptor(); + RestJsonEndpointProvidersAsyncClient client = asyncClientBuilder() + .overrideConfiguration(o -> o.addExecutionInterceptor(interceptor)) + .endpointProvider(r -> defaultProvider.resolveEndpoint(r) + .thenApply(e -> e.toBuilder() + .putHeader("TestHeader", "TestValue") + .build())) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> r.overrideConfiguration(c -> c.putHeader("TestHeader", + "TestValue0"))) + .join()) + .hasMessageContaining("stop"); + + assertThat(interceptor.context.httpRequest().matchingHeaders("TestHeader")).containsExactly("TestValue", "TestValue0"); + } + + // TODO(sra-identity-auth): Enable for useSraAuth=true + /* + @Test + public void sync_endpointProviderReturnsSignerProperties_overridesV4AuthSchemeResolverProperties() { + RestJsonEndpointProvidersEndpointProvider defaultEndpointProvider = + RestJsonEndpointProvidersEndpointProvider.defaultProvider(); + + CapturingSigner signer = new CapturingSigner(); + + List endpointAuthSchemes = new ArrayList<>(); + endpointAuthSchemes.add(SigV4AuthScheme.builder() + .signingRegion("region-from-ep") + .signingName("name-from-ep") + .disableDoubleEncoding(true) + .build()); + + RestJsonEndpointProvidersClient client = syncClientBuilder() + .endpointProvider(r -> defaultEndpointProvider.resolveEndpoint(r) + .thenApply(e -> e.toBuilder() + .putAttribute(AwsEndpointAttribute.AUTH_SCHEMES, endpointAuthSchemes) + .build())) + .putAuthScheme(authScheme("aws.auth#sigv4", signer)) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> {})) + .hasMessageContaining("stop"); + + assertThat(signer.request.property(AwsV4HttpSigner.REGION_NAME)).isEqualTo("region-from-ep"); + assertThat(signer.request.property(AwsV4HttpSigner.SERVICE_SIGNING_NAME)).isEqualTo("name-from-ep"); + assertThat(signer.request.property(AwsV4HttpSigner.DOUBLE_URL_ENCODE)).isEqualTo(false); + } + + @Test + public void async_endpointProviderReturnsSignerProperties_overridesV4AuthSchemeResolverProperties() { + RestJsonEndpointProvidersEndpointProvider defaultEndpointProvider = + RestJsonEndpointProvidersEndpointProvider.defaultProvider(); + + CapturingSigner signer = new CapturingSigner(); + + List endpointAuthSchemes = new ArrayList<>(); + endpointAuthSchemes.add(SigV4AuthScheme.builder() + .signingRegion("region-from-ep") + .signingName("name-from-ep") + .disableDoubleEncoding(true) + .build()); + + RestJsonEndpointProvidersAsyncClient client = asyncClientBuilder() + .endpointProvider(r -> defaultEndpointProvider.resolveEndpoint(r) + .thenApply(e -> e.toBuilder() + .putAttribute(AwsEndpointAttribute.AUTH_SCHEMES, endpointAuthSchemes) + .build())) + .putAuthScheme(authScheme("aws.auth#sigv4", signer)) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> {}).join()) + .hasMessageContaining("stop"); + + assertThat(signer.request.property(AwsV4HttpSigner.REGION_NAME)).isEqualTo("region-from-ep"); + assertThat(signer.request.property(AwsV4HttpSigner.SERVICE_SIGNING_NAME)).isEqualTo("name-from-ep"); + assertThat(signer.request.property(AwsV4HttpSigner.DOUBLE_URL_ENCODE)).isEqualTo(false); + } + + @Test + public void sync_endpointProviderReturnsSignerProperties_overridesV4AAuthSchemeResolverProperties() { + RestJsonEndpointProvidersEndpointProvider defaultEndpointProvider = + RestJsonEndpointProvidersEndpointProvider.defaultProvider(); + + CapturingSigner signer = new CapturingSigner(); + + List endpointAuthSchemes = new ArrayList<>(); + endpointAuthSchemes.add(SigV4aAuthScheme.builder() + .addSigningRegion("region-1-from-ep") + .signingName("name-from-ep") + .disableDoubleEncoding(true) + .build()); + + RestJsonEndpointProvidersClient client = syncClientBuilder() + .endpointProvider(r -> defaultEndpointProvider.resolveEndpoint(r) + .thenApply(e -> e.toBuilder() + .putAttribute(AwsEndpointAttribute.AUTH_SCHEMES, endpointAuthSchemes) + .build())) + .putAuthScheme(authScheme("aws.auth#sigv4a", signer)) + .authSchemeProvider(p -> singletonList(AuthSchemeOption.builder() + .schemeId("aws.auth#sigv4a") + .putSignerProperty( + AwsV4aHttpSigner.REGION_SET, RegionSet.create("us-east-1")) + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "Y") + .putSignerProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, true) + .build())) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> {})) + .hasMessageContaining("stop"); + + assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)).isEqualTo(RegionSet.create("region-1-from-ep")); + assertThat(signer.request.property(AwsV4aHttpSigner.SERVICE_SIGNING_NAME)).isEqualTo("name-from-ep"); + assertThat(signer.request.property(AwsV4aHttpSigner.DOUBLE_URL_ENCODE)).isEqualTo(false); + } + + @Test + public void async_endpointProviderReturnsSignerProperties_overridesV4AAuthSchemeResolverProperties() { + RestJsonEndpointProvidersEndpointProvider defaultEndpointProvider = + RestJsonEndpointProvidersEndpointProvider.defaultProvider(); + + CapturingSigner signer = new CapturingSigner(); + + List endpointAuthSchemes = new ArrayList<>(); + endpointAuthSchemes.add(SigV4aAuthScheme.builder() + .addSigningRegion("region-1-from-ep") + .signingName("name-from-ep") + .disableDoubleEncoding(true) + .build()); + + RestJsonEndpointProvidersAsyncClient client = asyncClientBuilder() + .endpointProvider(r -> defaultEndpointProvider.resolveEndpoint(r) + .thenApply(e -> e.toBuilder() + .putAttribute(AwsEndpointAttribute.AUTH_SCHEMES, endpointAuthSchemes) + .build())) + .putAuthScheme(authScheme("aws.auth#sigv4a", signer)) + .authSchemeProvider(p -> singletonList(AuthSchemeOption.builder() + .schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, + RegionSet.create("us-east-1")) + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "Y") + .putSignerProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, true) + .build())) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> {}).join()) + .hasMessageContaining("stop"); + + assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)).isEqualTo(RegionSet.create("region-1-from-ep")); + assertThat(signer.request.property(AwsV4aHttpSigner.SERVICE_SIGNING_NAME)).isEqualTo("name-from-ep"); + assertThat(signer.request.property(AwsV4aHttpSigner.DOUBLE_URL_ENCODE)).isEqualTo(false); + } + + @Test + public void sync_endpointProviderDoesNotReturnV4SignerProperties_executionAttributesFromAuthSchemeOption() { + RestJsonEndpointProvidersEndpointProvider defaultEndpointProvider = + RestJsonEndpointProvidersEndpointProvider.defaultProvider(); + CapturingInterceptor interceptor = new CapturingInterceptor(); + + RestJsonEndpointProvidersAsyncClient client = asyncClientBuilder() + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor)) + .authSchemeProvider(p -> singletonList(AuthSchemeOption.builder() + .schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, "X") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "Y") + .putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, true) + .build())) + .endpointProvider(r -> defaultEndpointProvider.resolveEndpoint(r) + .thenApply(e -> e.toBuilder() + .putAttribute(AwsEndpointAttribute.AUTH_SCHEMES, emptyList()) + .build())) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> {}).join()) + .hasMessageContaining("stop"); + + assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION)).isEqualTo(Region.of("X")); + assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME)).isEqualTo("Y"); + assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE)).isEqualTo(true); + } + + @Test + public void sync_endpointProviderReturnsV4SignerProperties_executionAttributesFromEndpointProvider() { + RestJsonEndpointProvidersEndpointProvider defaultEndpointProvider = + RestJsonEndpointProvidersEndpointProvider.defaultProvider(); + CapturingInterceptor interceptor = new CapturingInterceptor(); + + List endpointAuthSchemes = new ArrayList<>(); + endpointAuthSchemes.add(SigV4AuthScheme.builder() + .signingRegion("region-from-ep") + .signingName("name-from-ep") + .disableDoubleEncoding(false) + .build()); + + RestJsonEndpointProvidersAsyncClient client = asyncClientBuilder() + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor)) + .endpointProvider(r -> defaultEndpointProvider.resolveEndpoint(r) + .thenApply(e -> e.toBuilder() + .putAttribute(AwsEndpointAttribute.AUTH_SCHEMES, endpointAuthSchemes) + .build())) + .authSchemeProvider(p -> singletonList(AuthSchemeOption.builder() + .schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, "X") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "Y") + .putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, false) + .build())) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> {}).join()) + .hasMessageContaining("stop"); + + assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION)).isEqualTo(Region.of("region-from-ep")); + assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME)).isEqualTo("name-from-ep"); + assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE)).isEqualTo(true); + } + + @Test + public void sync_endpointProviderDoesNotReturnV4aSignerProperties_executionAttributesFromAuthSchemeOption() { + RestJsonEndpointProvidersEndpointProvider defaultEndpointProvider = + RestJsonEndpointProvidersEndpointProvider.defaultProvider(); + CapturingInterceptor interceptor = new CapturingInterceptor(); + + RestJsonEndpointProvidersAsyncClient client = asyncClientBuilder() + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor)) + .authSchemeProvider(p -> singletonList(AuthSchemeOption.builder() + .schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, + RegionSet.create("region-from-ap")) + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "Y") + .putSignerProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, true) + .build())) + .endpointProvider(r -> defaultEndpointProvider.resolveEndpoint(r) + .thenApply(e -> e.toBuilder() + .putAttribute(AwsEndpointAttribute.AUTH_SCHEMES, emptyList()) + .build())) + .putAuthScheme(authScheme("aws.auth#sigv4a", AwsV4HttpSigner.create())) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> {}).join()) + .hasMessageContaining("stop"); + + assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION_SCOPE)).isEqualTo(RegionScope.create("region-from-ap")); + assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME)).isEqualTo("Y"); + assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE)).isEqualTo(true); + } + + @Test + public void sync_endpointProviderReturnsV4aSignerProperties_executionAttributesFromEndpointProvider() { + RestJsonEndpointProvidersEndpointProvider defaultEndpointProvider = + RestJsonEndpointProvidersEndpointProvider.defaultProvider(); + CapturingInterceptor interceptor = new CapturingInterceptor(); + + List endpointAuthSchemes = new ArrayList<>(); + endpointAuthSchemes.add(SigV4aAuthScheme.builder() + .addSigningRegion("region-from-ep") + .signingName("name-from-ep") + .disableDoubleEncoding(false) + .build()); + + RestJsonEndpointProvidersAsyncClient client = asyncClientBuilder() + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor)) + .endpointProvider(r -> defaultEndpointProvider.resolveEndpoint(r) + .thenApply(e -> e.toBuilder() + .putAttribute(AwsEndpointAttribute.AUTH_SCHEMES, endpointAuthSchemes) + .build())) + .authSchemeProvider(p -> singletonList(AuthSchemeOption.builder() + .schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, + RegionSet.create("us-east-1")) + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "Y") + .putSignerProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, false) + .build())) + .putAuthScheme(authScheme("aws.auth#sigv4a", AwsV4HttpSigner.create())) + .build(); + + assertThatThrownBy(() -> client.operationWithHostPrefix(r -> {}).join()) + .hasMessageContaining("stop"); + + assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION_SCOPE)).isEqualTo(RegionScope.create("region-from-ep")); + assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME)).isEqualTo("name-from-ep"); + assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE)).isEqualTo(true); + } + */ + + private static AuthScheme authScheme(String schemeId, HttpSigner signer) { + return new AuthScheme() { + @Override + public String schemeId() { + return schemeId; + } + + @Override + public IdentityProvider identityProvider(IdentityProviders providers) { + return providers.identityProvider(AwsCredentialsIdentity.class); + } + + @Override + public HttpSigner signer() { + return signer; + } + }; + } + + public static class CapturingSigner implements HttpSigner { + private BaseSignRequest request; + + @Override + public SignedRequest sign(SignRequest request) { + this.request = request; + throw new CaptureCompletedException("stop"); + } + + @Override + public CompletableFuture signAsync(AsyncSignRequest request) { + this.request = request; + return CompletableFutureUtils.failedFuture(new CaptureCompletedException("stop")); + } + } + public static class CapturingInterceptor implements ExecutionInterceptor { + private Context.BeforeTransmission context; private ExecutionAttributes executionAttributes; @Override - public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { + public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { + this.context = context; this.executionAttributes = executionAttributes; throw new CaptureCompletedException("stop"); } @@ -111,13 +504,14 @@ public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, Execu public ExecutionAttributes executionAttributes() { return executionAttributes; } + } - public class CaptureCompletedException extends RuntimeException { - CaptureCompletedException(String message) { - super(message); - } + public static class CaptureCompletedException extends RuntimeException { + CaptureCompletedException(String message) { + super(message); } } + private RestJsonEndpointProvidersClientBuilder syncClientBuilder() { return RestJsonEndpointProvidersClient.builder() .region(Region.US_WEST_2) diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/eventstreams/EventMarshallingTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/eventstreams/EventMarshallingTest.java index 9132777be342..3fb253890e82 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/eventstreams/EventMarshallingTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/eventstreams/EventMarshallingTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; + import io.reactivex.Flowable; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -75,6 +76,9 @@ public void setup() { eventDecoder = new MessageDecoder(); } + // Still failing for useSraAuth=true + // TODO(sra-identity-and-auth): This test no longer works, it's unclear as of why not but seems related to the changes in + // the signing stage. @Test public void testMarshalling_setsCorrectEventType() { List inputEvents = Stream.of( diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/CoreMetricsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/CoreMetricsTest.java index 583f1ae1a30c..e4144e6692b6 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/CoreMetricsTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/CoreMetricsTest.java @@ -38,11 +38,9 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.core.exception.SdkException; -import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.core.internal.metrics.SdkErrorType; +import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.AbortableInputStream; import software.amazon.awssdk.http.ExecutableHttpRequest; @@ -60,6 +58,7 @@ import software.amazon.awssdk.services.protocolrestjson.model.EmptyModeledException; import software.amazon.awssdk.services.protocolrestjson.model.SimpleStruct; import software.amazon.awssdk.services.protocolrestjson.paginators.PaginatedOperationWithResultKeyIterable; +import software.amazon.awssdk.services.testutil.MockIdentityProviderUtil; @RunWith(MockitoJUnitRunner.class) public class CoreMetricsTest { @@ -76,9 +75,6 @@ public class CoreMetricsTest { @Mock private SdkHttpClient mockHttpClient; - @Mock - private AwsCredentialsProvider mockCredentialsProvider; - @Mock private MetricPublisher mockPublisher; @@ -90,7 +86,7 @@ public void setup() throws IOException { client = ProtocolRestJsonClient.builder() .httpClient(mockHttpClient) .region(Region.US_WEST_2) - .credentialsProvider(mockCredentialsProvider) + .credentialsProvider(MockIdentityProviderUtil.mockIdentityProvider()) .overrideConfiguration(c -> c.addMetricPublisher(mockPublisher).retryPolicy(b -> b.numRetries(MAX_RETRIES))) .endpointProvider(mockEndpointProvider) .build(); @@ -117,15 +113,6 @@ public void setup() throws IOException { when(mockHttpClient.prepareRequest(any(HttpExecuteRequest.class))) .thenReturn(mockExecuteRequest); - when(mockCredentialsProvider.resolveCredentials()).thenAnswer(invocation -> { - try { - Thread.sleep(100); - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - return AwsBasicCredentials.create("foo", "bar"); - }); - when(mockEndpointProvider.resolveEndpoint(any(ProtocolRestJsonEndpointParams.class))).thenReturn( CompletableFuture.completedFuture(Endpoint.builder() .url(URI.create("https://protocolrestjson.amazonaws.com")) @@ -144,7 +131,7 @@ public void teardown() { public void testApiCall_noConfiguredPublisher_succeeds() { ProtocolRestJsonClient noPublisher = ProtocolRestJsonClient.builder() .region(Region.US_WEST_2) - .credentialsProvider(mockCredentialsProvider) + .credentialsProvider(MockIdentityProviderUtil.mockIdentityProvider()) .httpClient(mockHttpClient) .build(); diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/SyncClientMetricPublisherResolutionTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/SyncClientMetricPublisherResolutionTest.java index e07c73703883..d0df719646dd 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/SyncClientMetricPublisherResolutionTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/SyncClientMetricPublisherResolutionTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Arrays; @@ -28,8 +29,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.http.AbortableInputStream; import software.amazon.awssdk.http.ExecutableHttpRequest; import software.amazon.awssdk.http.HttpExecuteRequest; @@ -41,6 +40,7 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient; import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClientBuilder; +import software.amazon.awssdk.services.testutil.MockIdentityProviderUtil; @RunWith(MockitoJUnitRunner.class) public class SyncClientMetricPublisherResolutionTest { @@ -48,9 +48,6 @@ public class SyncClientMetricPublisherResolutionTest { @Mock private SdkHttpClient mockHttpClient; - @Mock - private AwsCredentialsProvider mockCredentialsProvider; - private ProtocolRestJsonClient client; @After @@ -130,7 +127,7 @@ private ProtocolRestJsonClient clientWithPublishers(MetricPublisher... metricPub ProtocolRestJsonClientBuilder builder = ProtocolRestJsonClient.builder() .httpClient(mockHttpClient) .region(Region.US_WEST_2) - .credentialsProvider(mockCredentialsProvider); + .credentialsProvider(MockIdentityProviderUtil.mockIdentityProvider()); AbortableInputStream content = AbortableInputStream.create(new ByteArrayInputStream("{}".getBytes())); SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder() @@ -153,15 +150,6 @@ private ProtocolRestJsonClient clientWithPublishers(MetricPublisher... metricPub when(mockHttpClient.prepareRequest(any(HttpExecuteRequest.class))) .thenReturn(mockExecuteRequest); - when(mockCredentialsProvider.resolveCredentials()).thenAnswer(invocation -> { - try { - Thread.sleep(100); - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - return AwsBasicCredentials.create("foo", "bar"); - }); - if (metricPublishers != null) { builder.overrideConfiguration(o -> o.metricPublishers(Arrays.asList(metricPublishers))); } diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncClientMetricPublisherResolutionTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncClientMetricPublisherResolutionTest.java index 64d43fbe7560..7e1c218f9888 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncClientMetricPublisherResolutionTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncClientMetricPublisherResolutionTest.java @@ -21,10 +21,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; + import com.github.tomakehurst.wiremock.junit.WireMockRule; import java.io.IOException; import java.net.URI; import java.util.Arrays; +import java.util.concurrent.CompletableFuture; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -34,19 +36,18 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.metrics.MetricCollection; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonAsyncClient; import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonAsyncClientBuilder; import software.amazon.awssdk.services.protocolrestjson.model.ProtocolRestJsonException; +import software.amazon.awssdk.services.testutil.MockIdentityProviderUtil; @RunWith(MockitoJUnitRunner.class) public class AsyncClientMetricPublisherResolutionTest { - @Mock - private AwsCredentialsProvider mockCredentialsProvider; - @Rule public WireMockRule wireMock = new WireMockRule(0); @@ -56,18 +57,6 @@ public class AsyncClientMetricPublisherResolutionTest { private ProtocolRestJsonAsyncClient client; - @Before - public void setup() { - when(mockCredentialsProvider.resolveCredentials()).thenAnswer(invocation -> { - try { - Thread.sleep(100); - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - return AwsBasicCredentials.create("foo", "bar"); - }); - } - @After public void teardown() { wireMock.resetAll(); @@ -150,7 +139,7 @@ public void testApiCall_publishersSetOnClientAndRequest_requestPublishersInvoked private ProtocolRestJsonAsyncClient clientWithPublishers(MetricPublisher... metricPublishers) { ProtocolRestJsonAsyncClientBuilder builder = ProtocolRestJsonAsyncClient.builder() .region(Region.US_WEST_2) - .credentialsProvider(mockCredentialsProvider) + .credentialsProvider(MockIdentityProviderUtil.mockIdentityProvider()) .endpointOverride(URI.create("http://localhost:" + wireMock.port())); if (metricPublishers != null) { diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncCoreMetricsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncCoreMetricsTest.java index 7649fbae6fb0..0eb6366d689f 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncCoreMetricsTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncCoreMetricsTest.java @@ -24,12 +24,8 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule; import java.io.IOException; import java.net.URI; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.function.Supplier; -import java.util.stream.Collectors; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -38,15 +34,15 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.metrics.MetricCollection; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonAsyncClient; import software.amazon.awssdk.services.protocolrestjson.model.PaginatedOperationWithResultKeyResponse; -import software.amazon.awssdk.services.protocolrestjson.model.SimpleStruct; -import software.amazon.awssdk.services.protocolrestjson.paginators.PaginatedOperationWithResultKeyIterable; import software.amazon.awssdk.services.protocolrestjson.paginators.PaginatedOperationWithResultKeyPublisher; +import software.amazon.awssdk.services.testutil.MockIdentityProviderUtil; /** * Core metrics test for async non-streaming API @@ -54,9 +50,6 @@ @RunWith(MockitoJUnitRunner.class) public class AsyncCoreMetricsTest extends BaseAsyncCoreMetricsTest { - @Mock - private AwsCredentialsProvider mockCredentialsProvider; - @Mock private MetricPublisher mockPublisher; @@ -70,19 +63,10 @@ public class AsyncCoreMetricsTest extends BaseAsyncCoreMetricsTest { public void setup() throws IOException { client = ProtocolRestJsonAsyncClient.builder() .region(Region.US_WEST_2) - .credentialsProvider(mockCredentialsProvider) + .credentialsProvider(MockIdentityProviderUtil.mockIdentityProvider()) .endpointOverride(URI.create("http://localhost:" + wireMock.port())) .overrideConfiguration(c -> c.addMetricPublisher(mockPublisher).retryPolicy(b -> b.numRetries(MAX_RETRIES))) .build(); - - when(mockCredentialsProvider.resolveCredentials()).thenAnswer(invocation -> { - try { - Thread.sleep(100); - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - return AwsBasicCredentials.create("foo", "bar"); - }); } @After @@ -114,7 +98,7 @@ public void apiCall_noConfiguredPublisher_succeeds() { stubSuccessfulResponse(); ProtocolRestJsonAsyncClient noPublisher = ProtocolRestJsonAsyncClient.builder() .region(Region.US_WEST_2) - .credentialsProvider(mockCredentialsProvider) + .credentialsProvider(MockIdentityProviderUtil.mockIdentityProvider()) .endpointOverride(URI.create("http://localhost:" + wireMock.port())) .build(); diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncEventStreamingCoreMetricsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncEventStreamingCoreMetricsTest.java index 534217c04110..39001594577e 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncEventStreamingCoreMetricsTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncEventStreamingCoreMetricsTest.java @@ -28,14 +28,16 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.core.async.EmptyPublisher; import software.amazon.awssdk.core.signer.NoOpSigner; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonAsyncClient; import software.amazon.awssdk.services.protocolrestjson.model.EventStreamOperationRequest; import software.amazon.awssdk.services.protocolrestjson.model.EventStreamOperationResponseHandler; +import software.amazon.awssdk.services.testutil.MockIdentityProviderUtil; /** * Core metrics test for async streaming API @@ -44,10 +46,6 @@ public class AsyncEventStreamingCoreMetricsTest extends BaseAsyncCoreMetricsTest { @Rule public WireMockRule wireMock = new WireMockRule(0); - - @Mock - private AwsCredentialsProvider mockCredentialsProvider; - @Mock private MetricPublisher mockPublisher; @@ -58,20 +56,11 @@ public class AsyncEventStreamingCoreMetricsTest extends BaseAsyncCoreMetricsTest public void setup() { client = ProtocolRestJsonAsyncClient.builder() .region(Region.US_WEST_2) - .credentialsProvider(mockCredentialsProvider) + .credentialsProvider(MockIdentityProviderUtil.mockIdentityProvider()) .endpointOverride(URI.create("http://localhost:" + wireMock.port())) .overrideConfiguration(c -> c.addMetricPublisher(mockPublisher) .retryPolicy(b -> b.numRetries(MAX_RETRIES))) .build(); - - when(mockCredentialsProvider.resolveCredentials()).thenAnswer(invocation -> { - try { - Thread.sleep(100); - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - return AwsBasicCredentials.create("foo", "bar"); - }); } @After diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncStreamingCoreMetricsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncStreamingCoreMetricsTest.java index 15b1aa5bf129..933fac4ed27d 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncStreamingCoreMetricsTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/AsyncStreamingCoreMetricsTest.java @@ -15,8 +15,6 @@ package software.amazon.awssdk.services.metrics.async; -import static org.mockito.Mockito.when; - import com.github.tomakehurst.wiremock.junit.WireMockRule; import java.io.IOException; import java.net.URI; @@ -28,13 +26,12 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonAsyncClient; import software.amazon.awssdk.services.protocolrestjson.model.StreamingInputOperationRequest; +import software.amazon.awssdk.services.testutil.MockIdentityProviderUtil; /** * Core metrics test for async streaming API @@ -42,9 +39,6 @@ @RunWith(MockitoJUnitRunner.class) public class AsyncStreamingCoreMetricsTest extends BaseAsyncCoreMetricsTest { - @Mock - private AwsCredentialsProvider mockCredentialsProvider; - @Mock private MetricPublisher mockPublisher; @@ -57,19 +51,10 @@ public class AsyncStreamingCoreMetricsTest extends BaseAsyncCoreMetricsTest { public void setup() throws IOException { client = ProtocolRestJsonAsyncClient.builder() .region(Region.US_WEST_2) - .credentialsProvider(mockCredentialsProvider) + .credentialsProvider(MockIdentityProviderUtil.mockIdentityProvider()) .endpointOverride(URI.create("http://localhost:" + wireMock.port())) .overrideConfiguration(c -> c.addMetricPublisher(mockPublisher).retryPolicy(b -> b.numRetries(MAX_RETRIES))) .build(); - - when(mockCredentialsProvider.resolveCredentials()).thenAnswer(invocation -> { - try { - Thread.sleep(100); - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - return AwsBasicCredentials.create("foo", "bar"); - }); } @After diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/protocolrestjson/AsyncOperationCancelTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/protocolrestjson/AsyncOperationCancelTest.java index 77c66e39b345..590fa67ad3f3 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/protocolrestjson/AsyncOperationCancelTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/protocolrestjson/AsyncOperationCancelTest.java @@ -15,6 +15,12 @@ package software.amazon.awssdk.services.protocolrestjson; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import io.reactivex.Flowable; +import java.util.concurrent.CompletableFuture; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,15 +38,10 @@ import software.amazon.awssdk.services.protocolrestjson.model.EventStream; import software.amazon.awssdk.services.protocolrestjson.model.EventStreamOperationResponse; import software.amazon.awssdk.services.protocolrestjson.model.EventStreamOperationResponseHandler; +import software.amazon.awssdk.services.protocolrestjson.model.InputEventStream; import software.amazon.awssdk.services.protocolrestjson.model.StreamingInputOperationResponse; import software.amazon.awssdk.services.protocolrestjson.model.StreamingOutputOperationResponse; -import java.util.concurrent.CompletableFuture; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - /** * Test to ensure that cancelling the future returned for an async operation will cancel the future returned by the async HTTP client. */ @@ -51,7 +52,7 @@ public class AsyncOperationCancelTest { private ProtocolRestJsonAsyncClient client; - private CompletableFuture executeFuture; + private CompletableFuture executeFuture; @Before public void setUp() { @@ -62,7 +63,7 @@ public void setUp() { .httpClient(mockHttpClient) .build(); - executeFuture = new CompletableFuture(); + executeFuture = new CompletableFuture<>(); when(mockHttpClient.execute(any())).thenReturn(executeFuture); } @@ -91,11 +92,14 @@ public void testStreamingOutputOperation() { assertThat(executeFuture.isCancelled()).isTrue(); } + // Still failing for useSraAuth=true + // TODO(sra-identity-and-auth): This test is now failing after changes made to the event-stream signer module. We need to + // investigate and figure out the fix. @Test - public void testEventStreamingOperation() { - CompletableFuture responseFuture = client.eventStreamOperation(r -> { - }, - subscriber -> {}, + public void testEventStreamingOperation() throws InterruptedException { + CompletableFuture responseFuture = + client.eventStreamOperation(r -> {}, + Flowable.just(InputEventStream.inputEventBuilder().build()), new EventStreamOperationResponseHandler() { @Override public void responseReceived(EventStreamOperationResponse response) { diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/serviceclientconfiguration/ServiceClientConfigurationUsingPluginsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/serviceclientconfiguration/ServiceClientConfigurationUsingPluginsTest.java new file mode 100644 index 000000000000..0d0ac3ada95c --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/serviceclientconfiguration/ServiceClientConfigurationUsingPluginsTest.java @@ -0,0 +1,226 @@ +/* + * 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.serviceclientconfiguration; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.protocolrestxml.ProtocolRestXmlAsyncClient; +import software.amazon.awssdk.services.protocolrestxml.ProtocolRestXmlClient; +import software.amazon.awssdk.services.protocolrestxml.ProtocolRestXmlServiceClientConfiguration; +import software.amazon.awssdk.services.protocolrestxml.endpoints.ProtocolRestXmlEndpointProvider; + +// The same battery of tests as in `ServiceClientConfigurationTest` but using plugins. +public class ServiceClientConfigurationUsingPluginsTest { + private static final SdkPlugin NOOP_PLUGIN = config -> {}; + + @Test + void syncClient_serviceClientConfiguration_shouldReturnCorrectRegion() { + SdkPlugin testPlugin = config -> { + if (config instanceof ProtocolRestXmlServiceClientConfiguration.Builder) { + ProtocolRestXmlServiceClientConfiguration.Builder builder = + (ProtocolRestXmlServiceClientConfiguration.Builder) config; + builder.region(Region.ME_SOUTH_1); + } + }; + ProtocolRestXmlClient client = ProtocolRestXmlClient.builder() + .addPlugin(testPlugin) + .build(); + + Region region = client.serviceClientConfiguration().region(); + assertThat(region).isEqualTo(Region.ME_SOUTH_1); + } + + @Test + void syncClientWithEndpointOverride_serviceClientConfiguration_shouldReturnCorrectEndpointOverride() { + URI uri = URI.create("https://www.amazon.com/"); + ProtocolRestXmlClient client = ProtocolRestXmlClient.builder() + .addPlugin(config -> config.endpointOverride(uri)) + .build(); + + URI endpointOverride = client.serviceClientConfiguration().endpointOverride().orElse(null); + assertThat(endpointOverride).isEqualTo(uri); + } + + @Test + void syncClientWithoutEndpointOverride_serviceClientConfiguration_shouldReturnEmptyOptional() { + ProtocolRestXmlClient client = ProtocolRestXmlClient.builder() + .addPlugin(NOOP_PLUGIN) + .build(); + URI endpointOverride = client.serviceClientConfiguration().endpointOverride().orElse(null); + assertThat(endpointOverride).isNull(); + } + + @Test + void syncClient_serviceClientConfiguration_shouldReturnCorrectClientOverrideConfigurationFields() { + ClientOverrideConfiguration overrideConfiguration = ClientOverrideConfiguration.builder() + .apiCallAttemptTimeout(Duration.ofSeconds(30)) + .apiCallTimeout(Duration.ofSeconds(90)) + .retryPolicy(c -> c.numRetries(4)) + .build(); + + ProtocolRestXmlClient client = ProtocolRestXmlClient.builder() + .addPlugin(config -> config.overrideConfiguration(overrideConfiguration)) + .build(); + + ClientOverrideConfiguration result = client.serviceClientConfiguration().overrideConfiguration(); + assertThat(result.apiCallAttemptTimeout().get()).isEqualTo(Duration.ofSeconds(30)); + assertThat(result.apiCallTimeout().get()).isEqualTo(Duration.ofSeconds(90)); + assertThat(result.retryPolicy().get().numRetries()).isEqualTo(4); + assertThat(result.defaultProfileFile()).isNotPresent(); + assertThat(result.metricPublishers()).isEmpty(); + } + + @Test + void syncClient_serviceClientConfiguration_withoutOverrideConfiguration_shouldReturnEmptyFields() { + ProtocolRestXmlClient client = ProtocolRestXmlClient.builder() + .addPlugin(NOOP_PLUGIN) + .build(); + + ClientOverrideConfiguration overrideConfiguration = client.serviceClientConfiguration().overrideConfiguration(); + assertThat(overrideConfiguration.toString()).isEqualTo( + "ClientOverrideConfiguration(headers={}, executionInterceptors=[], advancedOptions={})"); + assertThat(overrideConfiguration.apiCallAttemptTimeout()).isNotPresent(); + assertThat(overrideConfiguration.apiCallTimeout()).isNotPresent(); + assertThat(overrideConfiguration.retryPolicy()).isNotPresent(); + assertThat(overrideConfiguration.defaultProfileFile()).isNotPresent(); + assertThat(overrideConfiguration.metricPublishers()).isEmpty(); + } + + @Test + void syncClientWithEndpointProvider_serviceClientConfiguration_shouldReturnCorrectEndpointProvider() { + ProtocolRestXmlEndpointProvider clientEndpointProvider = ProtocolRestXmlEndpointProvider.defaultProvider(); + ProtocolRestXmlClient client = ProtocolRestXmlClient.builder() + .addPlugin(config -> config.endpointProvider(clientEndpointProvider)) + .build(); + + EndpointProvider endpointProvider = client.serviceClientConfiguration().endpointProvider().orElse(null); + assertThat(endpointProvider).isEqualTo(clientEndpointProvider); + } + + @Test + void syncClientWithoutEndpointProvider_serviceClientConfiguration_shouldReturnDefaultEndpointProvider() { + ProtocolRestXmlClient client = ProtocolRestXmlClient.builder() + .build(); + + EndpointProvider endpointProvider = client.serviceClientConfiguration().endpointProvider().orElse(null); + assertThat(endpointProvider instanceof ProtocolRestXmlEndpointProvider).isTrue(); + } + + @Test + void asyncClient_serviceClientConfiguration_shouldReturnCorrectRegion() { + SdkPlugin testPlugin = config -> { + if (config instanceof ProtocolRestXmlServiceClientConfiguration.Builder) { + ProtocolRestXmlServiceClientConfiguration.Builder builder = + (ProtocolRestXmlServiceClientConfiguration.Builder) config; + + builder.region(Region.ME_SOUTH_1); + } + }; + ProtocolRestXmlAsyncClient client = ProtocolRestXmlAsyncClient.builder() + .addPlugin(testPlugin) + .build(); + + Region region = client.serviceClientConfiguration().region(); + assertThat(region).isEqualTo(Region.ME_SOUTH_1); + } + + @Test + void asyncClientWithEndpointOverride_serviceClientConfiguration_shouldReturnCorrectEndpointOverride() { + URI uri = URI.create("https://www.amazon.com/"); + ProtocolRestXmlAsyncClient client = ProtocolRestXmlAsyncClient.builder() + .addPlugin(config -> config.endpointOverride(uri)) + .build(); + + URI endpointOverride = client.serviceClientConfiguration().endpointOverride().orElse(null); + assertThat(endpointOverride).isEqualTo(uri); + } + + @Test + void asyncClientWithoutEndpointOverride_serviceClientConfiguration_shouldReturnEmptyOptional() { + ProtocolRestXmlAsyncClient client = ProtocolRestXmlAsyncClient.builder() + .addPlugin(NOOP_PLUGIN) + .build(); + + URI endpointOverride = client.serviceClientConfiguration().endpointOverride().orElse(null); + assertThat(endpointOverride).isNull(); + } + + @Test + void asyncClient_serviceClientConfiguration_shouldReturnCorrectClientOverrideConfigurationFields() { + ClientOverrideConfiguration overrideConfiguration = ClientOverrideConfiguration.builder() + .apiCallAttemptTimeout(Duration.ofSeconds(30)) + .apiCallTimeout(Duration.ofSeconds(90)) + .retryPolicy(c -> c.numRetries(4)) + .build(); + + ProtocolRestXmlAsyncClient client = ProtocolRestXmlAsyncClient.builder() + .addPlugin(config -> config.overrideConfiguration(overrideConfiguration)) + .build(); + + ClientOverrideConfiguration result = client.serviceClientConfiguration().overrideConfiguration(); + assertThat(result.apiCallAttemptTimeout().get()).isEqualTo(Duration.ofSeconds(30)); + assertThat(result.apiCallTimeout().get()).isEqualTo(Duration.ofSeconds(90)); + assertThat(result.retryPolicy().get().numRetries()).isEqualTo(4); + assertThat(result.defaultProfileFile()).isNotPresent(); + assertThat(result.metricPublishers()).isEmpty(); + } + + @Test + void asyncClient_serviceClientConfiguration_withoutOverrideConfiguration_shouldReturnEmptyFields() { + ProtocolRestXmlAsyncClient client = ProtocolRestXmlAsyncClient.builder() + .addPlugin(NOOP_PLUGIN) + .build(); + + assertThat(client.serviceClientConfiguration().overrideConfiguration().toString()).isEqualTo( + "ClientOverrideConfiguration(headers={}, executionInterceptors=[], advancedOptions={})"); + ClientOverrideConfiguration result = client.serviceClientConfiguration().overrideConfiguration(); + assertThat(result.apiCallAttemptTimeout()).isNotPresent(); + assertThat(result.apiCallTimeout()).isNotPresent(); + assertThat(result.retryPolicy()).isNotPresent(); + assertThat(result.defaultProfileFile()).isNotPresent(); + assertThat(result.metricPublishers()).isEmpty(); + } + + + @Test + void asyncClientWithEndpointProvider_serviceClientConfiguration_shouldReturnCorrectEndpointProvider() { + ProtocolRestXmlEndpointProvider clientEndpointProvider = ProtocolRestXmlEndpointProvider.defaultProvider(); + SdkPlugin testPlugin = config -> config.endpointProvider(clientEndpointProvider); + ProtocolRestXmlAsyncClient client = ProtocolRestXmlAsyncClient.builder() + .addPlugin(testPlugin) + .build(); + + EndpointProvider endpointProvider = client.serviceClientConfiguration().endpointProvider().orElse(null); + assertThat(endpointProvider).isEqualTo(clientEndpointProvider); + } + + @Test + void asyncClientWithoutEndpointProvider_serviceClientConfiguration_shouldReturnDefault() { + ProtocolRestXmlAsyncClient client = ProtocolRestXmlAsyncClient.builder() + .addPlugin(NOOP_PLUGIN) + .build(); + + EndpointProvider endpointProvider = client.serviceClientConfiguration().endpointProvider().orElse(null); + assertThat(endpointProvider instanceof ProtocolRestXmlEndpointProvider).isTrue(); + } +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/testutil/MockIdentityProviderUtil.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/testutil/MockIdentityProviderUtil.java new file mode 100644 index 000000000000..9fb56df81f8d --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/testutil/MockIdentityProviderUtil.java @@ -0,0 +1,60 @@ +/* + * 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.testutil; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import java.util.concurrent.CompletableFuture; +import org.mockito.MockSettings; +import org.mockito.internal.creation.MockSettingsImpl; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.ResolveIdentityRequest; + +public class MockIdentityProviderUtil { + + public static IdentityProvider mockIdentityProvider() { + IdentityProvider mockIdentityProvider = mock(IdentityProvider.class, withSettings().lenient()); + setup(mockIdentityProvider); + return mockIdentityProvider; + } + + public static void setup(IdentityProvider mockIdentityProvider) { + when(mockIdentityProvider.resolveIdentity(any(ResolveIdentityRequest.class))).thenAnswer(invocation -> { + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException(ie); + } + return CompletableFuture.completedFuture(AwsBasicCredentials.create("foo", "bar")); + }); + when(mockIdentityProvider.resolveIdentity()).thenAnswer(invocation -> { + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException(ie); + } + return CompletableFuture.completedFuture(AwsBasicCredentials.create("foo", "bar")); + }); + when(mockIdentityProvider.identityType()).thenReturn(AwsCredentialsIdentity.class); + } +} diff --git a/test/http-client-tests/pom.xml b/test/http-client-tests/pom.xml index 201f90a2c5b3..478542521e74 100644 --- a/test/http-client-tests/pom.xml +++ b/test/http-client-tests/pom.xml @@ -21,7 +21,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml http-client-tests diff --git a/test/module-path-tests/pom.xml b/test/module-path-tests/pom.xml index cc1ac7fea3ee..f688db16b29a 100644 --- a/test/module-path-tests/pom.xml +++ b/test/module-path-tests/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/test/old-client-version-compatibility-test/pom.xml b/test/old-client-version-compatibility-test/pom.xml new file mode 100644 index 000000000000..4bccf78e23a7 --- /dev/null +++ b/test/old-client-version-compatibility-test/pom.xml @@ -0,0 +1,177 @@ + + + + + 4.0.0 + + aws-sdk-java-pom + software.amazon.awssdk + 2.21.0-SNAPSHOT + ../../pom.xml + + + old-client-version-compatibility-test + AWS Java SDK :: Test :: Old Client Version Compatibility Test + + Tests for old client versions with the latest "core" package versions. + + https://aws.amazon.com/sdkforjava + + + + + software.amazon.awssdk + bom + ${project.version} + pom + import + + + software.amazon.awssdk + bom-internal + ${project.version} + pom + import + + + + + + + + software.amazon.awssdk + sdk-core + ${awsjavasdk.version} + + + software.amazon.awssdk + aws-core + ${awsjavasdk.version} + + + software.amazon.awssdk + auth + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth + ${awsjavasdk.version} + + + software.amazon.awssdk + http-client-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + regions + ${awsjavasdk.version} + + + + + software.amazon.awssdk + aws-xml-protocol + ${awsjavasdk.version} + runtime + + + software.amazon.awssdk + protocol-core + ${awsjavasdk.version} + runtime + + + software.amazon.awssdk + arns + ${awsjavasdk.version} + runtime + + + + + software.amazon.awssdk + s3 + 2.20.136 + + + + + software.amazon.awssdk + service-test-utils + ${project.version} + + + org.junit.jupiter + junit-jupiter + test + + + com.github.tomakehurst + wiremock-jre8 + test + + + org.apache.logging.log4j + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j-impl + test + + + org.mockito + mockito-core + test + + + org.slf4j + jcl-over-slf4j + test + ${slf4j.version} + + + org.assertj + assertj-core + test + + + software.amazon.awssdk + test-utils + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + diff --git a/test/old-client-version-compatibility-test/src/it/java/software/amazon/awssdk/services/oldclient/S3PutGetIntegrationTest.java b/test/old-client-version-compatibility-test/src/it/java/software/amazon/awssdk/services/oldclient/S3PutGetIntegrationTest.java new file mode 100644 index 000000000000..30f742d7a54d --- /dev/null +++ b/test/old-client-version-compatibility-test/src/it/java/software/amazon/awssdk/services/oldclient/S3PutGetIntegrationTest.java @@ -0,0 +1,181 @@ +/* + * 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.oldclient; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.UUID; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; +import software.amazon.awssdk.auth.signer.AwsS3V4Signer; +import software.amazon.awssdk.core.ResponseBytes; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.S3AsyncClientBuilder; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3ClientBuilder; +import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; +import software.amazon.awssdk.services.s3.model.ChecksumMode; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.testutils.service.AwsTestBase; + +public class S3PutGetIntegrationTest extends AwsTestBase { + private static final S3Client S3 = + s3ClientBuilder().build(); + private static final S3Client S3_CUSTOM_SIGNER = + s3ClientBuilder().overrideConfiguration(c -> c.putAdvancedOption(SdkAdvancedClientOption.SIGNER, + AwsS3V4Signer.create())) + .build(); + private static final S3Client S3_NO_CREDS = + s3ClientBuilder().credentialsProvider(AnonymousCredentialsProvider.create()) + .build(); + + private static final S3AsyncClient S3_ASYNC = + getS3AsyncClientBuilder().build(); + private static final S3AsyncClient S3_ASYNC_CUSTOM_SIGNER = + getS3AsyncClientBuilder().overrideConfiguration(c -> c.putAdvancedOption(SdkAdvancedClientOption.SIGNER, + AwsS3V4Signer.create())) + .build(); + private static final S3AsyncClient S3_ASYNC_NO_CREDS = + getS3AsyncClientBuilder().credentialsProvider(AnonymousCredentialsProvider.create()) + .build(); + + private static final String BUCKET = "sra-get-put-integ-" + System.currentTimeMillis(); + private static final String BODY = "foo"; + private static final String BODY_CRC32 = "jHNlIQ=="; + + private String key; + + @BeforeAll + public static void setup() { + S3.createBucket(r -> r.bucket(BUCKET)); + } + + @BeforeEach + public void setupTest() { + key = UUID.randomUUID().toString(); + } + + @AfterAll + public static void teardown() { + TestUtils.deleteBucketAndAllContents(S3, BUCKET); + } + + @Test + public void putGet() { + S3.putObject(r -> r.bucket(BUCKET).key(key), RequestBody.fromString(BODY)); + assertThat(S3.getObjectAsBytes(r -> r.bucket(BUCKET).key(key)).asUtf8String()).isEqualTo(BODY); + } + + @Test + public void putGet_async() { + S3_ASYNC.putObject(r -> r.bucket(BUCKET).key(key), AsyncRequestBody.fromString(BODY)).join(); + assertThat(S3_ASYNC.getObject(r -> r.bucket(BUCKET).key(key), + AsyncResponseTransformer.toBytes()) + .join() + .asUtf8String()).isEqualTo(BODY); + } + + @Test + public void putGet_requestLevelCreds() { + S3_NO_CREDS.putObject(r -> r.bucket(BUCKET) + .key(key) + .overrideConfiguration(c -> c.credentialsProvider(CREDENTIALS_PROVIDER_CHAIN)), + RequestBody.fromString(BODY)); + assertThat(S3_NO_CREDS.getObjectAsBytes(r -> r.bucket(BUCKET) + .key(key) + .overrideConfiguration(c -> c.credentialsProvider(CREDENTIALS_PROVIDER_CHAIN))) + .asUtf8String()).isEqualTo(BODY); + } + + @Test + public void putGet_async_requestLevelCreds() { + S3_ASYNC_NO_CREDS.putObject(r -> r.bucket(BUCKET) + .key(key) + .overrideConfiguration(c -> c.credentialsProvider(CREDENTIALS_PROVIDER_CHAIN)), + AsyncRequestBody.fromString(BODY)) + .join(); + assertThat(S3_ASYNC_NO_CREDS.getObject(r -> r.bucket(BUCKET) + .key(key) + .overrideConfiguration(c -> c.credentialsProvider(CREDENTIALS_PROVIDER_CHAIN)), + AsyncResponseTransformer.toBytes()) + .join() + .asUtf8String()).isEqualTo(BODY); + } + + @Test + public void putGet_flexibleChecksums() { + S3.putObject(r -> r.bucket(BUCKET).key(key).checksumAlgorithm(ChecksumAlgorithm.CRC32), + RequestBody.fromString(BODY)); + ResponseBytes response = + S3.getObjectAsBytes(r -> r.bucket(BUCKET).key(key).checksumMode(ChecksumMode.ENABLED)); + assertThat(response.asUtf8String()).isEqualTo(BODY); + assertThat(response.response().checksumCRC32()).isEqualTo(BODY_CRC32); + } + + @Test + public void putGet_async_flexibleChecksums() { + S3_ASYNC.putObject(r -> r.bucket(BUCKET).key(key).checksumAlgorithm(ChecksumAlgorithm.CRC32), + AsyncRequestBody.fromString(BODY)).join(); + ResponseBytes response = S3_ASYNC.getObject(r -> r.bucket(BUCKET).key(key).checksumMode(ChecksumMode.ENABLED), + AsyncResponseTransformer.toBytes()) + .join(); + assertThat(response.asUtf8String()).isEqualTo(BODY); + assertThat(response.response().checksumCRC32()).isEqualTo(BODY_CRC32); + } + + @Test + public void putGet_customSigner_flexibleChecksums() { + S3_CUSTOM_SIGNER.putObject(r -> r.bucket(BUCKET).key(key).checksumAlgorithm(ChecksumAlgorithm.CRC32), + RequestBody.fromString(BODY)); + ResponseBytes response = + S3_CUSTOM_SIGNER.getObjectAsBytes(r -> r.bucket(BUCKET).key(key).checksumMode(ChecksumMode.ENABLED)); + assertThat(response.asUtf8String()).isEqualTo(BODY); + assertThat(response.response().checksumCRC32()).isEqualTo(BODY_CRC32); + } + + @Test + public void putGet_async_customSigner_flexibleChecksums() { + S3_ASYNC_CUSTOM_SIGNER.putObject(r -> r.bucket(BUCKET).key(key).checksumAlgorithm(ChecksumAlgorithm.CRC32), + AsyncRequestBody.fromString(BODY)) + .join(); + ResponseBytes response = + S3_ASYNC_CUSTOM_SIGNER.getObject(r -> r.bucket(BUCKET).key(key).checksumMode(ChecksumMode.ENABLED), + AsyncResponseTransformer.toBytes()) + .join(); + assertThat(response.asUtf8String()).isEqualTo(BODY); + assertThat(response.response().checksumCRC32()).isEqualTo(BODY_CRC32); + } + + private static S3ClientBuilder s3ClientBuilder() { + return S3Client.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER_CHAIN); + } + + private static S3AsyncClientBuilder getS3AsyncClientBuilder() { + return S3AsyncClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER_CHAIN); + } +} diff --git a/test/old-client-version-compatibility-test/src/it/java/software/amazon/awssdk/services/oldclient/TestUtils.java b/test/old-client-version-compatibility-test/src/it/java/software/amazon/awssdk/services/oldclient/TestUtils.java new file mode 100644 index 000000000000..6a1bb1e4cec7 --- /dev/null +++ b/test/old-client-version-compatibility-test/src/it/java/software/amazon/awssdk/services/oldclient/TestUtils.java @@ -0,0 +1,86 @@ +/* + * 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.oldclient; + +import java.util.Iterator; +import java.util.List; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.ListObjectVersionsRequest; +import software.amazon.awssdk.services.s3.model.ListObjectVersionsResponse; +import software.amazon.awssdk.services.s3.model.ListObjectsRequest; +import software.amazon.awssdk.services.s3.model.ListObjectsResponse; +import software.amazon.awssdk.services.s3.model.NoSuchBucketException; +import software.amazon.awssdk.services.s3.model.S3Object; +import software.amazon.awssdk.testutils.Waiter; + +public class TestUtils { + + public static void deleteBucketAndAllContents(S3Client s3, String bucketName) { + try { + System.out.println("Deleting S3 bucket: " + bucketName); + ListObjectsResponse response = Waiter.run(() -> s3.listObjects(r -> r.bucket(bucketName))) + .ignoringException(NoSuchBucketException.class) + .orFail(); + List objectListing = response.contents(); + + if (objectListing != null) { + while (true) { + for (Iterator iterator = objectListing.iterator(); iterator.hasNext(); ) { + S3Object objectSummary = (S3Object) iterator.next(); + s3.deleteObject(DeleteObjectRequest.builder().bucket(bucketName).key(objectSummary.key()).build()); + } + + if (response.isTruncated()) { + objectListing = s3.listObjects(ListObjectsRequest.builder() + .bucket(bucketName) + .marker(response.marker()) + .build()) + .contents(); + } else { + break; + } + } + } + + + ListObjectVersionsResponse versions = s3 + .listObjectVersions(ListObjectVersionsRequest.builder().bucket(bucketName).build()); + + if (versions.deleteMarkers() != null) { + versions.deleteMarkers().forEach(v -> s3.deleteObject(DeleteObjectRequest.builder() + .versionId(v.versionId()) + .bucket(bucketName) + .key(v.key()) + .build())); + } + + if (versions.versions() != null) { + versions.versions().forEach(v -> s3.deleteObject(DeleteObjectRequest.builder() + .versionId(v.versionId()) + .bucket(bucketName) + .key(v.key()) + .build())); + } + + s3.deleteBucket(DeleteBucketRequest.builder().bucket(bucketName).build()); + } catch (Exception e) { + System.err.println("Failed to delete bucket: " + bucketName); + e.printStackTrace(); + } + } +} diff --git a/test/old-client-version-compatibility-test/src/it/resources/log4j2.properties b/test/old-client-version-compatibility-test/src/it/resources/log4j2.properties new file mode 100644 index 000000000000..340e7c449d54 --- /dev/null +++ b/test/old-client-version-compatibility-test/src/it/resources/log4j2.properties @@ -0,0 +1,38 @@ +# +# 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. +# + +status = warn + +appender.console.type = Console +appender.console.name = ConsoleAppender +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n%throwable + +rootLogger.level = debug +rootLogger.appenderRef.stdout.ref = ConsoleAppender + +# Uncomment below to enable more specific logging +# +#logger.sdk.name = software.amazon.awssdk +#logger.sdk.level = debug +# +#logger.request.name = software.amazon.awssdk.request +#logger.request.level = debug +# +#logger.apache.name = org.apache.http.wire +#logger.apache.level = debug +# +#logger.netty.name = io.netty.handler.logging +#logger.netty.level = debug diff --git a/test/old-client-version-compatibility-test/src/test/java/InterceptorSignerAttributeTest.java b/test/old-client-version-compatibility-test/src/test/java/InterceptorSignerAttributeTest.java new file mode 100644 index 000000000000..58b525e89528 --- /dev/null +++ b/test/old-client-version-compatibility-test/src/test/java/InterceptorSignerAttributeTest.java @@ -0,0 +1,245 @@ +/* + * 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. + */ + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttribute; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.S3AsyncClientBuilder; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3ClientBuilder; +import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; +import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; + +/** + * Ensure that attributes set in execution interceptors are passed to custom signers. These are protected APIs, but code + * searches show that customers are using them as if they aren't. We should push customers onto supported paths. + */ +public class InterceptorSignerAttributeTest { + private static final AwsCredentials CREDS = AwsBasicCredentials.create("akid", "skid"); + + @Test + public void canSetSignerExecutionAttributes_beforeExecution() { + test(attributeModifications -> new ExecutionInterceptor() { + @Override + public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + attributeModifications.accept(executionAttributes); + } + }, + AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, // Endpoint rules override signing name + AwsSignerExecutionAttribute.SIGNING_REGION, // Endpoint rules override signing region + AwsSignerExecutionAttribute.AWS_CREDENTIALS, // Legacy auth strategy overrides credentials + AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE); // Endpoint rules override double-url-encode + } + + @Test + public void canSetSignerExecutionAttributes_modifyRequest() { + test(attributeModifications -> new ExecutionInterceptor() { + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + attributeModifications.accept(executionAttributes); + return context.request(); + } + }, + AwsSignerExecutionAttribute.AWS_CREDENTIALS); // Legacy auth strategy overrides credentials + } + + @Test + public void canSetSignerExecutionAttributes_beforeMarshalling() { + test(attributeModifications -> new ExecutionInterceptor() { + @Override + public void beforeMarshalling(Context.BeforeMarshalling context, ExecutionAttributes executionAttributes) { + attributeModifications.accept(executionAttributes); + } + }); + } + + @Test + public void canSetSignerExecutionAttributes_afterMarshalling() { + test(attributeModifications -> new ExecutionInterceptor() { + @Override + public void afterMarshalling(Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { + attributeModifications.accept(executionAttributes); + } + }); + } + + @Test + public void canSetSignerExecutionAttributes_modifyHttpRequest() { + test(attributeModifications -> new ExecutionInterceptor() { + @Override + public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { + attributeModifications.accept(executionAttributes); + return context.httpRequest(); + } + }); + } + + private void test(Function, ExecutionInterceptor> interceptorFactory, + ExecutionAttribute... attributesToExcludeFromTest) { + Set> attributesToExclude = new HashSet<>(Arrays.asList(attributesToExcludeFromTest)); + + ExecutionInterceptor interceptor = interceptorFactory.apply(executionAttributes -> { + executionAttributes.putAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, "signing-name"); + executionAttributes.putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, Region.of("signing-region")); + executionAttributes.putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, CREDS); + executionAttributes.putAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE, true); + executionAttributes.putAttribute(AwsSignerExecutionAttribute.SIGNER_NORMALIZE_PATH, true); + }); + + ClientOverrideConfiguration.Builder configBuilder = + ClientOverrideConfiguration.builder() + .addExecutionInterceptor(interceptor); + + try (MockSyncHttpClient httpClient = new MockSyncHttpClient(); + MockAsyncHttpClient asyncHttpClient = new MockAsyncHttpClient()) { + stub200Responses(httpClient, asyncHttpClient); + + S3ClientBuilder s3Builder = createS3Builder(configBuilder, httpClient); + S3AsyncClientBuilder s3AsyncBuilder = createS3AsyncBuilder(configBuilder, asyncHttpClient); + + CapturingSigner signer1 = new CapturingSigner(); + try (S3Client s3 = s3Builder.overrideConfiguration(configBuilder.putAdvancedOption(SdkAdvancedClientOption.SIGNER, + signer1) + .build()) + .build()) { + callS3(s3); + validateLegacySignRequest(attributesToExclude, signer1); + } + + CapturingSigner signer2 = new CapturingSigner(); + try (S3AsyncClient s3 = + s3AsyncBuilder.overrideConfiguration(configBuilder.putAdvancedOption(SdkAdvancedClientOption.SIGNER, signer2) + .build()) + .build()) { + callS3(s3); + validateLegacySignRequest(attributesToExclude, signer2); + } + } + } + + private static void stub200Responses(MockSyncHttpClient httpClient, MockAsyncHttpClient asyncHttpClient) { + HttpExecuteResponse response = + HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder() + .statusCode(200) + .build()) + .build(); + httpClient.stubResponses(response); + asyncHttpClient.stubResponses(response); + } + + private static S3ClientBuilder createS3Builder(ClientOverrideConfiguration.Builder configBuilder, MockSyncHttpClient httpClient) { + return S3Client.builder() + .region(Region.US_WEST_2) + .credentialsProvider(AnonymousCredentialsProvider.create()) + .httpClient(httpClient) + .overrideConfiguration(configBuilder.build()); + } + + private static S3AsyncClientBuilder createS3AsyncBuilder(ClientOverrideConfiguration.Builder configBuilder, MockAsyncHttpClient asyncHttpClient) { + return S3AsyncClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(AnonymousCredentialsProvider.create()) + .httpClient(asyncHttpClient) + .overrideConfiguration(configBuilder.build()); + } + + private static void callS3(S3Client s3) { + s3.putObject(r -> r.bucket("foo") + .key("bar") + .checksumAlgorithm(ChecksumAlgorithm.CRC32), + RequestBody.fromString("text")); + } + + private void callS3(S3AsyncClient s3) { + s3.putObject(r -> r.bucket("foo") + .key("bar") + .checksumAlgorithm(ChecksumAlgorithm.CRC32), + AsyncRequestBody.fromString("text")) + .join(); + } + + private void validateLegacySignRequest(Set> attributesToExclude, CapturingSigner signer) { + if (!attributesToExclude.contains(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME)) { + assertThat(signer.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME)) + .isEqualTo("signing-name"); + } + if (!attributesToExclude.contains(AwsSignerExecutionAttribute.SIGNING_REGION)) { + assertThat(signer.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION)) + .isEqualTo(Region.of("signing-region")); + } + if (!attributesToExclude.contains(AwsSignerExecutionAttribute.AWS_CREDENTIALS)) { + assertThat(signer.executionAttributes.getAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS)) + .isEqualTo(CREDS); + } + if (!attributesToExclude.contains(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE)) { + assertThat(signer.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE)) + .isEqualTo(true); + } + if (!attributesToExclude.contains(AwsSignerExecutionAttribute.SIGNER_NORMALIZE_PATH)) { + assertThat(signer.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNER_NORMALIZE_PATH)) + .isEqualTo(true); + } + } + + private static class CapturingSigner implements Signer { + private ExecutionAttributes executionAttributes; + + @Override + public SdkHttpFullRequest sign(SdkHttpFullRequest request, ExecutionAttributes executionAttributes) { + this.executionAttributes = executionAttributes.copy(); + return request; + } + } +} diff --git a/test/old-client-version-compatibility-test/src/test/java/S3AnonymousTest.java b/test/old-client-version-compatibility-test/src/test/java/S3AnonymousTest.java new file mode 100644 index 000000000000..72e6baba808a --- /dev/null +++ b/test/old-client-version-compatibility-test/src/test/java/S3AnonymousTest.java @@ -0,0 +1,105 @@ +/* + * 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. + */ + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; + +/** + * Ensure that we can make anonymous requests using S3. + */ +public class S3AnonymousTest { + private MockSyncHttpClient httpClient; + private MockAsyncHttpClient asyncHttpClient; + private S3Client s3; + private S3AsyncClient s3Async; + + @BeforeEach + public void setup() { + this.httpClient = new MockSyncHttpClient(); + this.httpClient.stubNextResponse200(); + + this.asyncHttpClient = new MockAsyncHttpClient(); + this.asyncHttpClient.stubNextResponse200(); + + this.s3 = S3Client.builder() + .region(Region.US_WEST_2) + .credentialsProvider(AnonymousCredentialsProvider.create()) + .httpClient(httpClient) + .build(); + + this.s3Async = S3AsyncClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(AnonymousCredentialsProvider.create()) + .httpClient(asyncHttpClient) + .build(); + } + + @AfterEach + public void teardown() { + httpClient.close(); + asyncHttpClient.close(); + s3.close(); + s3Async.close(); + } + + @Test + public void nonStreamingOperations_canBeAnonymous() { + s3.listBuckets(); + assertThat(httpClient.getLastRequest().firstMatchingHeader("Authorization")).isEmpty(); + } + + @Test + public void nonStreamingOperations_async_canBeAnonymous() { + s3Async.listBuckets().join(); + assertThat(asyncHttpClient.getLastRequest().firstMatchingHeader("Authorization")).isEmpty(); + } + + @Test + public void streamingWriteOperations_canBeAnonymous() { + s3.putObject(r -> r.bucket("bucket").key("key"), RequestBody.fromString("foo")); + assertThat(httpClient.getLastRequest().firstMatchingHeader("Authorization")).isEmpty(); + } + + @Test + public void streamingWriteOperations_async_canBeAnonymous() { + s3Async.putObject(r -> r.bucket("bucket").key("key"), AsyncRequestBody.fromString("foo")).join(); + assertThat(asyncHttpClient.getLastRequest().firstMatchingHeader("Authorization")).isEmpty(); + } + + @Test + public void streamingReadOperations_canBeAnonymous() { + s3.getObject(r -> r.bucket("bucket").key("key"), ResponseTransformer.toBytes()); + assertThat(httpClient.getLastRequest().firstMatchingHeader("Authorization")).isEmpty(); + } + + @Test + public void streamingReadOperations_async_canBeAnonymous() { + s3Async.getObject(r -> r.bucket("bucket").key("key"), AsyncResponseTransformer.toBytes()).join(); + assertThat(asyncHttpClient.getLastRequest().firstMatchingHeader("Authorization")).isEmpty(); + } +} diff --git a/test/protocol-tests-core/pom.xml b/test/protocol-tests-core/pom.xml index 2831c07aa784..25c9a1ae4255 100644 --- a/test/protocol-tests-core/pom.xml +++ b/test/protocol-tests-core/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/test/protocol-tests/pom.xml b/test/protocol-tests/pom.xml index 3f83430aed20..c8cb868f5329 100644 --- a/test/protocol-tests/pom.xml +++ b/test/protocol-tests/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml 4.0.0 @@ -98,6 +98,21 @@ auth ${awsjavasdk.version} + + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + http-auth-aws + ${awsjavasdk.version} + software.amazon.awssdk regions @@ -118,6 +133,11 @@ endpoints-spi ${awsjavasdk.version} + + software.amazon.awssdk + profiles + ${awsjavasdk.version} + diff --git a/test/region-testing/pom.xml b/test/region-testing/pom.xml index 7c792b44b257..81ec3b2167c8 100644 --- a/test/region-testing/pom.xml +++ b/test/region-testing/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/test/ruleset-testing-core/pom.xml b/test/ruleset-testing-core/pom.xml index a06a4aa03a7b..0930024448ad 100644 --- a/test/ruleset-testing-core/pom.xml +++ b/test/ruleset-testing-core/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/test/s3-benchmarks/pom.xml b/test/s3-benchmarks/pom.xml index 8936f25941ce..fdacd46e2602 100644 --- a/test/s3-benchmarks/pom.xml +++ b/test/s3-benchmarks/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/test/sdk-benchmarks/pom.xml b/test/sdk-benchmarks/pom.xml index 719415339302..cc1eb5949e8c 100644 --- a/test/sdk-benchmarks/pom.xml +++ b/test/sdk-benchmarks/pom.xml @@ -19,7 +19,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml diff --git a/test/sdk-native-image-test/pom.xml b/test/sdk-native-image-test/pom.xml index c2395b71a68a..b3b8c8308baa 100644 --- a/test/sdk-native-image-test/pom.xml +++ b/test/sdk-native-image-test/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/test/service-test-utils/pom.xml b/test/service-test-utils/pom.xml index fa96a7a44e85..51224cb60414 100644 --- a/test/service-test-utils/pom.xml +++ b/test/service-test-utils/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml service-test-utils @@ -63,6 +63,11 @@ auth ${awsjavasdk.version} + + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + software.amazon.awssdk http-client-spi diff --git a/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/AwsIntegrationTestBase.java b/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/AwsIntegrationTestBase.java index 2b9de9dd2b03..70c9564fd2ae 100644 --- a/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/AwsIntegrationTestBase.java +++ b/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/AwsIntegrationTestBase.java @@ -19,11 +19,12 @@ import java.io.InputStream; import reactor.blockhound.BlockHound; import software.amazon.awssdk.auth.credentials.AwsCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.utils.IoUtils; public abstract class AwsIntegrationTestBase { @@ -57,9 +58,9 @@ protected static AwsCredentials getCredentials() { } /** - * @return AwsCredentialsProvider to use during tests. Setup by base fixture + * @return {@link IdentityProvider} to use during tests. Setup by base fixture */ - protected static AwsCredentialsProvider getCredentialsProvider() { + protected static IdentityProvider getCredentialsProvider() { return StaticCredentialsProvider.create(CREDENTIALS); } diff --git a/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/http/MockHttpClient.java b/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/http/MockHttpClient.java index 05f3e3c76301..be7c791f88a2 100644 --- a/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/http/MockHttpClient.java +++ b/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/http/MockHttpClient.java @@ -19,6 +19,7 @@ import java.util.List; import software.amazon.awssdk.http.HttpExecuteResponse; import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.SdkHttpResponse; import software.amazon.awssdk.utils.Pair; public interface MockHttpClient { @@ -32,6 +33,15 @@ public interface MockHttpClient { */ void stubNextResponse(HttpExecuteResponse nextResponse); + default void stubNextResponse200() { + stubNextResponse(HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder() + .statusCode(200) + .putHeader("Content-Length", "0") + .build()) + .build()); + } + /** * Sets up the next HTTP response that will be returned by the mock with a delay. Removes responses previously added to the * mock. diff --git a/test/stability-tests/pom.xml b/test/stability-tests/pom.xml index 748a7a50d4a2..8e8a53572704 100644 --- a/test/stability-tests/pom.xml +++ b/test/stability-tests/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/test/test-utils/pom.xml b/test/test-utils/pom.xml index 7559d0fc26e5..d4b6c2904353 100644 --- a/test/test-utils/pom.xml +++ b/test/test-utils/pom.xml @@ -21,7 +21,7 @@ software.amazon.awssdk aws-sdk-java-pom - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml test-utils diff --git a/test/tests-coverage-reporting/pom.xml b/test/tests-coverage-reporting/pom.xml index f5bcfee06009..c9d439be4906 100644 --- a/test/tests-coverage-reporting/pom.xml +++ b/test/tests-coverage-reporting/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT ../../pom.xml 4.0.0 @@ -37,6 +37,16 @@ software.amazon.awssdk ${awsjavasdk.version} + + checksums-spi + software.amazon.awssdk + ${awsjavasdk.version} + + + checksums + software.amazon.awssdk + ${awsjavasdk.version} + utils software.amazon.awssdk @@ -47,6 +57,36 @@ software.amazon.awssdk ${awsjavasdk.version} + + identity-spi + software.amazon.awssdk + ${awsjavasdk.version} + + + http-auth-spi + software.amazon.awssdk + ${awsjavasdk.version} + + + http-auth + software.amazon.awssdk + ${awsjavasdk.version} + + + http-auth-aws + software.amazon.awssdk + ${awsjavasdk.version} + + + http-auth-aws-crt + software.amazon.awssdk + ${awsjavasdk.version} + + + http-auth-aws-eventstream + software.amazon.awssdk + ${awsjavasdk.version} + aws-core software.amazon.awssdk @@ -157,6 +197,11 @@ software.amazon.awssdk ${awsjavasdk.version} + + old-client-version-compatibility-test + software.amazon.awssdk + ${awsjavasdk.version} + dynamodb-enhanced software.amazon.awssdk diff --git a/third-party/pom.xml b/third-party/pom.xml index bcdc27514f1f..056417082cc6 100644 --- a/third-party/pom.xml +++ b/third-party/pom.xml @@ -21,7 +21,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT third-party diff --git a/third-party/third-party-jackson-core/pom.xml b/third-party/third-party-jackson-core/pom.xml index 86373b20c832..98469327ed98 100644 --- a/third-party/third-party-jackson-core/pom.xml +++ b/third-party/third-party-jackson-core/pom.xml @@ -20,7 +20,7 @@ third-party software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/third-party/third-party-jackson-dataformat-cbor/pom.xml b/third-party/third-party-jackson-dataformat-cbor/pom.xml index 1dcca8d63c3c..f6289961d882 100644 --- a/third-party/third-party-jackson-dataformat-cbor/pom.xml +++ b/third-party/third-party-jackson-dataformat-cbor/pom.xml @@ -20,7 +20,7 @@ third-party software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/utils/pom.xml b/utils/pom.xml index dfd5b1a88d09..020de0a4e714 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -20,7 +20,7 @@ aws-sdk-java-pom software.amazon.awssdk - 2.20.163-SNAPSHOT + 2.21.0-SNAPSHOT 4.0.0 diff --git a/utils/src/main/java/software/amazon/awssdk/utils/CompletableFutureUtils.java b/utils/src/main/java/software/amazon/awssdk/utils/CompletableFutureUtils.java index 97129ac2df1a..410bfb50a42a 100644 --- a/utils/src/main/java/software/amazon/awssdk/utils/CompletableFutureUtils.java +++ b/utils/src/main/java/software/amazon/awssdk/utils/CompletableFutureUtils.java @@ -239,4 +239,22 @@ public static void joinInterruptiblyIgnoringFailures(CompletableFuture future // Ignore } } + + /** + * Joins (interruptibly) on the future, and re-throws any RuntimeExceptions or Errors just like the async task would have + * thrown if it was executed synchronously. + */ + public static T joinLikeSync(CompletableFuture future) { + try { + return joinInterruptibly(future); + } catch (CompletionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + // Make sure we don't lose the context of where the join is in the stack... + cause.addSuppressed(new RuntimeException("Task failed.")); + throw (RuntimeException) cause; + } + throw e; + } + } } diff --git a/utils/src/test/java/software/amazon/awssdk/utils/CompletableFutureUtilsTest.java b/utils/src/test/java/software/amazon/awssdk/utils/CompletableFutureUtilsTest.java index 2f2ed5e9acf2..2b6fe49e2cc1 100644 --- a/utils/src/test/java/software/amazon/awssdk/utils/CompletableFutureUtilsTest.java +++ b/utils/src/test/java/software/amazon/awssdk/utils/CompletableFutureUtilsTest.java @@ -19,7 +19,9 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.fail; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.AfterClass; @@ -159,4 +161,49 @@ public void allOfExceptionForwarded_allFutureSucceed_shouldComplete() { assertThat(resultFuture.isDone()).isTrue(); assertThat(resultFuture.isCompletedExceptionally()).isFalse(); } -} + + @Test(timeout = 1000) + public void joinLikeSync_completesExceptionally_throwsUnderlyingException() { + Exception e = new RuntimeException("BOOM"); + CompletableFuture future = new CompletableFuture(); + future.completeExceptionally(e); + + assertThatThrownBy(() -> CompletableFutureUtils.joinLikeSync(future)) + .hasSuppressedException(new RuntimeException("Task failed.")) + .isEqualTo(e); + } + + @Test(timeout = 1000) + public void joinLikeSync_completesExceptionallyChecked_throwsCompletionException() { + Exception e = new Exception("BOOM"); + CompletableFuture future = new CompletableFuture(); + future.completeExceptionally(e); + + assertThatThrownBy(() -> CompletableFutureUtils.joinLikeSync(future)) + .hasNoSuppressedExceptions() + .hasCause(e) + .isInstanceOf(CompletionException.class); + } + + @Test(timeout = 1000) + public void joinLikeSync_completesExceptionallyWithError_throwsError() { + Error e = new Error("BOOM"); + CompletableFuture future = new CompletableFuture(); + future.completeExceptionally(e); + + assertThatThrownBy(() -> CompletableFutureUtils.joinLikeSync(future)) + .hasNoSuppressedExceptions() + .isEqualTo(e); + } + + @Test(timeout = 1000) + public void joinLikeSync_canceled_throwsCancellationException() { + Exception e = new Exception("BOOM"); + CompletableFuture future = new CompletableFuture(); + future.cancel(false); + + assertThatThrownBy(() -> CompletableFutureUtils.joinLikeSync(future)) + .hasNoSuppressedExceptions() + .hasNoCause() + .isInstanceOf(CancellationException.class); + }} diff --git a/utils/src/test/java/software/amazon/awssdk/utils/async/FlatteningSubscriberTest.java b/utils/src/test/java/software/amazon/awssdk/utils/async/FlatteningSubscriberTest.java index 6993927924d7..4ca3592f6a32 100644 --- a/utils/src/test/java/software/amazon/awssdk/utils/async/FlatteningSubscriberTest.java +++ b/utils/src/test/java/software/amazon/awssdk/utils/async/FlatteningSubscriberTest.java @@ -15,8 +15,10 @@ package software.amazon.awssdk.utils.async; +import static java.time.temporal.ChronoUnit.SECONDS; import static org.mockito.Mockito.times; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -205,8 +207,9 @@ public void requestsFromDownstreamDoNothingAfterOnError() { @Test public void stochastic_dataFlushedBeforeOnComplete() { ExecutorService exec = Executors.newSingleThreadExecutor(); + Instant end = Instant.now().plus(10, SECONDS); try { - for (int i = 0; i < 30_000_000; ++i) { + while (Instant.now().isBefore(end)) { Publisher> iterablePublisher = subscriber -> subscriber.onSubscribe(new Subscription() { @Override public void request(long l) {