From e01cf36598dccda495caccc5b2cdc4c517395e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sugawara=20=28=E2=88=A9=EF=BD=80-=C2=B4=29?= =?UTF-8?q?=E2=8A=83=E2=94=81=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E?= Date: Fri, 26 Jan 2024 09:37:17 -0800 Subject: [PATCH 1/2] Configure modeled signer properties for endpoint based auth scheme resolver --- .../codegen/model/service/AuthType.java | 4 + .../scheme/AuthSchemeCodegenMetadata.java | 87 +++++- .../poet/auth/scheme/AuthSchemeSpecUtils.java | 150 +++++++++- .../poet/auth/scheme/AuthTypeDefaults.java | 33 +++ .../AuthTypeDefaultsToCodegenMetadata.java | 28 ++ .../auth/scheme/AuthTypeToSigV4Default.java | 164 ++++++++++ .../EndpointBasedAuthSchemeProviderSpec.java | 181 ++++++++--- .../ModelBasedAuthSchemeProviderSpec.java | 19 +- .../poet/auth/scheme/SigV4SignerDefaults.java | 240 +++++++++++++++ .../poet/builder/BaseClientBuilderClass.java | 25 +- .../builder/BaseClientBuilderClassTest.java | 58 +++- .../scheme/query-auth-scheme-interceptor.java | 9 +- ...e-endpoint-provider-without-allowlist.java | 20 +- ...-params-auth-scheme-endpoint-provider.java | 20 +- ...ith-allowlist-auth-scheme-interceptor.java | 9 +- ...out-allowlist-auth-scheme-interceptor.java | 9 +- ...test-bearer-auth-client-builder-class.java | 4 +- .../sra/test-client-builder-class.java | 4 +- ...-client-builder-endpoints-auth-params.java | 4 +- ...lient-builder-internal-defaults-class.java | 4 +- ...-composed-sync-default-client-builder.java | 4 +- ...test-no-auth-ops-client-builder-class.java | 4 +- ...-no-auth-service-client-builder-class.java | 4 +- .../sra/test-query-client-builder-class.java | 4 +- ...test-bearer-auth-client-builder-class.java | 4 +- .../builder/test-client-builder-class.java | 4 +- ...-client-builder-endpoints-auth-params.java | 4 +- ...lient-builder-internal-defaults-class.java | 4 +- ...-composed-sync-default-client-builder.java | 4 +- ...test-no-auth-ops-client-builder-class.java | 4 +- ...-no-auth-service-client-builder-class.java | 4 +- .../test-query-client-builder-class.java | 4 +- .../aws/signer/AwsV4FamilyHttpSigner.java | 8 + .../auth/spi/scheme/AuthSchemeOption.java | 2 - .../AsyncHttpChecksumIntegrationTest.java | 10 +- .../s3express/S3ExpressIntegrationTest.java | 4 +- .../signer/PayloadSigningIntegrationTest.java | 8 +- .../S3ExpressChecksumInterceptor.java | 7 +- ...isableChunkEncodingIfConfiguredPlugin.java | 87 ++++++ .../S3OverrideAuthSchemePropertiesPlugin.java | 180 +++++++++++ ...isableChunkEncodingAuthSchemeProvider.java | 62 ++++ .../S3ExpressAuthSchemeProvider.java | 12 +- .../codegen-resources/customization.config | 6 +- .../s3/PayloadSigningDisabledTest.java | 11 +- .../awssdk/services/s3/S3SignerTest.java | 51 +++- .../s3/functionaltests/S3ExpressTest.java | 227 +++++++++++++- ...leChunkEncodingIfConfiguredPluginTest.java | 280 ++++++++++++++++++ ...teRequestBodyIfNeededInterceptorTest.java} | 2 +- 48 files changed, 1889 insertions(+), 188 deletions(-) create mode 100644 codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeDefaults.java create mode 100644 codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeDefaultsToCodegenMetadata.java create mode 100644 codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeToSigV4Default.java create mode 100644 codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/SigV4SignerDefaults.java create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPlugin.java create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3OverrideAuthSchemePropertiesPlugin.java create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/s3express/S3DisableChunkEncodingAuthSchemeProvider.java create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPluginTest.java rename services/s3control/src/test/java/software/amazon/awssdk/services/s3control/internal/interceptors/{PayloadSigningInterceptorTest.java => CreateRequestBodyIfNeededInterceptorTest.java} (98%) diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/AuthType.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/AuthType.java index 1b1a73d5728f..b7cd768456c6 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/AuthType.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/AuthType.java @@ -36,6 +36,10 @@ public enum AuthType { this.value = value; } + public String value() { + return value; + } + public static AuthType fromValue(String value) { String normalizedValue = StringUtils.lowerCase(value); return Arrays.stream(values()) 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 index 90149c984fa5..95656f067956 100644 --- 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 @@ -19,7 +19,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.function.BiConsumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; 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; @@ -49,7 +52,7 @@ public final class AuthSchemeCodegenMetadata { .addProperty(SignerPropertyValueProvider.builder() .containingClass(AwsV4HttpSigner.class) .fieldName("PAYLOAD_SIGNING_ENABLED") - .valueEmitter((spec, utils) -> spec.addCode("$L", false)) + .constantValueSupplier(() -> false) .build()) .build(); @@ -58,17 +61,17 @@ public final class AuthSchemeCodegenMetadata { .addProperty(SignerPropertyValueProvider.builder() .containingClass(AwsV4HttpSigner.class) .fieldName("DOUBLE_URL_ENCODE") - .valueEmitter((spec, utils) -> spec.addCode("$L", "false")) + .constantValueSupplier(() -> false) .build()) .addProperty(SignerPropertyValueProvider.builder() .containingClass(AwsV4HttpSigner.class) .fieldName("NORMALIZE_PATH") - .valueEmitter((spec, utils) -> spec.addCode("$L", "false")) + .constantValueSupplier(() -> false) .build()) .addProperty(SignerPropertyValueProvider.builder() .containingClass(AwsV4HttpSigner.class) .fieldName("PAYLOAD_SIGNING_ENABLED") - .valueEmitter((spec, utils) -> spec.addCode("$L", false)) + .constantValueSupplier(() -> false) .build()) .build(); @@ -77,12 +80,12 @@ public final class AuthSchemeCodegenMetadata { .addProperty(SignerPropertyValueProvider.builder() .containingClass(AwsV4HttpSigner.class) .fieldName("DOUBLE_URL_ENCODE") - .valueEmitter((spec, utils) -> spec.addCode("$L", "false")) + .constantValueSupplier(() -> false) .build()) .addProperty(SignerPropertyValueProvider.builder() .containingClass(AwsV4HttpSigner.class) .fieldName("NORMALIZE_PATH") - .valueEmitter((spec, utils) -> spec.addCode("$L", "false")) + .constantValueSupplier(() -> false) .build()) .build(); @@ -126,6 +129,43 @@ private static Builder builder() { return new Builder(); } + + /** + * Transforms a {@link SigV4SignerDefaults} instance to an {@link AuthSchemeCodegenMetadata} instance. + */ + public static AuthSchemeCodegenMetadata fromConstants(SigV4SignerDefaults constants) { + AuthSchemeCodegenMetadata.Builder builder = SIGV4.toBuilder(); + for (SignerPropertyValueProvider property : propertiesFromConstants(constants)) { + builder.addProperty(property); + } + return builder.build(); + } + + public static List propertiesFromConstants(SigV4SignerDefaults constants) { + List properties = new ArrayList<>(); + if (constants.payloadSigningEnabled() != null) { + properties.add(from("PAYLOAD_SIGNING_ENABLED", constants::payloadSigningEnabled)); + } + if (constants.doubleUrlEncode() != null) { + properties.add(from("DOUBLE_URL_ENCODE", constants::doubleUrlEncode)); + } + if (constants.normalizePath() != null) { + properties.add(from("NORMALIZE_PATH", constants::normalizePath)); + } + if (constants.chunkEncodingEnabled() != null) { + properties.add(from("CHUNK_ENCODING_ENABLED", constants::chunkEncodingEnabled)); + } + return properties; + } + + private static SignerPropertyValueProvider from(String name, Supplier valueSupplier) { + return SignerPropertyValueProvider.builder() + .containingClass(AwsV4HttpSigner.class) + .fieldName(name) + .constantValueSupplier(valueSupplier) + .build(); + } + public static AuthSchemeCodegenMetadata fromAuthType(AuthType type) { switch (type) { case BEARER: @@ -145,7 +185,15 @@ public static AuthSchemeCodegenMetadata fromAuthType(AuthType type) { } } - private static class Builder { + public static Map constantProperties(AuthSchemeCodegenMetadata metadata) { + return metadata + .properties() + .stream() + .filter(SignerPropertyValueProvider::isConstant) + .collect(Collectors.toMap(SignerPropertyValueProvider::fieldName, SignerPropertyValueProvider::value)); + } + + public static class Builder { private String schemeId; private List properties = new ArrayList<>(); private Class authSchemeClass; @@ -169,6 +217,12 @@ public Builder addProperty(SignerPropertyValueProvider property) { return this; } + public Builder properties(List properties) { + this.properties.clear(); + this.properties.addAll(properties); + return this; + } + public Builder authSchemeClass(Class authSchemeClass) { this.authSchemeClass = authSchemeClass; return this; @@ -183,11 +237,13 @@ static class SignerPropertyValueProvider { private final Class containingClass; private final String fieldName; private final BiConsumer valueEmitter; + private final Supplier valueSupplier; SignerPropertyValueProvider(Builder builder) { this.containingClass = Validate.paramNotNull(builder.containingClass, "containingClass"); this.valueEmitter = Validate.paramNotNull(builder.valueEmitter, "valueEmitter"); this.fieldName = Validate.paramNotNull(builder.fieldName, "fieldName"); + this.valueSupplier = builder.valueSupplier; } public Class containingClass() { @@ -198,6 +254,14 @@ public String fieldName() { return fieldName; } + public boolean isConstant() { + return valueSupplier != null; + } + + public Object value() { + return valueSupplier.get(); + } + public void emitValue(MethodSpec.Builder spec, AuthSchemeSpecUtils utils) { valueEmitter.accept(spec, utils); } @@ -210,6 +274,7 @@ static class Builder { private Class containingClass; private String fieldName; private BiConsumer valueEmitter; + private Supplier valueSupplier; public Builder containingClass(Class containingClass) { this.containingClass = containingClass; @@ -226,6 +291,14 @@ public Builder valueEmitter(BiConsumer return this; } + public Builder constantValueSupplier(Supplier valueSupplier) { + this.valueSupplier = valueSupplier; + if (valueEmitter == null) { + valueEmitter = (spec, utils) -> spec.addCode("$L", valueSupplier.get()); + } + return this; + } + public SignerPropertyValueProvider build() { return new SignerPropertyValueProvider(this); } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java index a9e26356b919..31301bd6d00c 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java @@ -18,6 +18,9 @@ import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; @@ -25,15 +28,15 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; 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.AwsV4AuthScheme; 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; @@ -184,6 +187,133 @@ public Map, List> operationsToAuthType() { return operationsToAuthType; } + /** + * Computes a map from operations to codegen metadata objects. The intermediate model is used to compute mappings to + * {@link AuthType} values for the service and for each operation that has an override. Then we group all the operations + * that share the same set of auth types together and finally convert the auth types to their corresponding codegen + * metadata instances that then we can use to codegen switch statements. The service wide codegen metadata instances are + * keyed using {@link Collections#emptyList()}. + * + * @see #computeServiceWideDefaults + */ + private Map, List> operationsToModeledMetadata() { + Map, List> operationsToAuthType = operationsToAuthType(); + Map, List> operationsToMetadata = new LinkedHashMap<>(); + operationsToAuthType.forEach((k, v) -> operationsToMetadata.put(k, authTypeToCodegenMetadata(v))); + return operationsToMetadata; + } + + public Map, List> operationsToMetadata() { + List serviceDefaults = serviceDefaultAuthTypes(); + if (serviceDefaults.size() == 1) { + String authTypeName = serviceDefaults.get(0).value(); + SigV4SignerDefaults defaults = AuthTypeToSigV4Default.authTypeToDefaults().get(authTypeName); + if (areServiceWide(defaults)) { + return computeServiceWideDefaults(defaults); + } + } + return operationsToModeledMetadata(); + } + + /** + * Similar to {@link #operationsToModeledMetadata()} computes a map from operations to codegen metadata objects. The + * service default list of codegen metadata is keyed with {@link Collections#emptyList()}. + * + * This map is used to codegen switch statements. + */ + private Map, List> computeServiceWideDefaults(SigV4SignerDefaults defaults) { + Map> defaultsToOperations = + defaults.operations() + .entrySet() + .stream() + .map(kvp -> new AbstractMap.SimpleEntry<>(kvp.getKey(), kvp.getValue())) + .collect(Collectors.groupingBy(AbstractMap.SimpleEntry::getValue, + Collectors.mapping(AbstractMap.SimpleEntry::getKey, + Collectors.toList()))); + + Map, SigV4SignerDefaults> operationsToDefaults = + defaultsToOperations.entrySet() + .stream() + .sorted(Comparator.comparing(left -> left.getValue().get(0))) + .collect(Collectors.toMap(Map.Entry::getValue, + Map.Entry::getKey, (a, b) -> b, + LinkedHashMap::new)); + + Map, List> result = new LinkedHashMap<>(); + for (Map.Entry, SigV4SignerDefaults> kvp : operationsToDefaults.entrySet()) { + result.put(kvp.getKey(), + Arrays.asList(AuthSchemeCodegenMetadata.fromConstants(kvp.getValue()))); + } + result.put(Collections.emptyList(), Arrays.asList(AuthSchemeCodegenMetadata.fromConstants(defaults))); + return result; + } + + public boolean areServiceWide(SigV4SignerDefaults defaults) { + return defaults != null + && defaults.service() != null + && Objects.equals(intermediateModel.getMetadata().getServiceName(), defaults.service()); + } + + public Map, AuthSchemeCodegenMetadata> operationsToNonStandardSigv4Metadata() { + Map, AuthSchemeCodegenMetadata> result = + operationsToMetadata() + .entrySet() + .stream() + .filter(kvp -> containsNonStandardSigV4(kvp.getValue())) + .map(kvp -> new AbstractMap.SimpleEntry<>( + kvp.getKey(), + findNonStandardSigV4(kvp.getValue()))) + .collect( + Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue, + (a, b) -> b, + LinkedHashMap::new)); + return result; + } + + private boolean containsNonStandardSigV4(List options) { + return findNonStandardSigV4(options) != null; + } + + private AuthSchemeCodegenMetadata findNonStandardSigV4(List options) { + Map defaultSigv4Properties = + AuthSchemeCodegenMetadata.constantProperties(AuthSchemeCodegenMetadata.SIGV4); + for (AuthSchemeCodegenMetadata metadata : options) { + if (metadata.authSchemeClass() != AwsV4AuthScheme.class) { + continue; + } + Map sigv4Properties = AuthSchemeCodegenMetadata.constantProperties(metadata); + if (defaultSigv4Properties.equals(sigv4Properties)) { + return null; + } + List properties = + metadata + .properties() + .stream() + .filter(AuthSchemeSpecUtils::isNonDefaultSigv4Property) + .collect(Collectors.toList()); + if (!properties.isEmpty()) { + return metadata.toBuilder().properties(properties).build(); + } + return null; + } + return null; + } + + private static boolean isNonDefaultSigv4Property(AuthSchemeCodegenMetadata.SignerPropertyValueProvider provider) { + switch (provider.fieldName()) { + case "SERVICE_SIGNING_NAME": + case "REGION_NAME": + case "DOUBLE_URL_ENCODE": + return false; + default: + return true; + } + } + + private List authTypeToCodegenMetadata(List authTypes) { + return authTypes.stream().map(AuthSchemeCodegenMetadata::fromAuthType).collect(Collectors.toList()); + } + public List serviceDefaultAuthTypes() { List modeled = intermediateModel.getMetadata().getAuth(); if (!modeled.isEmpty()) { @@ -193,16 +323,12 @@ public List serviceDefaultAuthTypes() { } 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)))); + Set> result = operationsToMetadata() + .values() + .stream() + .flatMap(Collection::stream) + .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. diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeDefaults.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeDefaults.java new file mode 100644 index 000000000000..5319d863768a --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeDefaults.java @@ -0,0 +1,33 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.codegen.poet.auth.scheme; + +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.codegen.model.service.AuthType; + +public interface AuthTypeDefaults { + + /** + * Map of operation that override the service defaults with the given defaults. + */ + List serviceDefaults(); + + /** + * Map of operation that override the service defaults with the given defaults. + */ + Map> operationToDefaults(); +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeDefaultsToCodegenMetadata.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeDefaultsToCodegenMetadata.java new file mode 100644 index 000000000000..26117b8f5822 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeDefaultsToCodegenMetadata.java @@ -0,0 +1,28 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.codegen.poet.auth.scheme; + +import java.util.List; +import java.util.Map; + +public interface AuthTypeDefaultsToCodegenMetadata { + + + + AuthTypeDefaults defaults(); + + Map, List> operations(); +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeToSigV4Default.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeToSigV4Default.java new file mode 100644 index 000000000000..a77a6d301902 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeToSigV4Default.java @@ -0,0 +1,164 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.codegen.poet.auth.scheme; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.codegen.model.service.AuthType; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.utils.Lazy; + +public final class AuthTypeToSigV4Default { + + public static final SigV4SignerDefaults SIGV4_DEFAULT = SigV4SignerDefaults + .builder() + .authType("v4") + .schemeId(AwsV4AuthScheme.SCHEME_ID) + .build(); + + private static final Lazy> AUTH_TYPE_TO_DEFAULTS = new Lazy<>( + () -> { + Map map = new LinkedHashMap<>(); + for (SigV4SignerDefaults sigv4FamilySignerConstants : knownAuthTypes()) { + if (map.put(sigv4FamilySignerConstants.authType(), sigv4FamilySignerConstants) != null) { + throw new IllegalStateException("Duplicate key: " + sigv4FamilySignerConstants.authType()); + } + } + return map; + }); + + private AuthTypeToSigV4Default() { + } + + /** + * Returns a mapping from an auth-type name to a set of AWS sigV4 default values.The auth-type names are the same as the + * {@link AuthType} enum values. + * + * @see SigV4SignerDefaults + */ + public static Map authTypeToDefaults() { + return AUTH_TYPE_TO_DEFAULTS.getValue(); + } + + /** + * Returns a mapping from an auth-type name to a set of AWS sigV4 default values.The auth-type names are the same as the + * {@link AuthType} enum values. + * + * @see SigV4SignerDefaults + */ + public static Map authTypeToDefaults0() { + return AUTH_TYPE_TO_DEFAULTS.getValue(); + } + + public static List knownAuthTypes() { + return Arrays.asList( + sigv4Default(), + s3Defaults(), + s3v4Defaults(), + sigv4UnsignedPayload() + ); + } + + /** + * Set of default signer defaults. None is set by default. + */ + private static SigV4SignerDefaults sigv4Default() { + return SIGV4_DEFAULT; + } + + /** + * Set of default signer defaults for S3. Sets the following defaults signer properties + * + *
    + *
  • {@code doubleUrlEncode(false)} + *
  • {@code normalizePath(false)} + *
  • {@code payloadSigningEnabled(false)} + *
+ *

+ * Also overrides for the following operations + * + *

    + *
  • {@code UploadParts} Sets the defaults and also {@code chunkEncodingEnabled(true)}
  • + *
  • {@code PutObject} Sets the defaults and also {@code chunkEncodingEnabled(true)}
  • + *
+ */ + private static SigV4SignerDefaults s3Defaults() { + return sigv4Default() + .toBuilder() + .authType("s3") + .service("S3") + .doubleUrlEncode(Boolean.FALSE) + .normalizePath(Boolean.FALSE) + .payloadSigningEnabled(Boolean.FALSE) + .putOperation("UploadPart", + sigv4Default() + .toBuilder() + // Default S3 signer properties + .doubleUrlEncode(Boolean.FALSE) + .normalizePath(Boolean.FALSE) + .payloadSigningEnabled(Boolean.FALSE) + // Including chunkEncodingEnabled TRUE + .chunkEncodingEnabled(Boolean.TRUE) + .build()) + .putOperation("PutObject", + sigv4Default() + .toBuilder() + // Default S3 signer properties + .doubleUrlEncode(Boolean.FALSE) + .normalizePath(Boolean.FALSE) + .payloadSigningEnabled(Boolean.FALSE) + // Including chunkEncodingEnabled TRUE + .chunkEncodingEnabled(Boolean.TRUE) + .build()) + .build(); + } + + + /** + * Set of default signer defaults for auth-type s3v4. Currently only used by S3Control. + */ + + private static SigV4SignerDefaults s3v4Defaults() { + SigV4SignerDefaults s3v4 = sigv4Default().toBuilder() + .authType("s3v4") + .doubleUrlEncode(false) + .normalizePath(false) + // Figure out if s3Control uses this + // .payloadSigningEnabled(false) + .build(); + + // TODO: Figure out if there are any ops overrides needed that are currently habdled by interceptors. + return s3v4; + } + + + /** + * Set of default signer defaults for auth-type s3v4. Currently only used by disable payload signing for some operations. Sets + * the following default signer property + * + *
    + *
  • {@code payloadSigningEnabled(false)} + *
+ */ + private static SigV4SignerDefaults sigv4UnsignedPayload() { + return sigv4Default().toBuilder() + .authType("v4-unsigned-body") + .payloadSigningEnabled(false) + .build(); + } +} 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 index a30dd68bbcce..500c28f81c3c 100644 --- 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 @@ -16,6 +16,7 @@ package software.amazon.awssdk.codegen.poet.auth.scheme; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; @@ -62,17 +63,24 @@ public ClassName className() { @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(); + Map, AuthSchemeCodegenMetadata> operationsToSigv4 = + authSchemeSpecUtils.operationsToNonStandardSigv4Metadata(); + boolean applyServiceDefaults = !operationsToSigv4.isEmpty(); + TypeSpec.Builder builder = 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(applyServiceDefaults)); + + if (applyServiceDefaults) { + builder.addMethod(addV4Defaults(operationsToSigv4)); + } + return builder.build(); } private MethodSpec constructor() { @@ -108,7 +116,7 @@ private MethodSpec createMethod() { .build(); } - private MethodSpec resolveAuthSchemeMethod() { + private MethodSpec resolveAuthSchemeMethod(boolean applyServiceDefaults) { MethodSpec.Builder spec = MethodSpec.methodBuilder("resolveAuthScheme") .addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) @@ -136,40 +144,52 @@ private MethodSpec resolveAuthSchemeMethod() { 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); + addAuthSchemeSwitch(spec, applyServiceDefaults); spec.endControlFlow(); return spec.addStatement("return $T.unmodifiableList(options)", Collections.class) .build(); } - private void addAuthSchemeSwitch(MethodSpec.Builder spec) { + private void addAuthSchemeSwitch(MethodSpec.Builder spec, boolean applyServiceDefaults) { spec.addStatement("$T name = authScheme.name()", String.class); spec.beginControlFlow("switch(name)"); - addAuthSchemeSwitchSigV4Case(spec); - addAuthSchemeSwitchSigV4aCase(spec); + addAuthSchemeSwitchSigV4Case(spec, applyServiceDefaults); + addAuthSchemeSwitchSigV4aCase(spec, applyServiceDefaults); if (endpointRulesSpecUtils.useS3Express()) { - addAuthSchemeSwitchS3ExpressCase(spec); + addAuthSchemeSwitchS3ExpressCase(spec, applyServiceDefaults); } addAuthSchemeSwitchDefaultCase(spec); spec.endControlFlow(); } - private void addAuthSchemeSwitchSigV4Case(MethodSpec.Builder spec) { + private void addAuthSchemeSwitchSigV4Case(MethodSpec.Builder spec, boolean applyServiceDefaults) { 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());"); + CodeBlock.Builder block = CodeBlock.builder(); + block.add("$1T.builder().schemeId($2T.SCHEME_ID)", AuthSchemeOption.class, AwsV4AuthScheme.class) + .add(".putSignerProperty($T.SERVICE_SIGNING_NAME, sigv4AuthScheme.signingName())", AwsV4HttpSigner.class) + .add(".putSignerProperty($T.REGION_NAME, sigv4AuthScheme.signingRegion())", AwsV4HttpSigner.class) + .add(".putSignerProperty($T.DOUBLE_URL_ENCODE, !sigv4AuthScheme.disableDoubleEncoding())", + AwsV4HttpSigner.class); + + if (applyServiceDefaults) { + spec.addCode("$1T sigv4AuthSchemeOption = applySigV4FamilyDefaults(", AuthSchemeOption.class) + .addCode(block.build()) + .addCode(", params)") + .addStatement(".build()"); + } else { + spec.addCode("$1T sigv4AuthSchemeOption = ", AuthSchemeOption.class) + .addCode(block.build()) + .addStatement(".build()"); + } + spec.addStatement("options.add(sigv4AuthSchemeOption)"); spec.addStatement("break"); } - private void addAuthSchemeSwitchSigV4aCase(MethodSpec.Builder spec) { + private void addAuthSchemeSwitchSigV4aCase(MethodSpec.Builder spec, boolean applyServiceDefaults) { spec.addCode("case $S:", "sigv4a"); spec.addStatement("$T sigv4aAuthScheme = $T.isInstanceOf($T.class, authScheme, $S, authScheme.getClass().getName())", @@ -178,33 +198,61 @@ private void addAuthSchemeSwitchSigV4aCase(MethodSpec.Builder spec) { 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());"); + CodeBlock.Builder block = CodeBlock.builder(); + block.add("$1T.builder().schemeId($2T.SCHEME_ID)", AuthSchemeOption.class, + AwsV4aAuthScheme.class) + .add(".putSignerProperty($T.SERVICE_SIGNING_NAME, sigv4aAuthScheme.signingName())", AwsV4HttpSigner.class) + .add(".putSignerProperty($T.REGION_SET, regionSet)", AwsV4aHttpSigner.class) + .add(".putSignerProperty($T.DOUBLE_URL_ENCODE, !sigv4aAuthScheme.disableDoubleEncoding())", AwsV4HttpSigner.class); + + if (applyServiceDefaults) { + spec.addCode("$1T sigv4aAuthSchemeOption = applySigV4FamilyDefaults(", AuthSchemeOption.class) + .addCode(block.build()) + .addCode(", params)") + .addStatement(".build()"); + + } else { + spec.addCode("$1T sigv4aAuthSchemeOption = ", AuthSchemeOption.class) + .addCode(block.build()) + .addStatement(".build()"); + } + spec.addStatement("options.add(sigv4aAuthSchemeOption)"); spec.addStatement("break"); } - private void addAuthSchemeSwitchS3ExpressCase(MethodSpec.Builder spec) { + private void addAuthSchemeSwitchS3ExpressCase(MethodSpec.Builder spec, boolean applyServiceDefaults) { spec.addCode("case $S:", "sigv4-s3express"); ClassName s3ExpressEndpointAuthScheme = ClassName.get( authSchemeSpecUtils.baseClientPackageName() + ".endpoints.authscheme", "S3ExpressEndpointAuthScheme"); + spec.addStatement("$T s3ExpressAuthScheme = $T.isInstanceOf($T.class, authScheme, $S, authScheme.getClass().getName())", s3ExpressEndpointAuthScheme, Validate.class, s3ExpressEndpointAuthScheme, "Expecting auth scheme of class S3ExpressAuthScheme, got instead object of class %s"); ClassName s3ExpressAuthScheme = ClassName.get(authSchemeSpecUtils.baseClientPackageName() + ".s3express", "S3ExpressAuthScheme"); - spec.addCode("options.add($T.builder().schemeId($T.SCHEME_ID)", AuthSchemeOption.class, s3ExpressAuthScheme) - .addCode(".putSignerProperty($T.SERVICE_SIGNING_NAME, s3ExpressAuthScheme.signingName())", AwsV4HttpSigner.class) - .addCode(".putSignerProperty($T.REGION_NAME, s3ExpressAuthScheme.signingRegion())", AwsV4HttpSigner.class) - .addCode(".putSignerProperty($T.DOUBLE_URL_ENCODE, !s3ExpressAuthScheme.disableDoubleEncoding())", - AwsV4HttpSigner.class) - .addCode(".build());"); + + CodeBlock.Builder block = CodeBlock.builder(); + block.add("$1T.builder().schemeId($2T.SCHEME_ID)", AuthSchemeOption.class, s3ExpressAuthScheme) + .add(".putSignerProperty($T.SERVICE_SIGNING_NAME, s3ExpressAuthScheme.signingName())", AwsV4HttpSigner.class) + .add(".putSignerProperty($T.REGION_NAME, s3ExpressAuthScheme.signingRegion())", AwsV4HttpSigner.class) + .add(".putSignerProperty($T.DOUBLE_URL_ENCODE, !s3ExpressAuthScheme.disableDoubleEncoding())", + AwsV4HttpSigner.class); + + if (applyServiceDefaults) { + spec.addCode("$1T s3ExpressAuthSchemeOption = applySigV4FamilyDefaults(", AuthSchemeOption.class) + .addCode(block.build()) + .addCode(", params)") + .addStatement(".build()"); + } else { + spec.addCode("$1T s3ExpressAuthSchemeOption = ", AuthSchemeOption.class) + .addCode(block.build()) + .addStatement(".build()"); + } + spec.addStatement("options.add(s3ExpressAuthSchemeOption)"); spec.addStatement("break"); + } private void addAuthSchemeSwitchDefaultCase(MethodSpec.Builder spec) { @@ -212,6 +260,63 @@ private void addAuthSchemeSwitchDefaultCase(MethodSpec.Builder spec) { spec.addStatement("throw new $T($S + name)", IllegalArgumentException.class, "Unknown auth scheme: "); } + + private MethodSpec addV4Defaults(Map, AuthSchemeCodegenMetadata> operationsToSigv4) { + MethodSpec.Builder spec = MethodSpec.methodBuilder("applySigV4FamilyDefaults") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(AuthSchemeOption.Builder.class) + .addParameter(AuthSchemeOption.Builder.class, "option") + .addParameter(authSchemeSpecUtils.parametersInterfaceName(), "params"); + + if (operationsToSigv4.isEmpty()) { + return spec.addStatement("return option").build(); + } + + // All the operations share the same set of auth schemes, no need to create a switch statement. + if (operationsToSigv4.size() == 1) { + AuthSchemeCodegenMetadata authType = operationsToSigv4.get(Collections.emptyList()); + addAuthTypeProperties(spec, authType); + return spec.build(); + } + spec.beginControlFlow("switch(params.operation())"); + operationsToSigv4.forEach((ops, scheme) -> { + if (!ops.isEmpty()) { + addCasesForOperations(spec, ops, scheme); + } + }); + AuthSchemeCodegenMetadata authType = operationsToSigv4.get(Collections.emptyList()); + if (authType != null) { + addCasesForOperations(spec, Collections.emptyList(), authType); + } + spec.endControlFlow(); + return spec.build(); + } + + private void addCasesForOperations(MethodSpec.Builder spec, List operations, + AuthSchemeCodegenMetadata metadata) { + if (operations.isEmpty()) { + spec.addCode("default:"); + } else { + for (String name : operations) { + spec.addCode("case $S:", name); + } + } + addAuthTypeProperties(spec, metadata); + } + + public void addAuthTypeProperties(MethodSpec.Builder spec, AuthSchemeCodegenMetadata metadata) { + spec.addCode("return option"); + for (AuthSchemeCodegenMetadata.SignerPropertyValueProvider property : metadata.properties()) { + if ("REGION_NAME".equals(property.fieldName())) { + continue; + } + spec.addCode(".putSignerPropertyIfAbsent($T.$N, ", property.containingClass(), property.fieldName()); + property.emitValue(spec, authSchemeSpecUtils); + spec.addCode(")"); + } + spec.addStatement(""); + } + 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 index 9f9f0abcf04b..0d48a62153a7 100644 --- 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 @@ -16,7 +16,6 @@ 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; @@ -31,7 +30,6 @@ 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; @@ -93,12 +91,12 @@ private MethodSpec resolveAuthSchemeMethod() { spec.addStatement("$T options = new $T<>()", ParameterizedTypeName.get(List.class, AuthSchemeOption.class), TypeName.get(ArrayList.class)); - Map, List> operationsToAuthType = authSchemeSpecUtils.operationsToAuthType(); + Map, List> operationsToAuthType = authSchemeSpecUtils.operationsToMetadata(); // 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) { + List types = operationsToAuthType.get(Collections.emptyList()); + for (AuthSchemeCodegenMetadata authType : types) { addAuthTypeProperties(spec, authType); } return spec.addStatement("return $T.unmodifiableList(options)", Collections.class) @@ -117,7 +115,8 @@ private MethodSpec resolveAuthSchemeMethod() { .build(); } - private void addCasesForOperations(MethodSpec.Builder spec, List operations, List schemes) { + private void addCasesForOperations(MethodSpec.Builder spec, List operations, + List schemes) { if (operations.isEmpty()) { spec.addCode("default:"); } else { @@ -125,21 +124,19 @@ private void addCasesForOperations(MethodSpec.Builder spec, List operati spec.addCode("case $S:", name); } } - for (AuthType authType : schemes) { - addAuthTypeProperties(spec, authType); + for (AuthSchemeCodegenMetadata metadata : schemes) { + addAuthTypeProperties(spec, metadata); } spec.addStatement("break"); } - public void addAuthTypeProperties(MethodSpec.Builder spec, AuthType authType) { - AuthSchemeCodegenMetadata metadata = fromAuthType(authType); + public void addAuthTypeProperties(MethodSpec.Builder spec, AuthSchemeCodegenMetadata metadata) { 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/auth/scheme/SigV4SignerDefaults.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/SigV4SignerDefaults.java new file mode 100644 index 000000000000..cb7123351679 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/SigV4SignerDefaults.java @@ -0,0 +1,240 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.codegen.poet.auth.scheme; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import software.amazon.awssdk.utils.Validate; + +/** + * Tracks a set of explicitly enabled signer properties for the family of AWS SigV4 signers. The currently supported attributes + * are {@code doubleUrlEncode}, {@code normalizePath}, {@code payloadSigningEnabled}, {@code chunkEncodingEnabled}. If their + * value is not null then is taken as-is, otherwise is ignored as in the value is not overridden. An auth type can also + * represent a service-wide set of defaults when the set of operations is not empty. + */ +public final class SigV4SignerDefaults { + private final String service; + private final String authType; + private final String schemeId; + private final Boolean doubleUrlEncode; + private final Boolean normalizePath; + private final Boolean payloadSigningEnabled; + private final Boolean chunkEncodingEnabled; + private final Map operations; + + public SigV4SignerDefaults(Builder builder) { + this.service = builder.service; + this.authType = Validate.notNull(builder.authType, "authType"); + this.schemeId = Validate.notNull(builder.schemeId, "schemeId"); + this.doubleUrlEncode = builder.doubleUrlEncode; + this.normalizePath = builder.normalizePath; + this.payloadSigningEnabled = builder.payloadSigningEnabled; + this.chunkEncodingEnabled = builder.chunkEncodingEnabled; + this.operations = Collections.unmodifiableMap(new HashMap<>(builder.operations)); + } + + public String service() { + return service; + } + + public String authType() { + return authType; + } + + public String schemeId() { + return schemeId; + } + + public Boolean doubleUrlEncode() { + return doubleUrlEncode; + } + + public Boolean normalizePath() { + return normalizePath; + } + + public Boolean payloadSigningEnabled() { + return payloadSigningEnabled; + } + + public Boolean chunkEncodingEnabled() { + return chunkEncodingEnabled; + } + + public Map operations() { + return operations; + } + + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SigV4SignerDefaults defaults = (SigV4SignerDefaults) o; + + if (!Objects.equals(service, defaults.service)) { + return false; + } + if (!authType.equals(defaults.authType)) { + return false; + } + if (!schemeId.equals(defaults.schemeId)) { + return false; + } + if (!Objects.equals(doubleUrlEncode, defaults.doubleUrlEncode)) { + return false; + } + if (!Objects.equals(normalizePath, defaults.normalizePath)) { + return false; + } + if (!Objects.equals(payloadSigningEnabled, defaults.payloadSigningEnabled)) { + return false; + } + if (!Objects.equals(chunkEncodingEnabled, defaults.chunkEncodingEnabled)) { + return false; + } + return operations.equals(defaults.operations); + } + + @Override + public int hashCode() { + int result = service != null ? service.hashCode() : 0; + result = 31 * result + authType.hashCode(); + result = 31 * result + schemeId.hashCode(); + result = 31 * result + (doubleUrlEncode != null ? doubleUrlEncode.hashCode() : 0); + result = 31 * result + (normalizePath != null ? normalizePath.hashCode() : 0); + result = 31 * result + (payloadSigningEnabled != null ? payloadSigningEnabled.hashCode() : 0); + result = 31 * result + (chunkEncodingEnabled != null ? chunkEncodingEnabled.hashCode() : 0); + result = 31 * result + operations.hashCode(); + return result; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String authType; + private String service; + private String schemeId; + private Boolean doubleUrlEncode; + private Boolean normalizePath; + private Boolean payloadSigningEnabled; + private Boolean chunkEncodingEnabled; + + private Map operations = new HashMap<>(); + + public Builder() { + } + + public Builder(SigV4SignerDefaults other) { + this.service = other.service; + this.authType = Validate.notNull(other.authType, "name"); + this.schemeId = Validate.notNull(other.schemeId, "schemeId"); + this.doubleUrlEncode = other.doubleUrlEncode; + this.normalizePath = other.normalizePath; + this.payloadSigningEnabled = other.payloadSigningEnabled; + this.chunkEncodingEnabled = other.chunkEncodingEnabled; + this.operations.putAll(other.operations); + } + + public String service() { + return service; + } + + public Builder service(String service) { + this.service = service; + return this; + } + + public String authType() { + return authType; + } + + public Builder authType(String authType) { + this.authType = authType; + return this; + } + + public String schemeId() { + return schemeId; + } + + public Builder schemeId(String schemeId) { + this.schemeId = schemeId; + return this; + } + + public Boolean doubleUrlEncode() { + return doubleUrlEncode; + } + + public Builder doubleUrlEncode(Boolean doubleUrlEncode) { + this.doubleUrlEncode = doubleUrlEncode; + return this; + } + + public Boolean normalizePath() { + return normalizePath; + } + + public Builder normalizePath(Boolean normalizePath) { + this.normalizePath = normalizePath; + return this; + } + + public Boolean payloadSigningEnabled() { + return payloadSigningEnabled; + } + + public Builder payloadSigningEnabled(Boolean payloadSigningEnabled) { + this.payloadSigningEnabled = payloadSigningEnabled; + return this; + } + + public Boolean chunkEncodingEnabled() { + return chunkEncodingEnabled; + } + + public Builder chunkEncodingEnabled(Boolean chunkEncodingEnabled) { + this.chunkEncodingEnabled = chunkEncodingEnabled; + return this; + } + + public Map operations() { + return operations; + } + + public Builder putOperation(String name, SigV4SignerDefaults constants) { + this.operations.put(name, constants); + return this; + } + + public SigV4SignerDefaults build() { + return new SigV4SignerDefaults(this); + } + } +} 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 b045552c5950..9c6d540b736e 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 @@ -744,7 +744,7 @@ private MethodSpec invokePluginsMethod() { .addParameter(SdkClientConfiguration.class, "config") .returns(SdkClientConfiguration.class); - builder.addStatement("$T internalPlugins = internalPlugins()", + builder.addStatement("$T internalPlugins = internalPlugins(config)", ParameterizedTypeName.get(List.class, SdkPlugin.class)); builder.addStatement("$T externalPlugins = plugins()", @@ -773,6 +773,7 @@ private MethodSpec internalPluginsMethod() { MethodSpec.Builder builder = MethodSpec.methodBuilder("internalPlugins") .addModifiers(PRIVATE) + .addParameter(SdkClientConfiguration.class, "config") .returns(parameterizedTypeName); List internalPlugins = model.getCustomizationConfig().getInternalPlugins(); @@ -784,14 +785,32 @@ private MethodSpec internalPluginsMethod() { builder.addStatement("$T internalPlugins = new $T<>()", parameterizedTypeName, ArrayList.class); for (String internalPlugin : internalPlugins) { - ClassName pluginClass = ClassName.bestGuess(internalPlugin); - builder.addStatement("internalPlugins.add(new $T())", pluginClass); + String arguments = internalPluginNewArguments(internalPlugin); + String internalPluginClass = internalPluginClass(internalPlugin); + ClassName pluginClass = ClassName.bestGuess(internalPluginClass); + builder.addStatement("internalPlugins.add(new $T($L))", pluginClass, arguments); } builder.addStatement("return internalPlugins"); return builder.build(); } + private String internalPluginClass(String internalPlugin) { + int openParenthesisIndex = internalPlugin.indexOf('('); + if (openParenthesisIndex == -1) { + return internalPlugin; + } + return internalPlugin.substring(0, openParenthesisIndex); + } + + private String internalPluginNewArguments(String internalPlugin) { + int openParenthesisIndex = internalPlugin.indexOf('('); + if (openParenthesisIndex == -1) { + return ""; + } + return internalPlugin.substring(openParenthesisIndex); + } + @Override public ClassName className() { return builderClassName; diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java index ae6ea5a4df3c..23eca7eac038 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java @@ -74,11 +74,61 @@ public void syncComposedDefaultClientBuilderClass() { "test-composed-sync-default-client-builder.java"); } + + @Test + public void baseClientBuilderClass_sra() { + validateBaseClientBuilderClassGeneration(restJsonServiceModels(), "test-client-builder-class.java", true); + } + + @Test + public void baseClientBuilderClassWithBearerAuth_sra() { + validateBaseClientBuilderClassGeneration(bearerAuthServiceModels(), "test-bearer-auth-client-builder-class.java", true); + } + + @Test + public void baseClientBuilderClassWithNoAuthOperation_sra() { + validateBaseClientBuilderClassGeneration(operationWithNoAuth(), "test-no-auth-ops-client-builder-class.java", true); + } + + @Test + public void baseClientBuilderClassWithNoAuthService_sra() { + validateBaseClientBuilderClassGeneration(serviceWithNoAuth(), "test-no-auth-service-client-builder-class.java", true); + } + + @Test + public void baseClientBuilderClassWithInternalUserAgent_sra() { + validateBaseClientBuilderClassGeneration(internalConfigModels(), "test-client-builder-internal-defaults-class.java", + true); + } + + @Test + public void baseQueryClientBuilderClass_sra() { + validateBaseClientBuilderClassGeneration(queryServiceModels(), "test-query-client-builder-class.java", true); + } + + @Test + public void baseClientBuilderClassWithEndpointsAuthParams_sra() { + validateBaseClientBuilderClassGeneration(queryServiceModelsEndpointAuthParamsWithAllowList(), + "test-client-builder-endpoints-auth-params.java", true); + } + + @Test + public void syncComposedDefaultClientBuilderClass_sra() { + validateBaseClientBuilderClassGeneration(composedClientJsonServiceModels(), + "test-composed-sync-default-client-builder.java", true); + } + private void validateBaseClientBuilderClassGeneration(IntermediateModel model, String expectedClassName) { - model.getCustomizationConfig().setUseSraAuth(false); - validateGeneration(BaseClientBuilderClass::new, model, expectedClassName); + validateBaseClientBuilderClassGeneration(model, expectedClassName, false); + } - model.getCustomizationConfig().setUseSraAuth(true); - validateGeneration(BaseClientBuilderClass::new, model, "sra/" + expectedClassName); + private void validateBaseClientBuilderClassGeneration(IntermediateModel model, String expectedClassName, boolean sra) { + if (sra) { + model.getCustomizationConfig().setUseSraAuth(true); + validateGeneration(BaseClientBuilderClass::new, model, "sra/" + expectedClassName); + } else { + model.getCustomizationConfig().setUseSraAuth(false); + validateGeneration(BaseClientBuilderClass::new, model, expectedClassName); + } } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java index 1db64c130b05..be41e3e83869 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java @@ -60,14 +60,12 @@ private SelectedAuthScheme selectAuthScheme(List> authSchemes = executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES); - IdentityProviders identityProviders = executionAttributes - .getAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS); + IdentityProviders identityProviders = executionAttributes.getAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS); List> discardedReasons = new ArrayList<>(); for (AuthSchemeOption authOption : authOptions) { AuthScheme authScheme = authSchemes.get(authOption.schemeId()); SelectedAuthScheme selectedAuthScheme = trySelectAuthScheme(authOption, authScheme, - identityProviders, discardedReasons, - metricCollector, executionAttributes); + identityProviders, discardedReasons, metricCollector, executionAttributes); if (selectedAuthScheme != null) { if (!discardedReasons.isEmpty()) { LOG.debug(() -> String.format("%s auth will be used, discarded: '%s'", authOption.schemeId(), @@ -90,8 +88,7 @@ private QueryAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttr } private SelectedAuthScheme trySelectAuthScheme(AuthSchemeOption authOption, AuthScheme authScheme, - IdentityProviders identityProviders, List> discardedReasons, - MetricCollector metricCollector, + IdentityProviders identityProviders, List> discardedReasons, MetricCollector metricCollector, ExecutionAttributes executionAttributes) { if (authScheme == null) { discardedReasons.add(() -> String.format("'%s' is not enabled for this request.", authOption.schemeId())); 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 index d22e9c1ca151..31166b99659c 100644 --- 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 @@ -10,6 +10,8 @@ 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.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; @@ -58,20 +60,22 @@ public List resolveAuthScheme(QueryAuthSchemeParams params) { 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()); + AuthSchemeOption sigv4AuthSchemeOption = AuthSchemeOption.builder().schemeId(AwsV4AuthScheme.SCHEME_ID) + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, sigv4AuthScheme.signingName()) + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, sigv4AuthScheme.signingRegion()) + .putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, !sigv4AuthScheme.disableDoubleEncoding()).build(); + options.add(sigv4AuthSchemeOption); 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()); + AuthSchemeOption sigv4aAuthSchemeOption = AuthSchemeOption.builder().schemeId(AwsV4aAuthScheme.SCHEME_ID) + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, sigv4aAuthScheme.signingName()) + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet) + .putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, !sigv4aAuthScheme.disableDoubleEncoding()).build(); + options.add(sigv4aAuthSchemeOption); break; default: throw new IllegalArgumentException("Unknown auth scheme: " + name); 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 index 0fd273b82882..8f0025bf6d97 100644 --- 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 @@ -10,6 +10,8 @@ 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.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; @@ -56,20 +58,22 @@ public List resolveAuthScheme(QueryAuthSchemeParams params) { 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()); + AuthSchemeOption sigv4AuthSchemeOption = AuthSchemeOption.builder().schemeId(AwsV4AuthScheme.SCHEME_ID) + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, sigv4AuthScheme.signingName()) + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, sigv4AuthScheme.signingRegion()) + .putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, !sigv4AuthScheme.disableDoubleEncoding()).build(); + options.add(sigv4AuthSchemeOption); 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()); + AuthSchemeOption sigv4aAuthSchemeOption = AuthSchemeOption.builder().schemeId(AwsV4aAuthScheme.SCHEME_ID) + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, sigv4aAuthScheme.signingName()) + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet) + .putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, !sigv4aAuthScheme.disableDoubleEncoding()).build(); + options.add(sigv4aAuthSchemeOption); break; default: throw new IllegalArgumentException("Unknown auth scheme: " + name); 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 index 5abf214378a0..4ae51e38025d 100644 --- 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 @@ -60,14 +60,12 @@ private SelectedAuthScheme selectAuthScheme(List> authSchemes = executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES); - IdentityProviders identityProviders = executionAttributes - .getAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS); + IdentityProviders identityProviders = executionAttributes.getAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS); List> discardedReasons = new ArrayList<>(); for (AuthSchemeOption authOption : authOptions) { AuthScheme authScheme = authSchemes.get(authOption.schemeId()); SelectedAuthScheme selectedAuthScheme = trySelectAuthScheme(authOption, authScheme, - identityProviders, discardedReasons, - metricCollector, executionAttributes); + identityProviders, discardedReasons, metricCollector, executionAttributes); if (selectedAuthScheme != null) { if (!discardedReasons.isEmpty()) { LOG.debug(() -> String.format("%s auth will be used, discarded: '%s'", authOption.schemeId(), @@ -99,8 +97,7 @@ private QueryAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttr } private SelectedAuthScheme trySelectAuthScheme(AuthSchemeOption authOption, AuthScheme authScheme, - IdentityProviders identityProviders, List> discardedReasons, - MetricCollector metricCollector, + IdentityProviders identityProviders, List> discardedReasons, MetricCollector metricCollector, ExecutionAttributes executionAttributes) { if (authScheme == null) { discardedReasons.add(() -> String.format("'%s' is not enabled for this request.", authOption.schemeId())); 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 index 378032221bc1..9b9839ee5b6e 100644 --- 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 @@ -60,14 +60,12 @@ private SelectedAuthScheme selectAuthScheme(List> authSchemes = executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES); - IdentityProviders identityProviders = executionAttributes - .getAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS); + IdentityProviders identityProviders = executionAttributes.getAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS); List> discardedReasons = new ArrayList<>(); for (AuthSchemeOption authOption : authOptions) { AuthScheme authScheme = authSchemes.get(authOption.schemeId()); SelectedAuthScheme selectedAuthScheme = trySelectAuthScheme(authOption, authScheme, - identityProviders, discardedReasons, - metricCollector, executionAttributes); + identityProviders, discardedReasons, metricCollector, executionAttributes); if (selectedAuthScheme != null) { if (!discardedReasons.isEmpty()) { LOG.debug(() -> String.format("%s auth will be used, discarded: '%s'", authOption.schemeId(), @@ -102,8 +100,7 @@ private QueryAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttr } private SelectedAuthScheme trySelectAuthScheme(AuthSchemeOption authOption, AuthScheme authScheme, - IdentityProviders identityProviders, List> discardedReasons, - MetricCollector metricCollector, + IdentityProviders identityProviders, List> discardedReasons, MetricCollector metricCollector, ExecutionAttributes executionAttributes) { if (authScheme == null) { discardedReasons.add(() -> String.format("'%s' is not enabled for this request.", authOption.schemeId())); 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 index d69b3bf46ee0..65fa6c74f696 100644 --- 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 @@ -127,7 +127,7 @@ private IdentityProvider defaultTokenProvider() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -141,7 +141,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 index 139f9cb63620..8fc7ef906c62 100644 --- 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 @@ -214,7 +214,7 @@ protected final AttributeMap serviceHttpConfig() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -228,7 +228,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { List internalPlugins = new ArrayList<>(); internalPlugins.add(new InternalTestPlugin1()); internalPlugins.add(new InternalTestPlugin2()); 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 index 843a92ab430e..0529fabd1584 100644 --- 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 @@ -149,7 +149,7 @@ private IdentityProvider defaultTokenProvider() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -163,7 +163,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 index 2d02273d245b..b8bced3923a8 100644 --- 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 @@ -124,7 +124,7 @@ private Map> authSchemes() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -138,7 +138,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 index 6d1252e85078..3ee6097742aa 100644 --- 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 @@ -158,7 +158,7 @@ private IdentityProvider defaultTokenProvider() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -172,7 +172,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 index 487f9d1c42a9..62ded62ffa67 100644 --- 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 @@ -119,7 +119,7 @@ private Map> authSchemes() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -134,7 +134,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 index 9876e54cd444..e9881e8fc484 100644 --- 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 @@ -107,7 +107,7 @@ private Map> authSchemes() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -122,7 +122,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 index 2b2853cf98df..12e6fe65e94f 100644 --- 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 @@ -146,7 +146,7 @@ private IdentityProvider defaultTokenProvider() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -160,7 +160,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 b323e0e4b91b..e77d35434d4c 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 @@ -98,7 +98,7 @@ private Signer defaultTokenSigner() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -112,7 +112,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 8b44ec2ce87f..ee013e70a9c4 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 @@ -188,7 +188,7 @@ protected final AttributeMap serviceHttpConfig() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -202,7 +202,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { List internalPlugins = new ArrayList<>(); internalPlugins.add(new InternalTestPlugin1()); internalPlugins.add(new InternalTestPlugin2()); 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 index 945b796781ae..5e7c97377b7c 100644 --- 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 @@ -120,7 +120,7 @@ private Signer defaultTokenSigner() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -134,7 +134,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 4185bbc4e72c..82a6d7ad5549 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 @@ -96,7 +96,7 @@ private JsonEndpointProvider defaultEndpointProvider() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -110,7 +110,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 01f52c3553c7..bbffc0a4d47c 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 @@ -132,7 +132,7 @@ private Signer defaultTokenSigner() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -146,7 +146,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 index 450339ce03fd..8ff06f85e88d 100644 --- 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 @@ -88,7 +88,7 @@ private DatabaseEndpointProvider defaultEndpointProvider() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -103,7 +103,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 index f35b5f3c2b0d..dc6570ae80bc 100644 --- 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 @@ -73,7 +73,7 @@ private DatabaseEndpointProvider defaultEndpointProvider() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -88,7 +88,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 945b796781ae..5e7c97377b7c 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 @@ -120,7 +120,7 @@ private Signer defaultTokenSigner() { @Override protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { - List internalPlugins = internalPlugins(); + List internalPlugins = internalPlugins(config); List externalPlugins = plugins(); if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { return config; @@ -134,7 +134,7 @@ protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { return configuration.build(); } - private List internalPlugins() { + private List internalPlugins(SdkClientConfiguration config) { return Collections.emptyList(); } 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 index dd6ebdf72760..740124ec41f7 100644 --- 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 @@ -18,6 +18,8 @@ import java.time.Duration; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; +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.signer.HttpSigner; import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; import software.amazon.awssdk.identity.spi.Identity; @@ -28,6 +30,12 @@ */ @SdkPublicApi public interface AwsV4FamilyHttpSigner extends HttpSigner { + + static boolean isSupported(String schemeId) { + return AwsV4AuthScheme.SCHEME_ID.equals(schemeId) + || AwsV4aAuthScheme.SCHEME_ID.equals(schemeId); + } + /** * The name of the AWS service. This property is required. */ 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 index b53cbaaab38c..5599c1fcbe01 100644 --- 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 @@ -75,7 +75,6 @@ static Builder builder() { /** * Interface for operating on an {@link IdentityProperty} value. */ - @FunctionalInterface interface IdentityPropertyConsumer { /** * A method to operate on an {@link IdentityProperty} and it's value. @@ -89,7 +88,6 @@ interface IdentityPropertyConsumer { /** * Interface for operating on an {@link SignerProperty} value. */ - @FunctionalInterface interface SignerPropertyConsumer { /** * A method to operate on a {@link SignerProperty} and it's value. 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 d88d54610387..ab0d8fac1dad 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 @@ -36,17 +36,17 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; -import software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute; import software.amazon.awssdk.authcrt.signer.internal.DefaultAwsCrtS3V4aSigner; +import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.core.checksums.Algorithm; import software.amazon.awssdk.core.checksums.ChecksumValidation; -import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.internal.async.FileAsyncRequestBody; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Configuration; import software.amazon.awssdk.services.s3.S3IntegrationTestBase; +import software.amazon.awssdk.services.s3.internal.plugins.S3OverrideAuthSchemePropertiesPlugin; import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; import software.amazon.awssdk.services.s3.model.ChecksumMode; import software.amazon.awssdk.services.s3.model.GetObjectRequest; @@ -222,12 +222,10 @@ void asyncHttpsValidUnsignedTrailer_TwoRequests_withDifferentChunkSize_OfFileAsy @Disabled("Http Async Signing is not supported for S3") void asyncValidSignedTrailerChecksumCalculatedBySdkClient() { - ExecutionAttributes executionAttributes = ExecutionAttributes.builder() - .put(S3SignerExecutionAttribute.ENABLE_PAYLOAD_SIGNING, - true).build(); + SdkPlugin enablePayloadSigningPlugin = S3OverrideAuthSchemePropertiesPlugin.enablePayloadSigningPlugin(); s3HttpAsync.putObject(PutObjectRequest.builder() .bucket(BUCKET) - .overrideConfiguration(o -> o.executionAttributes(executionAttributes)) + .overrideConfiguration(o -> o.addPlugin(enablePayloadSigningPlugin)) .key(KEY) .build(), AsyncRequestBody.fromString("Hello world")).join(); String response = s3HttpAsync.getObject(GetObjectRequest.builder().bucket(BUCKET) diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/s3/s3express/S3ExpressIntegrationTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/s3/s3express/S3ExpressIntegrationTest.java index b6edf3585691..d49ea54e86be 100644 --- a/services/s3/src/it/java/software/amazon/awssdk/services/s3/s3express/S3ExpressIntegrationTest.java +++ b/services/s3/src/it/java/software/amazon/awssdk/services/s3/s3express/S3ExpressIntegrationTest.java @@ -44,7 +44,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute; import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; @@ -62,6 +61,7 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.internal.plugins.S3OverrideAuthSchemePropertiesPlugin; import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest; import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadResponse; import software.amazon.awssdk.services.s3.model.CompletedMultipartUpload; @@ -226,7 +226,7 @@ public void putObject_payloadSigningEnabledSra_executesSuccessfully() { S3Client s3Client = S3Client.builder() .region(TEST_REGION) .credentialsProvider(CREDENTIALS_PROVIDER_CHAIN) - .overrideConfiguration(o -> o.putExecutionAttribute(S3SignerExecutionAttribute.ENABLE_PAYLOAD_SIGNING, true)) + .addPlugin(S3OverrideAuthSchemePropertiesPlugin.enablePayloadSigningPlugin()) .build(); PutObjectRequest request = PutObjectRequest.builder() diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/s3/signer/PayloadSigningIntegrationTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/s3/signer/PayloadSigningIntegrationTest.java index ca77bc1cc588..dbe9bc2f7486 100644 --- a/services/s3/src/it/java/software/amazon/awssdk/services/s3/signer/PayloadSigningIntegrationTest.java +++ b/services/s3/src/it/java/software/amazon/awssdk/services/s3/signer/PayloadSigningIntegrationTest.java @@ -29,7 +29,6 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -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; @@ -39,6 +38,7 @@ import software.amazon.awssdk.http.SdkHttpRequest; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.S3IntegrationTestBase; +import software.amazon.awssdk.services.s3.internal.plugins.S3OverrideAuthSchemePropertiesPlugin; import software.amazon.awssdk.services.s3.utils.S3TestUtils; /** @@ -99,7 +99,8 @@ public void standardSyncApacheHttpClient_httpCauses_signedPayload() { public void standardSyncApacheHttpClient_manuallyEnabled_signedPayload() { S3Client syncClient = s3ClientBuilder() .overrideConfiguration(o -> o.addExecutionInterceptor(capturingInterceptor) - .addExecutionInterceptor(new PayloadSigningInterceptor())) + .addExecutionInterceptor(new CreateRequestBodyIfNeededInterceptor())) + .addPlugin(S3OverrideAuthSchemePropertiesPlugin.enablePayloadSigningPlugin()) .build(); assertThat(syncClient.putObject(b -> b.bucket(BUCKET).key(KEY), RequestBody.fromBytes("helloworld".getBytes()))).isNotNull(); @@ -132,12 +133,11 @@ public List capturedRequests() { } } - private static class PayloadSigningInterceptor implements ExecutionInterceptor { + private static class CreateRequestBodyIfNeededInterceptor implements ExecutionInterceptor { @Override public Optional modifyHttpContent(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { - executionAttributes.putAttribute(S3SignerExecutionAttribute.ENABLE_PAYLOAD_SIGNING, true); if (!context.requestBody().isPresent() && context.httpRequest().method().equals(SdkHttpMethod.POST)) { return Optional.of(RequestBody.fromBytes(new byte[0])); } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/S3ExpressChecksumInterceptor.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/S3ExpressChecksumInterceptor.java index 2cd3401f7487..c303ba248fe5 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/S3ExpressChecksumInterceptor.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/S3ExpressChecksumInterceptor.java @@ -21,7 +21,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.stream.Stream; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.checksums.DefaultChecksumAlgorithm; import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; @@ -98,8 +97,10 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut } private boolean requestContainsUserCalculatedChecksum(SdkRequest request) { - return Stream.of("ChecksumCRC32", "ChecksumCRC32C", "ChecksumSHA1", "ChecksumSHA256") - .anyMatch(s -> request.getValueForField(s, String.class).isPresent()); + return request.getValueForField("ChecksumCRC32", String.class).isPresent() + || request.getValueForField("ChecksumCRC32C", String.class).isPresent() + || request.getValueForField("ChecksumSHA1", String.class).isPresent() + || request.getValueForField("ChecksumSHA256", String.class).isPresent(); } private boolean shouldAlwaysAddChecksum(ChecksumSpecs checksumSpecs, SdkRequest request) { diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPlugin.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPlugin.java new file mode 100644 index 000000000000..348c7b07205c --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPlugin.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.services.s3.internal.plugins; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkServiceClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.S3ServiceClientConfiguration; +import software.amazon.awssdk.services.s3.auth.scheme.S3AuthSchemeProvider; +import software.amazon.awssdk.services.s3.internal.s3express.S3DisableChunkEncodingAuthSchemeProvider; +import software.amazon.awssdk.utils.Logger; + +@SdkInternalApi +public class S3DisableChunkEncodingIfConfiguredPlugin implements SdkPlugin { + + private static final Logger LOG = Logger.loggerFor(S3DisableChunkEncodingIfConfiguredPlugin.class); + + private final boolean isServiceConfigurationPresent; + private final boolean isChunkedEncodingEnabledConfigured; + private final boolean isChunkedEncodingEnabledDisabled; + private final boolean configuresDisableChunkEncoding; + + public S3DisableChunkEncodingIfConfiguredPlugin(SdkClientConfiguration config) { + S3Configuration serviceConfiguration = + (S3Configuration) config.option(SdkClientOption.SERVICE_CONFIGURATION); + + boolean isServiceConfigurationPresent = serviceConfiguration != null; + boolean shouldAddDisableChunkEncoding = false; + boolean isChunkedEncodingEnabledConfigured = false; + boolean isChunkedEncodingEnabledDisabled = false; + boolean configuresDisableChunkEncoding = false; + if (isServiceConfigurationPresent) { + isChunkedEncodingEnabledConfigured = serviceConfiguration.toBuilder().chunkedEncodingEnabled() != null; + isChunkedEncodingEnabledDisabled = !serviceConfiguration.chunkedEncodingEnabled(); + configuresDisableChunkEncoding = isChunkedEncodingEnabledConfigured && isChunkedEncodingEnabledDisabled; + if (configuresDisableChunkEncoding) { + shouldAddDisableChunkEncoding = true; + } + } + this.configuresDisableChunkEncoding = shouldAddDisableChunkEncoding; + this.isChunkedEncodingEnabledConfigured = isChunkedEncodingEnabledConfigured; + this.isChunkedEncodingEnabledDisabled = isChunkedEncodingEnabledDisabled; + this.isServiceConfigurationPresent = isServiceConfigurationPresent; + } + + @Override + public void configureClient(SdkServiceClientConfiguration.Builder config) { + if (configuresDisableChunkEncoding) { + LOG.debug(() -> String.format("chunkedEncodingEnabled was explicitly disabled in the configuration, adding " + + "`S3DisableChunkEncodingAuthSchemeProvider` auth provider wrapper.")); + S3ServiceClientConfiguration.Builder s3Config = (S3ServiceClientConfiguration.Builder) config; + + // We wrap the default S3AuthSchemeProvider with a S3DisableChunkEncodingAuthSchemeProvider + // instance that sets the AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED signer + // property to false but only if the operations is `"PutObject" or "UploadPart"` + // This legacy logic was implemented before using an interceptor but now requires + // wrapping the S3AuthSchemeProvider for it to work. + S3AuthSchemeProvider disablingAuthSchemeProvider = + S3DisableChunkEncodingAuthSchemeProvider.create(s3Config.authSchemeProvider()); + s3Config.authSchemeProvider(disablingAuthSchemeProvider); + } else { + LOG.debug(() -> String.format("chunkedEncodingEnabled was not explicitly disabled in the configuration, not adding " + + "the `S3DisableChunkEncodingAuthSchemeProvider` auth provider wrapper." + + "[isServiceConfigurationPresent: %s, isChunkedEncodingEnabledConfigured: %s, " + + "isChunkedEncodingEnabledDisabled: %s]", + isServiceConfigurationPresent, + isChunkedEncodingEnabledConfigured, + isChunkedEncodingEnabledDisabled)); + } + } +} diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3OverrideAuthSchemePropertiesPlugin.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3OverrideAuthSchemePropertiesPlugin.java new file mode 100644 index 000000000000..7e314fe9a63d --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3OverrideAuthSchemePropertiesPlugin.java @@ -0,0 +1,180 @@ +/* + * 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.plugins; + +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.SdkProtectedApi; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.SdkServiceClientConfiguration; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +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.SignerProperty; +import software.amazon.awssdk.identity.spi.IdentityProperty; +import software.amazon.awssdk.services.s3.S3ServiceClientConfiguration; +import software.amazon.awssdk.services.s3.auth.scheme.S3AuthSchemeProvider; + +/** + * Plugin that allows override of signer and identity properties on the selected auth scheme options. The class offers static + * methods to create plugins for common cases such as enable payload signing by default. For instance, if you want + * to unconditionally disable chunk-encoding across the board you can create the S3 client, e.g., + * + * {@snippet + * S3AsyncClient s3 = S3AsyncClient.builder() + * .region(Region.US_WEST_2) + * .credentialsProvider(CREDENTIALS) + * .httpClient(httpClient) + * .addPlugin(S3OverrideAuthSchemePropertiesPlugin.enablePayloadSigningPlugin()) + * .build()); + * } + * + * The plugin can also be used for a particular request, e.g., + * + * {@snippet + * s3Client.putObject(PutObjectRequest.builder() + * .overrideConfiguration(c -> c.addPlugin( + * S3OverrideAuthSchemePropertiesPlugin.enablePayloadSigningPlugin())) + * .checksumAlgorithm(ChecksumAlgorithm.SHA256) + * .bucket("test").key("test").build(), RequestBody.fromBytes("abc".getBytes())); + * } + */ +@SdkProtectedApi +public final class S3OverrideAuthSchemePropertiesPlugin implements SdkPlugin { + private final Map, Object> identityProperties; + private final Map, Object> signerProperties; + + private S3OverrideAuthSchemePropertiesPlugin(Builder builder) { + if (builder.identityProperties.isEmpty()) { + this.identityProperties = Collections.emptyMap(); + } else { + this.identityProperties = Collections.unmodifiableMap(new HashMap<>(builder.identityProperties)); + } + if (builder.signerProperties.isEmpty()) { + this.signerProperties = Collections.emptyMap(); + } else { + this.signerProperties = Collections.unmodifiableMap(new HashMap<>(builder.signerProperties)); + } + } + + @Override + public void configureClient(SdkServiceClientConfiguration.Builder config) { + if (identityProperties.isEmpty() && signerProperties.isEmpty()) { + return; + } + S3ServiceClientConfiguration.Builder s3Config = (S3ServiceClientConfiguration.Builder) config; + S3AuthSchemeProvider delegate = s3Config.authSchemeProvider(); + s3Config.authSchemeProvider(params -> { + List options = delegate.resolveAuthScheme(params); + List result = new ArrayList<>(options.size()); + for (AuthSchemeOption option : options) { + String schemeId = option.schemeId(); + // We check here that the scheme id is sigV4 or sigV4a or some other in the same family. + // We don't set the overrides for non-sigV4 auth schemes. + if (schemeId.startsWith(AwsV4AuthScheme.SCHEME_ID)) { + AuthSchemeOption.Builder builder = option.toBuilder(); + identityProperties.forEach((k, v) -> putIdentityProperty(builder, k, v)); + signerProperties.forEach((k, v) -> putSingerProperty(builder, k, v)); + result.add(builder.build()); + } else { + result.add(option); + } + } + return result; + }); + } + + @SuppressWarnings("unchecked") + private void putIdentityProperty(AuthSchemeOption.Builder builder, IdentityProperty key, Object value) { + // Safe because of Builder#putIdentityProperty + builder.putIdentityProperty((IdentityProperty) key, (T) value); + } + + @SuppressWarnings("unchecked") + private void putSingerProperty(AuthSchemeOption.Builder builder, SignerProperty key, Object value) { + // Safe because of Builder#putSignerProperty + builder.putSignerProperty((SignerProperty) key, (T) value); + } + + + /** + * Creates a new plugin that enables payload signing. This plugin can be used per client or by per-request. + */ + public static SdkPlugin enablePayloadSigningPlugin() { + return builder() + .payloadSigningEnabled(true) + .build(); + } + + /** + * Creates a new builder to configure the plugin. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private final Map, Object> identityProperties = new HashMap<>(); + private final Map, Object> signerProperties = new HashMap<>(); + + /** + * Adds the provided property value as an override. + */ + public Builder putIdentityProperty(IdentityProperty key, T value) { + identityProperties.put(key, value); + return this; + } + + /** + * Adds the provided property value as an override. + */ + public Builder putSignerProperty(SignerProperty key, T value) { + signerProperties.put(key, value); + return this; + } + + /** + * Sets the {@link AwsV4FamilyHttpSigner#NORMALIZE_PATH} signing property to the given value. + */ + public Builder normalizePath(Boolean value) { + return putSignerProperty(AwsV4FamilyHttpSigner.NORMALIZE_PATH, value); + } + + /** + * Sets the {@link AwsV4FamilyHttpSigner#CHUNK_ENCODING_ENABLED} signing property to the given value. + */ + public Builder chunkEncodingEnabled(Boolean value) { + return putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, value); + } + + /** + * Sets the {@link AwsV4FamilyHttpSigner#PAYLOAD_SIGNING_ENABLED} signing property to the given value. + */ + public Builder payloadSigningEnabled(Boolean value) { + return putSignerProperty(AwsV4FamilyHttpSigner.PAYLOAD_SIGNING_ENABLED, value); + } + + /** + * Builds and returns a new plugin. + */ + public S3OverrideAuthSchemePropertiesPlugin build() { + return new S3OverrideAuthSchemePropertiesPlugin(this); + } + } +} diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/s3express/S3DisableChunkEncodingAuthSchemeProvider.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/s3express/S3DisableChunkEncodingAuthSchemeProvider.java new file mode 100644 index 000000000000..d4aba9ce244d --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/s3express/S3DisableChunkEncodingAuthSchemeProvider.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.services.s3.internal.s3express; + +import java.util.ArrayList; +import java.util.List; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.s3.auth.scheme.S3AuthSchemeParams; +import software.amazon.awssdk.services.s3.auth.scheme.S3AuthSchemeProvider; + + +@SdkInternalApi +public final class S3DisableChunkEncodingAuthSchemeProvider implements S3AuthSchemeProvider { + + private final S3AuthSchemeProvider delegate; + + private S3DisableChunkEncodingAuthSchemeProvider(S3AuthSchemeProvider delegate) { + this.delegate = delegate; + } + + public static S3DisableChunkEncodingAuthSchemeProvider create(S3AuthSchemeProvider delegate) { + return new S3DisableChunkEncodingAuthSchemeProvider(delegate); + } + + @Override + public List resolveAuthScheme(S3AuthSchemeParams authSchemeParams) { + List options = delegate.resolveAuthScheme(authSchemeParams); + List result = options; + + // Duplicates the legacy logic that only disables chunk encoding for the + // PutObject and UploadPart operations. + String operation = authSchemeParams.operation(); + if ("PutObject".equals(operation) || "UploadPart".equals(operation)) { + result = new ArrayList<>(options.size()); + for (AuthSchemeOption option : options) { + result.add(option.toBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, false) + .build()); + } + } + return result; + } + + public S3AuthSchemeProvider delegate() { + return delegate; + } +} diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/s3express/S3ExpressAuthSchemeProvider.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/s3express/S3ExpressAuthSchemeProvider.java index f6fadc7d422f..24314769cb5a 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/s3express/S3ExpressAuthSchemeProvider.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/s3express/S3ExpressAuthSchemeProvider.java @@ -15,8 +15,8 @@ package software.amazon.awssdk.services.s3.internal.s3express; +import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; import software.amazon.awssdk.identity.spi.IdentityProperty; @@ -42,9 +42,13 @@ public static S3ExpressAuthSchemeProvider create(S3AuthSchemeProvider delegate) @Override public List resolveAuthScheme(S3AuthSchemeParams authSchemeParams) { List options = delegate.resolveAuthScheme(authSchemeParams); - return options.stream() - .map(option -> option.toBuilder().putIdentityProperty(BUCKET, authSchemeParams.bucket()).build()) - .collect(Collectors.toList()); + List result = new ArrayList<>(options.size()); + for (AuthSchemeOption option : options) { + result.add(option.toBuilder() + .putIdentityProperty(BUCKET, authSchemeParams.bucket()) + .build()); + } + return result; } public S3AuthSchemeProvider delegate() { diff --git a/services/s3/src/main/resources/codegen-resources/customization.config b/services/s3/src/main/resources/codegen-resources/customization.config index 7bd3369c2b88..07c09308a2eb 100644 --- a/services/s3/src/main/resources/codegen-resources/customization.config +++ b/services/s3/src/main/resources/codegen-resources/customization.config @@ -248,8 +248,6 @@ "software.amazon.awssdk.services.s3.internal.handlers.StreamingRequestInterceptor", "software.amazon.awssdk.services.s3.internal.handlers.CreateBucketInterceptor", "software.amazon.awssdk.services.s3.internal.handlers.CreateMultipartUploadRequestInterceptor", - "software.amazon.awssdk.services.s3.internal.handlers.EnableChunkedEncodingInterceptor", - "software.amazon.awssdk.services.s3.internal.handlers.ConfigureSignerInterceptor", "software.amazon.awssdk.services.s3.internal.handlers.DecodeUrlEncodedResponseInterceptor", "software.amazon.awssdk.services.s3.internal.handlers.GetBucketPolicyInterceptor", "software.amazon.awssdk.services.s3.internal.handlers.S3ExpressChecksumInterceptor", @@ -258,10 +256,10 @@ "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.DisablePayloadSigningInterceptor" + "software.amazon.awssdk.services.s3.internal.handlers.CopySourceInterceptor" ], "internalPlugins": [ + "software.amazon.awssdk.services.s3.internal.plugins.S3DisableChunkEncodingIfConfiguredPlugin(config)", "software.amazon.awssdk.services.s3.internal.s3express.S3ExpressPlugin" ], "requiredTraitValidationEnabled": true, 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 index 0ac8d0eec1bb..35091b38875d 100644 --- 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 @@ -20,11 +20,10 @@ 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.services.s3.internal.plugins.S3OverrideAuthSchemePropertiesPlugin; import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient; import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; @@ -33,10 +32,6 @@ */ 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() { @@ -83,7 +78,7 @@ public void syncPayloadSigningCanBeEnabled() { .region(Region.US_WEST_2) .credentialsProvider(CREDENTIALS) .httpClient(httpClient) - .overrideConfiguration(ENABLE_PAYLOAD_SIGNING_CONFIG) + .addPlugin(S3OverrideAuthSchemePropertiesPlugin.enablePayloadSigningPlugin()) .build()) { httpClient.stubNextResponse(HttpExecuteResponse.builder() .response(SdkHttpResponse.builder().statusCode(200).build()) @@ -103,7 +98,7 @@ public void asyncPayloadSigningCanBeEnabled() { .region(Region.US_WEST_2) .credentialsProvider(CREDENTIALS) .httpClient(httpClient) - .overrideConfiguration(ENABLE_PAYLOAD_SIGNING_CONFIG) + .addPlugin(S3OverrideAuthSchemePropertiesPlugin.enablePayloadSigningPlugin()) .build()) { httpClient.stubNextResponse(HttpExecuteResponse.builder() .response(SdkHttpResponse.builder().statusCode(200).build()) diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/S3SignerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/S3SignerTest.java index 0652127a8bb2..d6616af8eddf 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/S3SignerTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/S3SignerTest.java @@ -32,6 +32,8 @@ import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.junit.WireMockRule; import java.net.URI; +import java.util.List; +import java.util.stream.Collectors; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,14 +41,18 @@ import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.auth.signer.AwsS3V4Signer; -import software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute; +import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.checksums.Algorithm; import software.amazon.awssdk.core.checksums.ChecksumSpecs; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.internal.util.Mimetype; import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.auth.scheme.S3AuthSchemeProvider; +import software.amazon.awssdk.services.s3.internal.plugins.S3OverrideAuthSchemePropertiesPlugin; import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; import software.amazon.awssdk.services.s3.model.PutObjectRequest; @@ -69,10 +75,13 @@ private String getEndpoint() { } private S3Client getS3Client(boolean chunkedEncoding, boolean payloadSigning, URI endpoint) { + S3OverrideAuthSchemePropertiesPlugin plugin = S3OverrideAuthSchemePropertiesPlugin.builder() + .chunkEncodingEnabled(chunkedEncoding) + .payloadSigningEnabled(payloadSigning) + .build(); return S3Client.builder() + .addPlugin(plugin) .overrideConfiguration(ClientOverrideConfiguration.builder() - .putExecutionAttribute(S3SignerExecutionAttribute.ENABLE_CHUNKED_ENCODING, chunkedEncoding) - .putExecutionAttribute(S3SignerExecutionAttribute.ENABLE_PAYLOAD_SIGNING, payloadSigning) .putAdvancedOption(SdkAdvancedClientOption.SIGNER, AwsS3V4Signer.create()).build()) .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid"))) @@ -150,7 +159,29 @@ public void headerBasedSignedPayload() { stubFor(any(urlMatching(".*")) .willReturn(response())); s3Client.putObject(PutObjectRequest.builder() - .checksumAlgorithm(ChecksumAlgorithm.SHA256) + .checksumAlgorithm(ChecksumAlgorithm.SHA256) + .bucket("test").key("test").build(), RequestBody.fromBytes("abc".getBytes())); + verify(putRequestedFor(anyUrl()).withHeader(CONTENT_TYPE, equalTo(Mimetype.MIMETYPE_OCTET_STREAM))); + verify(putRequestedFor(anyUrl()).withHeader(CONTENT_LENGTH, equalTo("3"))); + verify(putRequestedFor(anyUrl()).withHeader(SHA256_HEADER.headerName(), equalTo("ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD" + + "/YfIAFa0="))); + verify(putRequestedFor(anyUrl()).withHeader("x-amz-content-sha256", notMatching("STREAMING-AWS4-HMAC-SHA256-PAYLOAD" + + "-TRAILER"))); + // This keeps changing based on time so matching if a valid string exist as signature. + // TODO : mock the clock and make the signature static for given time. + verify(putRequestedFor(anyUrl()).withHeader("x-amz-content-sha256", matching("\\w+"))); + verify(putRequestedFor(anyUrl()).withoutHeader("x-amz-trailer")); + verify(putRequestedFor(anyUrl()).withoutHeader("Content-Encoding")); + } + + @Test + public void headerBasedSignedPayload2() { + S3Client s3Client = getS3Client(false, false, URI.create(getEndpoint())); + stubFor(any(urlMatching(".*")) + .willReturn(response())); + s3Client.putObject(PutObjectRequest.builder() + .overrideConfiguration(c -> c.addPlugin(S3OverrideAuthSchemePropertiesPlugin.enablePayloadSigningPlugin())) + .checksumAlgorithm(ChecksumAlgorithm.SHA256) .bucket("test").key("test").build(), RequestBody.fromBytes("abc".getBytes())); verify(putRequestedFor(anyUrl()).withHeader(CONTENT_TYPE, equalTo(Mimetype.MIMETYPE_OCTET_STREAM))); verify(putRequestedFor(anyUrl()).withHeader(CONTENT_LENGTH, equalTo("3"))); @@ -168,4 +199,16 @@ public void headerBasedSignedPayload() { private ResponseDefinitionBuilder response() { return aResponse().withStatus(200).withHeader(CONTENT_LENGTH, "0").withBody(""); } + + private static SdkPlugin createPluginToOverride(SignerProperty property, T value) { + return config -> { + S3ServiceClientConfiguration.Builder s3Config = (S3ServiceClientConfiguration.Builder) config; + S3AuthSchemeProvider authSchemeProvider = s3Config.authSchemeProvider(); + s3Config.authSchemeProvider(authSchemeParams -> { + List result = authSchemeProvider.resolveAuthScheme(authSchemeParams); + return result.stream().map(o -> o.copy(b -> b.putSignerProperty(property, value))) + .collect(Collectors.toList()); + }); + }; + } } \ No newline at end of file diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/functionaltests/S3ExpressTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/functionaltests/S3ExpressTest.java index 4373a90df9d8..ddb5de07c954 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/functionaltests/S3ExpressTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/functionaltests/S3ExpressTest.java @@ -34,9 +34,12 @@ import com.github.tomakehurst.wiremock.junit5.WireMockTest; import java.net.URI; import java.util.ArrayList; +import java.util.Collections; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Function; import org.junit.jupiter.api.BeforeEach; @@ -46,15 +49,21 @@ 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.core.SelectedAuthScheme; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; 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.rules.testing.BaseRuleSetClientTest; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.http.SdkHttpRequest; import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; @@ -70,6 +79,7 @@ import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.UploadPartRequest; import software.amazon.awssdk.utils.AttributeMap; +import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.http.SdkHttpUtils; @WireMockTest(httpsEnabled = true) @@ -202,12 +212,14 @@ private void createClientAndCallPutObject(ClientType clientType, Protocol protoc if (clientType == ClientType.SYNC) { getSyncClient(protocol, wm, s3ExpressSessionAuth).putObject(putObjectRequest, RequestBody.fromString(PUT_BODY)); } else { - getAsyncClient(protocol, wm, s3ExpressSessionAuth).putObject(putObjectRequest, AsyncRequestBody.fromString(PUT_BODY)).join(); + getAsyncClient(protocol, wm, s3ExpressSessionAuth).putObject(putObjectRequest, + AsyncRequestBody.fromString(PUT_BODY)).join(); } } - private void createClientAndCallUploadPart(ClientType clientType, Protocol protocol, S3ExpressSessionAuth s3ExpressSessionAuth, - ChecksumAlgorithm checksumAlgorithm, WireMockRuntimeInfo wm) { + private void createClientAndCallUploadPart(ClientType clientType, Protocol protocol, + S3ExpressSessionAuth s3ExpressSessionAuth, + ChecksumAlgorithm checksumAlgorithm, WireMockRuntimeInfo wm) { UploadPartRequest.Builder requestBuilder = UploadPartRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY).partNumber(0).uploadId("test"); if (checksumAlgorithm != ChecksumAlgorithm.UNKNOWN_TO_SDK_VERSION) { @@ -217,7 +229,8 @@ private void createClientAndCallUploadPart(ClientType clientType, Protocol proto if (clientType == ClientType.SYNC) { getSyncClient(protocol, wm, s3ExpressSessionAuth).uploadPart(uploadPartRequest, RequestBody.fromString(PUT_BODY)); } else { - getAsyncClient(protocol, wm, s3ExpressSessionAuth).uploadPart(uploadPartRequest, AsyncRequestBody.fromString(PUT_BODY)).join(); + getAsyncClient(protocol, wm, s3ExpressSessionAuth).uploadPart(uploadPartRequest, + AsyncRequestBody.fromString(PUT_BODY)).join(); } } @@ -294,8 +307,8 @@ void verifyUploadPartHeaders(ClientType clientType, Protocol protocol, ChecksumA assertThat(headers.get("Content-Length")).isNotNull(); assertThat(headers.get("x-amz-content-sha256")).isNotNull(); - if ((protocol == Protocol.HTTPS || clientType == ClientType.ASYNC) && - checksumAlgorithm == ChecksumAlgorithm.UNKNOWN_TO_SDK_VERSION) { + if ((protocol == Protocol.HTTPS || clientType == ClientType.ASYNC) && + checksumAlgorithm == ChecksumAlgorithm.UNKNOWN_TO_SDK_VERSION) { assertThat(headers.get("x-amz-content-sha256").get(0)).isEqualToIgnoringCase("UNSIGNED-PAYLOAD"); } else { assertThat(headers.get("x-amz-decoded-content-length")).isNotNull(); @@ -406,9 +419,9 @@ private S3AsyncClientBuilder getS3AsyncClientBuilder() { } /** - * S3Express does not support path style enforcement through client configuration and the endpoint will resolve - * to virtual style. However, path style is required for the HTTP client to be able to direct requests to localhost - * and the WireMock port. + * S3Express does not support path style enforcement through client configuration and the endpoint will resolve to virtual + * style. However, path style is required for the HTTP client to be able to direct requests to localhost and the WireMock + * port. */ private static final class PathStyleEnforcingInterceptor implements ExecutionInterceptor { @@ -425,10 +438,13 @@ public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, Execu } private static final class CapturingInterceptor implements ExecutionInterceptor { + private SelectedAuthScheme selectedAuthScheme; private Map> headers; @Override public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { + selectedAuthScheme = executionAttributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + System.out.printf("AuthScheme Option: %s%n", selectedAuthScheme.authSchemeOption()); SdkHttpRequest sdkHttpRequest = context.httpRequest(); this.headers = sdkHttpRequest.headers(); System.out.printf("%s %s%n", sdkHttpRequest.method(), sdkHttpRequest.encodedPath()); @@ -436,4 +452,197 @@ public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttr System.out.println(); } } + + private static class SigV4SignerProperties { + private final String schemeId; + private final Boolean doubleUrlEncode; + private final Boolean normalizePath; + private final Boolean payloadSigningEnabled; + private final Boolean chunkEncodingEnabled; + private final Map operations; + + public SigV4SignerProperties(Builder builder) { + this.schemeId = Validate.notNull(builder.schemeId, "schemeId"); + this.doubleUrlEncode = builder.doubleUrlEncode; + this.normalizePath = builder.normalizePath; + this.payloadSigningEnabled = builder.payloadSigningEnabled; + this.chunkEncodingEnabled = builder.chunkEncodingEnabled; + this.operations = Collections.unmodifiableMap(new HashMap<>(builder.operations)); + } + + public String schemeId() { + return schemeId; + } + + public Boolean doubleUrlEncode() { + return doubleUrlEncode; + } + + public Boolean normalizePath() { + return normalizePath; + } + + public Boolean payloadSigningEnabled() { + return payloadSigningEnabled; + } + + public Boolean chunkEncodingEnabled() { + return chunkEncodingEnabled; + } + + public Map operations() { + return operations; + } + + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SigV4SignerProperties defaults = (SigV4SignerProperties) o; + if (!schemeId.equals(defaults.schemeId)) { + return false; + } + if (!Objects.equals(doubleUrlEncode, defaults.doubleUrlEncode)) { + return false; + } + if (!Objects.equals(normalizePath, defaults.normalizePath)) { + return false; + } + if (!Objects.equals(payloadSigningEnabled, defaults.payloadSigningEnabled)) { + return false; + } + if (!Objects.equals(chunkEncodingEnabled, defaults.chunkEncodingEnabled)) { + return false; + } + return operations.equals(defaults.operations); + } + + @Override + public int hashCode() { + int result = schemeId.hashCode(); + result = 31 * result + (doubleUrlEncode != null ? doubleUrlEncode.hashCode() : 0); + result = 31 * result + (normalizePath != null ? normalizePath.hashCode() : 0); + result = 31 * result + (payloadSigningEnabled != null ? payloadSigningEnabled.hashCode() : 0); + result = 31 * result + (chunkEncodingEnabled != null ? chunkEncodingEnabled.hashCode() : 0); + result = 31 * result + operations.hashCode(); + return result; + } + + public static Builder builder() { + return new Builder(); + } + + public static Builder fromAuthSchemeOption(AuthSchemeOption option) { + if (!option.schemeId().startsWith(AwsV4AuthScheme.SCHEME_ID)) { + throw new IllegalArgumentException("unsupported auth scheme option"); + } + Builder builder = builder(); + builder.schemeId(option.schemeId()); + option.forEachSignerProperty(builder::putSignerProperty); + return builder; + } + + + public static class Builder { + private String schemeId; + private Boolean doubleUrlEncode; + private Boolean normalizePath; + private Boolean payloadSigningEnabled; + private Boolean chunkEncodingEnabled; + + private Map operations = new HashMap<>(); + + public Builder() { + } + + public Builder(SigV4SignerProperties other) { + this.schemeId = Validate.notNull(other.schemeId, "schemeId"); + this.doubleUrlEncode = other.doubleUrlEncode; + this.normalizePath = other.normalizePath; + this.payloadSigningEnabled = other.payloadSigningEnabled; + this.chunkEncodingEnabled = other.chunkEncodingEnabled; + this.operations.putAll(other.operations); + } + + public String schemeId() { + return schemeId; + } + + public Builder schemeId(String schemeId) { + this.schemeId = schemeId; + return this; + } + + public Boolean doubleUrlEncode() { + return doubleUrlEncode; + } + + public Builder doubleUrlEncode(Boolean doubleUrlEncode) { + this.doubleUrlEncode = doubleUrlEncode; + return this; + } + + public Boolean normalizePath() { + return normalizePath; + } + + public Builder normalizePath(Boolean normalizePath) { + this.normalizePath = normalizePath; + return this; + } + + public Boolean payloadSigningEnabled() { + return payloadSigningEnabled; + } + + public Builder payloadSigningEnabled(Boolean payloadSigningEnabled) { + this.payloadSigningEnabled = payloadSigningEnabled; + return this; + } + + public Boolean chunkEncodingEnabled() { + return chunkEncodingEnabled; + } + + public Builder chunkEncodingEnabled(Boolean chunkEncodingEnabled) { + this.chunkEncodingEnabled = chunkEncodingEnabled; + return this; + } + + public Map operations() { + return operations; + } + + public Builder putOperation(String name, SigV4SignerProperties constants) { + this.operations.put(name, constants); + return this; + } + + public void putSignerProperty(SignerProperty key, T value) { + if (AwsV4HttpSigner.DOUBLE_URL_ENCODE.equals(key)) { + doubleUrlEncode((Boolean) value); + } else if (AwsV4HttpSigner.NORMALIZE_PATH.equals(key)) { + normalizePath((Boolean) value); + } else if (AwsV4HttpSigner.PAYLOAD_SIGNING_ENABLED.equals(key)) { + payloadSigningEnabled((Boolean) value); + } else if (AwsV4HttpSigner.CHUNK_ENCODING_ENABLED.equals(key)) { + normalizePath((Boolean) value); + } + } + + public SigV4SignerProperties build() { + return new SigV4SignerProperties(this); + } + } + + } } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPluginTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPluginTest.java new file mode 100644 index 000000000000..bcfeae126b2c --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPluginTest.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.services.s3.internal.plugins; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +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.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.SelectedAuthScheme; +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.sync.RequestBody; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +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.SignerProperty; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3ClientBuilder; +import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.UploadPartRequest; + +class S3DisableChunkEncodingIfConfiguredPluginTest { + private static final String DEFAULT_BUCKET = "bucket"; + private static final String DEFAULT_KEY = "key"; + private static final String PUT_BODY = "Hello from Java SDK"; + + private static final AwsCredentialsProvider CREDENTIALS_PROVIDER = + StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")); + + CapturingInterceptor capturingInterceptor = null; + + @BeforeEach + void setup() { + capturingInterceptor = new CapturingInterceptor(); + } + + @Test + void testUploadPartEnablesChunkEncodingByDefault() { + S3Client syncClient = getS3ClientBuilder() + .build(); + UploadPartRequest.Builder requestBuilder = + UploadPartRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY).partNumber(0).uploadId("test"); + assertThatThrownBy(() -> syncClient.uploadPart(requestBuilder.build(), RequestBody.fromString(PUT_BODY))) + .hasMessageContaining("boom"); + + AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, true) + .build(); + Map, Object> expectedProperties = signerProperties(expectedValues); + assertThat(selectSignerProperties(signerProperties(authSchemeOption()), expectedProperties.keySet())) + .isEqualTo(expectedProperties); + } + + @Test + void testUploadPartDisablesChunkEncodingWhenConfigured() { + S3Client syncClient = getS3ClientBuilder() + .serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(false).build()) + .build(); + UploadPartRequest.Builder requestBuilder = + UploadPartRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY).partNumber(0).uploadId("test"); + assertThatThrownBy(() -> syncClient.uploadPart(requestBuilder.build(), RequestBody.fromString(PUT_BODY))) + .hasMessageContaining("boom"); + + AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, false) + .build(); + Map, Object> expectedProperties = signerProperties(expectedValues); + assertThat(selectSignerProperties(signerProperties(authSchemeOption()), expectedProperties.keySet())) + .isEqualTo(expectedProperties); + } + + @Test + void testPutObjectEnablesChunkEncodingByDefault() { + S3Client syncClient = getS3ClientBuilder() + .build(); + PutObjectRequest.Builder requestBuilder = + PutObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); + assertThatThrownBy(() -> syncClient.putObject(requestBuilder.build(), RequestBody.fromString(PUT_BODY))) + .hasMessageContaining("boom"); + + AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, true) + .build(); + Map, Object> expectedProperties = signerProperties(expectedValues); + assertThat(selectSignerProperties(signerProperties(authSchemeOption()), expectedProperties.keySet())) + .isEqualTo(expectedProperties); + } + + @Test + void testPutObjectDisablesChunkEncodingWhenConfigured() { + S3Client syncClient = getS3ClientBuilder() + .serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(false).build()) + .build(); + PutObjectRequest.Builder requestBuilder = + PutObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); + assertThatThrownBy(() -> syncClient.putObject(requestBuilder.build(), RequestBody.fromString(PUT_BODY))) + .hasMessageContaining("boom"); + + AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, false) + .build(); + Map, Object> expectedProperties = signerProperties(expectedValues); + assertThat(selectSignerProperties(signerProperties(authSchemeOption()), expectedProperties.keySet())) + .isEqualTo(expectedProperties); + } + + @Test + void testGetObjectDoesNotSetChunkEncoding() { + S3Client syncClient = getS3ClientBuilder() + .serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(true).build()) + .build(); + GetObjectRequest.Builder requestBuilder = + GetObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); + assertThatThrownBy(() -> syncClient.getObject(requestBuilder.build())) + .hasMessageContaining("boom"); + + AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() + .build(); + Map, Object> expectedProperties = signerProperties(expectedValues); + Map, Object> givenProperties = signerProperties(authSchemeOption()); + assertThat(selectSignerProperties(givenProperties, expectedProperties.keySet())) + .isEqualTo(expectedProperties); + assertThat(givenProperties.get(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED)).isNull(); + } + + @Test + void testGetObjectDoesNotSetChunkEncodingIfNotConfigured() { + S3Client syncClient = getS3ClientBuilder() + .serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(true).build()) + .build(); + GetObjectRequest.Builder requestBuilder = + GetObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); + assertThatThrownBy(() -> syncClient.getObject(requestBuilder.build())) + .hasMessageContaining("boom"); + + AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() + .build(); + Map, Object> expectedProperties = signerProperties(expectedValues); + Map, Object> givenProperties = signerProperties(authSchemeOption()); + assertThat(selectSignerProperties(givenProperties, expectedProperties.keySet())) + .isEqualTo(expectedProperties); + assertThat(givenProperties.get(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED)).isNull(); + } + + @Test + void testGetObjectDoesNotSetChunkEncodingIfConfiguredAsEnabled() { + S3Client syncClient = getS3ClientBuilder() + .serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(true).build()) + .build(); + GetObjectRequest.Builder requestBuilder = + GetObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); + assertThatThrownBy(() -> syncClient.getObject(requestBuilder.build())) + .hasMessageContaining("boom"); + + AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() + .build(); + Map, Object> expectedProperties = signerProperties(expectedValues); + Map, Object> givenProperties = signerProperties(authSchemeOption()); + assertThat(selectSignerProperties(givenProperties, expectedProperties.keySet())) + .isEqualTo(expectedProperties); + assertThat(givenProperties.get(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED)).isNull(); + } + + @Test + void testGetObjectDoesNotSetChunkEncodingIfConfiguredAsDisabled() { + S3Client syncClient = getS3ClientBuilder() + .serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(false).build()) + .build(); + GetObjectRequest.Builder requestBuilder = + GetObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); + assertThatThrownBy(() -> syncClient.getObject(requestBuilder.build())) + .hasMessageContaining("boom"); + + AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() + .build(); + Map, Object> expectedProperties = signerProperties(expectedValues); + Map, Object> givenProperties = signerProperties(authSchemeOption()); + assertThat(selectSignerProperties(givenProperties, expectedProperties.keySet())) + .isEqualTo(expectedProperties); + assertThat(givenProperties.get(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED)).isNull(); + } + + private AuthSchemeOption authSchemeOption() { + return capturingInterceptor.authSchemeOption(); + } + + AuthSchemeOption.Builder defaultExpectedAuthSchemeOptionBuilder() { + return AuthSchemeOption.builder() + .schemeId(AwsV4AuthScheme.SCHEME_ID) + // The following properties are always set + .putSignerProperty(AwsV4FamilyHttpSigner.NORMALIZE_PATH, false) + .putSignerProperty(AwsV4FamilyHttpSigner.DOUBLE_URL_ENCODE, false) + .putSignerProperty(AwsV4FamilyHttpSigner.PAYLOAD_SIGNING_ENABLED, false); + } + + Map, Object> signerProperties(AuthSchemeOption option) { + return SignerPropertiesBuilder.from(option).build(); + } + + Map, Object> selectSignerProperties( + Map, Object> signerProperties, + Collection> keys) { + Map, Object> result = new HashMap<>(); + for (SignerProperty key : keys) { + if (signerProperties.containsKey(key)) { + result.put(key, signerProperties.get(key)); + } + } + return result; + } + + S3ClientBuilder getS3ClientBuilder() { + return S3Client.builder() + .region(Region.US_EAST_1) + .overrideConfiguration(c -> c.addExecutionInterceptor(capturingInterceptor)) + .credentialsProvider(CREDENTIALS_PROVIDER); + } + + + static class SignerPropertiesBuilder { + Map, Object> map = new HashMap<>(); + + static SignerPropertiesBuilder from(AuthSchemeOption option) { + SignerPropertiesBuilder builder = new SignerPropertiesBuilder(); + option.forEachSignerProperty(builder::putSignerProperty); + return builder; + } + + public void putSignerProperty(SignerProperty key, T value) { + map.put(key, value); + } + + public Map, Object> build() { + return map; + } + } + + static class CapturingInterceptor implements ExecutionInterceptor { + private static final RuntimeException RTE = new RuntimeException("boom"); + private SelectedAuthScheme selectedAuthScheme; + + @Override + public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { + selectedAuthScheme = executionAttributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + throw RTE; + } + + public AuthSchemeOption authSchemeOption() { + if (selectedAuthScheme == null) { + return null; + } + return selectedAuthScheme.authSchemeOption(); + } + } +} diff --git a/services/s3control/src/test/java/software/amazon/awssdk/services/s3control/internal/interceptors/PayloadSigningInterceptorTest.java b/services/s3control/src/test/java/software/amazon/awssdk/services/s3control/internal/interceptors/CreateRequestBodyIfNeededInterceptorTest.java similarity index 98% rename from services/s3control/src/test/java/software/amazon/awssdk/services/s3control/internal/interceptors/PayloadSigningInterceptorTest.java rename to services/s3control/src/test/java/software/amazon/awssdk/services/s3control/internal/interceptors/CreateRequestBodyIfNeededInterceptorTest.java index a7acddb6e4ab..1cd6ab9a3aa0 100644 --- a/services/s3control/src/test/java/software/amazon/awssdk/services/s3control/internal/interceptors/PayloadSigningInterceptorTest.java +++ b/services/s3control/src/test/java/software/amazon/awssdk/services/s3control/internal/interceptors/CreateRequestBodyIfNeededInterceptorTest.java @@ -31,7 +31,7 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3control.S3ControlClient; -public class PayloadSigningInterceptorTest { +public class CreateRequestBodyIfNeededInterceptorTest { private SdkHttpRequest request; From 252952f953506c54196fb1e7c2aa11ed15164092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sugawara=20=28=E2=88=A9=EF=BD=80-=C2=B4=29?= =?UTF-8?q?=E2=8A=83=E2=94=81=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E?= Date: Wed, 7 Feb 2024 11:07:27 -0800 Subject: [PATCH 2/2] Refactor the logic to use Knowledge Indexes --- .../AuthSchemeCodegenKnowledgeIndex.java | 80 ++++ .../scheme/AuthSchemeCodegenMetadata.java | 114 +---- .../scheme/AuthSchemeCodegenMetadataExt.java | 173 ++++++++ .../auth/scheme/AuthSchemeProviderSpec.java | 12 +- .../poet/auth/scheme/AuthSchemeSpecUtils.java | 204 +-------- .../auth/scheme/AuthTypeToSigV4Default.java | 29 +- .../EndpointBasedAuthSchemeProviderSpec.java | 99 ++--- .../ModelAuthSchemeClassesKnowledgeIndex.java | 74 ++++ .../scheme/ModelAuthSchemeKnowledgeIndex.java | 165 +++++++ .../ModelBasedAuthSchemeProviderSpec.java | 49 +- .../SigV4AuthSchemeCodegenKnowledgeIndex.java | 168 +++++++ .../poet/auth/scheme/SigV4SignerDefaults.java | 11 +- .../poet/builder/BaseClientBuilderClass.java | 4 +- .../EndpointResolverInterceptorSpec.java | 4 +- .../builder/BaseClientBuilderClassTest.java | 19 +- ...isableChunkEncodingAuthSchemeProvider.java | 2 +- ...isableChunkEncodingIfConfiguredPlugin.java | 21 +- .../S3OverrideAuthSchemePropertiesPlugin.java | 48 +- ...leChunkEncodingIfConfiguredPluginTest.java | 280 ------------ .../S3SignerPropertiesPluginsTest.java | 418 ++++++++++++++++++ 20 files changed, 1244 insertions(+), 730 deletions(-) create mode 100644 codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenKnowledgeIndex.java create mode 100644 codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java create mode 100644 codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java create mode 100644 codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java create mode 100644 codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/SigV4AuthSchemeCodegenKnowledgeIndex.java rename services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/{s3express => plugins}/S3DisableChunkEncodingAuthSchemeProvider.java (97%) delete mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPluginTest.java create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/plugins/S3SignerPropertiesPluginsTest.java diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenKnowledgeIndex.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenKnowledgeIndex.java new file mode 100644 index 000000000000..d8bf80517ca1 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenKnowledgeIndex.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.codegen.poet.auth.scheme; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; + +/** + * Knowledge index to get access to the configured service auth schemes and operations overrides. This index is optimized for + * code generation of switch statements therefore the data is grouped by operations that share the same auth schemes. + */ +public final class AuthSchemeCodegenKnowledgeIndex { + /** + * We delegate this value to {@link ModelAuthSchemeKnowledgeIndex#operationsToMetadata()}. We just wrap the results in an + * interface that easier to use for the layer that does the code generation. + */ + private final Map, List> operationsToAuthSchemes; + + private AuthSchemeCodegenKnowledgeIndex(IntermediateModel intermediateModel) { + this.operationsToAuthSchemes = ModelAuthSchemeKnowledgeIndex.of(intermediateModel).operationsToMetadata(); + } + + /** + * Creates a new {@link AuthSchemeCodegenKnowledgeIndex} using the given {@code intermediateModel}.. + */ + public static AuthSchemeCodegenKnowledgeIndex of(IntermediateModel intermediateModel) { + return new AuthSchemeCodegenKnowledgeIndex(intermediateModel); + } + + /** + * Returns the service defaults auth schemes. These can be overridden by operation. + * + * @return the service defaults auth schemes. + */ + public List serviceDefaultAuthSchemes() { + return operationsToAuthSchemes.get(Collections.emptyList()); + } + + /** + * Returns true if there are auth scheme overrides per operation. + * + * @return true if there are auth scheme overrides per operation + */ + public boolean hasPerOperationAuthSchemesOverrides() { + // The map at least contains one key-value pair (keyed with Collections.emptyList()). + // If we have more than that then we have at least one override. + return operationsToAuthSchemes.size() > 1; + } + + /** + * Traverses each group of operations with the same set of auth schemes. + * + * @param consumer The consumer to call for each group of operations with the same set of auth schemes. + */ + public void forEachOperationsOverridesGroup(BiConsumer, List> consumer) { + for (Map.Entry, List> kvp : operationsToAuthSchemes.entrySet()) { + if (kvp.getKey().isEmpty()) { + // We are traversing operation groups, ignore service wide defaults. + continue; + } + consumer.accept(kvp.getKey(), kvp.getValue()); + } + } +} 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 index 28c68f0f984f..158e70dc0bba 100644 --- 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 @@ -15,48 +15,18 @@ package software.amazon.awssdk.codegen.poet.auth.scheme; -import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.CodeBlock; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Supplier; -import java.util.stream.Collectors; -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; +/** + * Represents a modeled auth scheme option. + */ 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 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; @@ -83,71 +53,10 @@ public Builder toBuilder() { return new Builder(this); } - private static Builder builder() { + public static Builder builder() { return new Builder(); } - - /** - * Transforms a {@link SigV4SignerDefaults} instance to an {@link AuthSchemeCodegenMetadata} instance. - */ - public static AuthSchemeCodegenMetadata fromConstants(SigV4SignerDefaults constants) { - AuthSchemeCodegenMetadata.Builder builder = SIGV4.toBuilder(); - for (SignerPropertyValueProvider property : propertiesFromConstants(constants)) { - builder.addProperty(property); - } - return builder.build(); - } - - public static List propertiesFromConstants(SigV4SignerDefaults constants) { - List properties = new ArrayList<>(); - if (constants.payloadSigningEnabled() != null) { - properties.add(from("PAYLOAD_SIGNING_ENABLED", constants::payloadSigningEnabled)); - } - if (constants.doubleUrlEncode() != null) { - properties.add(from("DOUBLE_URL_ENCODE", constants::doubleUrlEncode)); - } - if (constants.normalizePath() != null) { - properties.add(from("NORMALIZE_PATH", constants::normalizePath)); - } - if (constants.chunkEncodingEnabled() != null) { - properties.add(from("CHUNK_ENCODING_ENABLED", constants::chunkEncodingEnabled)); - } - return properties; - } - - private static SignerPropertyValueProvider from(String name, Supplier valueSupplier) { - return SignerPropertyValueProvider.builder() - .containingClass(AwsV4HttpSigner.class) - .fieldName(name) - .constantValueSupplier(valueSupplier) - .build(); - } - - public static AuthSchemeCodegenMetadata fromAuthType(AuthType type) { - switch (type) { - case BEARER: - return BEARER; - case NONE: - return NO_AUTH; - default: - String authTypeName = type.value(); - SigV4SignerDefaults defaults = AuthTypeToSigV4Default.authTypeToDefaults().get(authTypeName); - if (defaults == null) { - throw new IllegalArgumentException("Unknown auth type: " + type); - } - return fromConstants(defaults); - } - } - - public static Map constantProperties(AuthSchemeCodegenMetadata metadata) { - return metadata - .properties() - .stream() - .filter(SignerPropertyValueProvider::isConstant) - .collect(Collectors.toMap(SignerPropertyValueProvider::fieldName, SignerPropertyValueProvider::value)); - } - public static class Builder { private String schemeId; private List properties = new ArrayList<>(); @@ -191,7 +100,7 @@ public AuthSchemeCodegenMetadata build() { static class SignerPropertyValueProvider { private final Class containingClass; private final String fieldName; - private final BiConsumer valueEmitter; + private final BiConsumer valueEmitter; private final Supplier valueSupplier; SignerPropertyValueProvider(Builder builder) { @@ -217,18 +126,19 @@ public Object value() { return valueSupplier.get(); } - public void emitValue(MethodSpec.Builder spec, AuthSchemeSpecUtils utils) { + public void emitValue(CodeBlock.Builder spec, AuthSchemeSpecUtils utils) { valueEmitter.accept(spec, utils); } - private static Builder builder() { + + public static Builder builder() { return new Builder(); } static class Builder { private Class containingClass; private String fieldName; - private BiConsumer valueEmitter; + private BiConsumer valueEmitter; private Supplier valueSupplier; public Builder containingClass(Class containingClass) { @@ -241,7 +151,7 @@ public Builder fieldName(String fieldName) { return this; } - public Builder valueEmitter(BiConsumer valueEmitter) { + public Builder valueEmitter(BiConsumer valueEmitter) { this.valueEmitter = valueEmitter; return this; } @@ -249,7 +159,7 @@ public Builder valueEmitter(BiConsumer public Builder constantValueSupplier(Supplier valueSupplier) { this.valueSupplier = valueSupplier; if (valueEmitter == null) { - valueEmitter = (spec, utils) -> spec.addCode("$L", valueSupplier.get()); + valueEmitter = (spec, utils) -> spec.add("$L", valueSupplier.get()); } return this; } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java new file mode 100644 index 000000000000..2063aa506771 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java @@ -0,0 +1,173 @@ +/* + * 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.Builder; +import static software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeCodegenMetadata.builder; + +import com.squareup.javapoet.CodeBlock; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; +import software.amazon.awssdk.codegen.model.service.AuthType; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeCodegenMetadata.SignerPropertyValueProvider; +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.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; + +/** + * Extension and utility methods for the {@link AuthSchemeCodegenMetadata} class. + */ +public final class AuthSchemeCodegenMetadataExt { + + 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.add("$S", utils.signingName())) + .build()) + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4HttpSigner.class) + .fieldName( + "REGION_NAME") + .valueEmitter((spec, utils) -> spec.add("$L", "params.region().id()")) + .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 AuthSchemeCodegenMetadataExt() { + } + + /** + * Creates a new auth scheme codegen metadata instance using the defaults for the given {@link AuthType} defaults. + */ + public static AuthSchemeCodegenMetadata fromAuthType(AuthType type) { + switch (type) { + case BEARER: + return BEARER; + case NONE: + return NO_AUTH; + default: + String authTypeName = type.value(); + SigV4SignerDefaults defaults = AuthTypeToSigV4Default.authTypeToDefaults().get(authTypeName); + if (defaults == null) { + throw new IllegalArgumentException("Unknown auth type: " + type); + } + return fromConstants(defaults); + } + } + + /** + * Transforms a {@link SigV4SignerDefaults} instance to an {@link AuthSchemeCodegenMetadata} instance. + */ + public static AuthSchemeCodegenMetadata fromConstants(SigV4SignerDefaults constants) { + Builder builder = SIGV4.toBuilder(); + for (SignerPropertyValueProvider property : propertiesFromConstants(constants)) { + builder.addProperty(property); + } + return builder.build(); + } + + /** + * Renders the AuthSchemeCodegenMetadata as to create a new {@link AuthSchemeOption} using the configured values. + */ + public static CodeBlock codegenNewAuthOption( + AuthSchemeCodegenMetadata metadata, + AuthSchemeSpecUtils authSchemeSpecUtils + ) { + CodeBlock.Builder builder = CodeBlock.builder(); + builder.add("$T.builder().schemeId($S)", + AuthSchemeOption.class, metadata.schemeId()); + builder.add(codegenSignerProperties(authSchemeSpecUtils, metadata.properties())); + return builder.build(); + } + + /** + * Renders a chain of calls to {@link AuthSchemeOption.Builder#putSignerProperty(SignerProperty, Object)} for each of the + * given properties. + */ + public static CodeBlock codegenSignerProperties( + AuthSchemeSpecUtils authSchemeSpecUtils, + List properties + ) { + CodeBlock.Builder builder = CodeBlock.builder(); + for (SignerPropertyValueProvider property : properties) { + builder.add("\n.putSignerProperty($T.$N, ", property.containingClass(), property.fieldName()); + property.emitValue(builder, authSchemeSpecUtils); + builder.add(")"); + } + return builder.build(); + } + + /** + * Renders a chain of calls to {@link AuthSchemeOption.Builder#putSignerPropertyIfAbsent(SignerProperty, Object)} for each of + * the given properties. + */ + public static CodeBlock codegenSignerPropertiesIfAbsent( + AuthSchemeSpecUtils authSchemeSpecUtils, + List properties + ) { + CodeBlock.Builder builder = CodeBlock.builder(); + for (SignerPropertyValueProvider property : properties) { + builder.add("\n.putSignerPropertyIfAbsent($T.$N, ", property.containingClass(), property.fieldName()); + property.emitValue(builder, authSchemeSpecUtils); + builder.add(")"); + } + return builder.build(); + } + + private static List propertiesFromConstants(SigV4SignerDefaults constants) { + List properties = new ArrayList<>(); + if (constants.payloadSigningEnabled() != null) { + properties.add(from("PAYLOAD_SIGNING_ENABLED", constants::payloadSigningEnabled)); + } + if (constants.doubleUrlEncode() != null) { + properties.add(from("DOUBLE_URL_ENCODE", constants::doubleUrlEncode)); + } + if (constants.normalizePath() != null) { + properties.add(from("NORMALIZE_PATH", constants::normalizePath)); + } + if (constants.chunkEncodingEnabled() != null) { + properties.add(from("CHUNK_ENCODING_ENABLED", constants::chunkEncodingEnabled)); + } + return properties; + } + + private static SignerPropertyValueProvider from(String name, Supplier valueSupplier) { + return SignerPropertyValueProvider.builder() + .containingClass(AwsV4HttpSigner.class) + .fieldName(name) + .constantValueSupplier(valueSupplier) + .build(); + } +} 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 index 3b767b1c2235..bc5255695ad1 100644 --- 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 @@ -62,7 +62,7 @@ private MethodSpec resolveAuthSchemeMethod() { b.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT); b.addParameter(authSchemeSpecUtils.parametersInterfaceName(), "authSchemeParams"); b.returns(authSchemeSpecUtils.resolverReturnType()); - b.addJavadoc(resolveMethodJavadoc()); + b.addJavadoc("Resolve the auth schemes based on the given set of parameters."); return b.build(); } @@ -75,7 +75,7 @@ private MethodSpec resolveAuthSchemeConsumerBuilderMethod() { b.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT); b.addParameter(consumerType, "consumer"); b.returns(authSchemeSpecUtils.resolverReturnType()); - b.addJavadoc(resolveMethodJavadoc()); + b.addJavadoc("Resolve the auth schemes based on the given set of parameters."); b.addStatement("$T builder = $T.builder()", parametersBuilderInterface, parametersInterface); b.addStatement("consumer.accept(builder)"); @@ -104,12 +104,4 @@ private CodeBlock interfaceJavadoc() { 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 index 70ca08e21fbf..5724e2b78f57 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java @@ -18,31 +18,18 @@ import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; -import java.util.AbstractMap; -import java.util.Arrays; -import java.util.Collection; 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.Objects; import java.util.Set; -import java.util.TreeSet; -import java.util.stream.Collectors; import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig; import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; -import software.amazon.awssdk.codegen.model.service.AuthType; import software.amazon.awssdk.codegen.utils.AuthUtils; -import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; -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 static final Set DEFAULT_AUTH_SCHEME_PARAMS = setOf("region", "operation"); private final IntermediateModel intermediateModel; private final boolean useSraAuth; private final Set allowedEndpointAuthSchemeParams; @@ -164,189 +151,10 @@ 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; - } - - /** - * Computes a map from operations to codegen metadata objects. The intermediate model is used to compute mappings to - * {@link AuthType} values for the service and for each operation that has an override. Then we group all the operations - * that share the same set of auth types together and finally convert the auth types to their corresponding codegen - * metadata instances that then we can use to codegen switch statements. The service wide codegen metadata instances are - * keyed using {@link Collections#emptyList()}. - * - * @see #computeServiceWideDefaults - */ - private Map, List> operationsToModeledMetadata() { - Map, List> operationsToAuthType = operationsToAuthType(); - Map, List> operationsToMetadata = new LinkedHashMap<>(); - operationsToAuthType.forEach((k, v) -> operationsToMetadata.put(k, authTypeToCodegenMetadata(v))); - return operationsToMetadata; - } - - public Map, List> operationsToMetadata() { - List serviceDefaults = serviceDefaultAuthTypes(); - if (serviceDefaults.size() == 1) { - String authTypeName = serviceDefaults.get(0).value(); - SigV4SignerDefaults defaults = AuthTypeToSigV4Default.authTypeToDefaults().get(authTypeName); - if (areServiceWide(defaults)) { - return computeServiceWideDefaults(defaults); - } - } - return operationsToModeledMetadata(); - } - - /** - * Similar to {@link #operationsToModeledMetadata()} computes a map from operations to codegen metadata objects. The - * service default list of codegen metadata is keyed with {@link Collections#emptyList()}. - * - * This map is used to codegen switch statements. - */ - private Map, List> computeServiceWideDefaults(SigV4SignerDefaults defaults) { - Map> defaultsToOperations = - defaults.operations() - .entrySet() - .stream() - .map(kvp -> new AbstractMap.SimpleEntry<>(kvp.getKey(), kvp.getValue())) - .collect(Collectors.groupingBy(AbstractMap.SimpleEntry::getValue, - Collectors.mapping(AbstractMap.SimpleEntry::getKey, - Collectors.toList()))); - - Map, SigV4SignerDefaults> operationsToDefaults = - defaultsToOperations.entrySet() - .stream() - .sorted(Comparator.comparing(left -> left.getValue().get(0))) - .collect(Collectors.toMap(Map.Entry::getValue, - Map.Entry::getKey, (a, b) -> b, - LinkedHashMap::new)); - - Map, List> result = new LinkedHashMap<>(); - for (Map.Entry, SigV4SignerDefaults> kvp : operationsToDefaults.entrySet()) { - result.put(kvp.getKey(), - Arrays.asList(AuthSchemeCodegenMetadata.fromConstants(kvp.getValue()))); - } - result.put(Collections.emptyList(), Arrays.asList(AuthSchemeCodegenMetadata.fromConstants(defaults))); - return result; - } - - public boolean areServiceWide(SigV4SignerDefaults defaults) { - return defaults != null - && defaults.service() != null - && Objects.equals(intermediateModel.getMetadata().getServiceName(), defaults.service()); - } - - public Map, AuthSchemeCodegenMetadata> operationsToNonStandardSigv4Metadata() { - Map, AuthSchemeCodegenMetadata> result = - operationsToMetadata() - .entrySet() - .stream() - .filter(kvp -> containsNonStandardSigV4(kvp.getValue())) - .map(kvp -> new AbstractMap.SimpleEntry<>( - kvp.getKey(), - findNonStandardSigV4(kvp.getValue()))) - .collect( - Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue, - (a, b) -> b, - LinkedHashMap::new)); - return result; - } - - private boolean containsNonStandardSigV4(List options) { - return findNonStandardSigV4(options) != null; - } - - private AuthSchemeCodegenMetadata findNonStandardSigV4(List options) { - Map defaultSigv4Properties = - AuthSchemeCodegenMetadata.constantProperties(AuthSchemeCodegenMetadata.SIGV4); - for (AuthSchemeCodegenMetadata metadata : options) { - if (metadata.authSchemeClass() != AwsV4AuthScheme.class) { - continue; - } - Map sigv4Properties = AuthSchemeCodegenMetadata.constantProperties(metadata); - if (defaultSigv4Properties.equals(sigv4Properties)) { - return null; - } - List properties = - metadata - .properties() - .stream() - .filter(AuthSchemeSpecUtils::isNonDefaultSigv4Property) - .collect(Collectors.toList()); - if (!properties.isEmpty()) { - return metadata.toBuilder().properties(properties).build(); - } - return null; - } - return null; - } - - private static boolean isNonDefaultSigv4Property(AuthSchemeCodegenMetadata.SignerPropertyValueProvider provider) { - switch (provider.fieldName()) { - case "SERVICE_SIGNING_NAME": - case "REGION_NAME": - case "DOUBLE_URL_ENCODE": - return false; - default: - return true; - } - } - - private List authTypeToCodegenMetadata(List authTypes) { - return authTypes.stream().map(AuthSchemeCodegenMetadata::fromAuthType).collect(Collectors.toList()); - } - - public List serviceDefaultAuthTypes() { - List modeled = intermediateModel.getMetadata().getAuth(); - if (!modeled.isEmpty()) { - return modeled; - } - return Collections.singletonList(intermediateModel.getMetadata().getAuthType()); - } - - public Set> allServiceConcreteAuthSchemeClasses() { - Set> result = operationsToMetadata() - .values() - .stream() - .flatMap(Collection::stream) - .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; + private static Set setOf(String val1, String val2) { + Set result = new HashSet<>(); + result.add(val1); + result.add(val2); + return Collections.unmodifiableSet(result); } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeToSigV4Default.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeToSigV4Default.java index cb3f438c7a1f..02f6a682e3f4 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeToSigV4Default.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeToSigV4Default.java @@ -24,7 +24,8 @@ import software.amazon.awssdk.utils.Lazy; /** - * Contains maps from all {@link AuthType} to {@link SigV4SignerDefaults} that we can then transform for use in codegen. + * Contains maps from all sigv4 based {@link AuthType} to {@link SigV4SignerDefaults} that we can then transform for use in + * codegen. */ public final class AuthTypeToSigV4Default { @@ -58,18 +59,9 @@ public static Map authTypeToDefaults() { return AUTH_TYPE_TO_DEFAULTS.getValue(); } - /** - * Returns a mapping from an auth-type name to a set of AWS sigV4 default values.The auth-type names are the same as the - * {@link AuthType} enum values. - * - * @see SigV4SignerDefaults - */ - public static Map authTypeToDefaults0() { - return AUTH_TYPE_TO_DEFAULTS.getValue(); - } - /** * Returns the list fo all known auth types to s3v4Defaults instances. + * * @return */ public static List knownAuthTypes() { @@ -140,16 +132,11 @@ private static SigV4SignerDefaults s3Defaults() { * Set of default signer defaults for auth-type s3v4. Currently only used by S3Control. */ private static SigV4SignerDefaults s3v4Defaults() { - SigV4SignerDefaults s3v4 = sigv4Default().toBuilder() - .authType("s3v4") - .doubleUrlEncode(false) - .normalizePath(false) - // Figure out if s3Control uses this - // .payloadSigningEnabled(false) - .build(); - - // TODO: Figure out if there are any ops overrides needed that are currently habdled by interceptors. - return s3v4; + return sigv4Default().toBuilder() + .authType("s3v4") + .doubleUrlEncode(false) + .normalizePath(false) + .build(); } 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 index 80986b9f5a6b..c921153ac395 100644 --- 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 @@ -50,10 +50,12 @@ public class EndpointBasedAuthSchemeProviderSpec implements ClassSpec { private final AuthSchemeSpecUtils authSchemeSpecUtils; private final EndpointRulesSpecUtils endpointRulesSpecUtils; + private final SigV4AuthSchemeCodegenKnowledgeIndex sigV4AuthSchemeCodegenKnowledgeIndex; public EndpointBasedAuthSchemeProviderSpec(IntermediateModel intermediateModel) { this.authSchemeSpecUtils = new AuthSchemeSpecUtils(intermediateModel); this.endpointRulesSpecUtils = new EndpointRulesSpecUtils(intermediateModel); + this.sigV4AuthSchemeCodegenKnowledgeIndex = SigV4AuthSchemeCodegenKnowledgeIndex.of(intermediateModel); } @Override @@ -63,9 +65,6 @@ public ClassName className() { @Override public TypeSpec poetSpec() { - Map, AuthSchemeCodegenMetadata> operationsToSigv4 = - authSchemeSpecUtils.operationsToNonStandardSigv4Metadata(); - boolean applyServiceDefaults = !operationsToSigv4.isEmpty(); TypeSpec.Builder builder = PoetUtils.createClassBuilder(className()) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addAnnotation(SdkInternalApi.class) @@ -75,11 +74,12 @@ public TypeSpec poetSpec() { .addField(modeledResolverInstance()) .addField(endpointDelegateInstance()) .addMethod(createMethod()) - .addMethod(resolveAuthSchemeMethod(applyServiceDefaults)) + .addMethod(resolveAuthSchemeMethod()) .addMethod(endpointProvider()); + boolean applyServiceDefaults = sigV4AuthSchemeCodegenKnowledgeIndex.hasSigV4Overrides(); if (applyServiceDefaults) { - builder.addMethod(addV4Defaults(operationsToSigv4)); + builder.addMethod(addV4Defaults()); } return builder.build(); } @@ -136,7 +136,7 @@ private MethodSpec createMethod() { .build(); } - private MethodSpec resolveAuthSchemeMethod(boolean applyServiceDefaults) { + private MethodSpec resolveAuthSchemeMethod() { MethodSpec.Builder spec = MethodSpec.methodBuilder("resolveAuthScheme") .addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) @@ -164,38 +164,39 @@ private MethodSpec resolveAuthSchemeMethod(boolean applyServiceDefaults) { 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, applyServiceDefaults); + addAuthSchemeSwitch(spec); spec.endControlFlow(); return spec.addStatement("return $T.unmodifiableList(options)", Collections.class) .build(); } - private void addAuthSchemeSwitch(MethodSpec.Builder spec, boolean applyServiceDefaults) { + private void addAuthSchemeSwitch(MethodSpec.Builder spec) { spec.addStatement("$T name = authScheme.name()", String.class); spec.beginControlFlow("switch(name)"); - addAuthSchemeSwitchSigV4Case(spec, applyServiceDefaults); - addAuthSchemeSwitchSigV4aCase(spec, applyServiceDefaults); + addAuthSchemeSwitchSigV4Case(spec); + addAuthSchemeSwitchSigV4aCase(spec); if (endpointRulesSpecUtils.useS3Express()) { - addAuthSchemeSwitchS3ExpressCase(spec, applyServiceDefaults); + addAuthSchemeSwitchS3ExpressCase(spec); } addAuthSchemeSwitchDefaultCase(spec); spec.endControlFlow(); } - private void addAuthSchemeSwitchSigV4Case(MethodSpec.Builder spec, boolean applyServiceDefaults) { + 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"); CodeBlock.Builder block = CodeBlock.builder(); - block.add("$1T.builder().schemeId($2T.SCHEME_ID)", AuthSchemeOption.class, AwsV4AuthScheme.class) - .add(".putSignerProperty($T.SERVICE_SIGNING_NAME, sigv4AuthScheme.signingName())", AwsV4HttpSigner.class) - .add(".putSignerProperty($T.REGION_NAME, sigv4AuthScheme.signingRegion())", AwsV4HttpSigner.class) - .add(".putSignerProperty($T.DOUBLE_URL_ENCODE, !sigv4AuthScheme.disableDoubleEncoding())", + block.add("$T.builder()", AuthSchemeOption.class) + .add("\n.schemeId($T.SCHEME_ID)", AwsV4AuthScheme.class) + .add("\n.putSignerProperty($T.SERVICE_SIGNING_NAME, sigv4AuthScheme.signingName())", AwsV4HttpSigner.class) + .add("\n.putSignerProperty($T.REGION_NAME, sigv4AuthScheme.signingRegion())", AwsV4HttpSigner.class) + .add("\n.putSignerProperty($T.DOUBLE_URL_ENCODE, !sigv4AuthScheme.disableDoubleEncoding())", AwsV4HttpSigner.class); - if (applyServiceDefaults) { + if (sigV4AuthSchemeCodegenKnowledgeIndex.hasSigV4Overrides()) { spec.addCode("$1T sigv4AuthSchemeOption = applySigV4FamilyDefaults(", AuthSchemeOption.class) .addCode(block.build()) .addCode(", params)") @@ -209,7 +210,7 @@ private void addAuthSchemeSwitchSigV4Case(MethodSpec.Builder spec, boolean apply spec.addStatement("break"); } - private void addAuthSchemeSwitchSigV4aCase(MethodSpec.Builder spec, boolean applyServiceDefaults) { + private void addAuthSchemeSwitchSigV4aCase(MethodSpec.Builder spec) { spec.addCode("case $S:", "sigv4a"); spec.addStatement("$T sigv4aAuthScheme = $T.isInstanceOf($T.class, authScheme, $S, authScheme.getClass().getName())", @@ -221,11 +222,11 @@ private void addAuthSchemeSwitchSigV4aCase(MethodSpec.Builder spec, boolean appl CodeBlock.Builder block = CodeBlock.builder(); block.add("$1T.builder().schemeId($2T.SCHEME_ID)", AuthSchemeOption.class, AwsV4aAuthScheme.class) - .add(".putSignerProperty($T.SERVICE_SIGNING_NAME, sigv4aAuthScheme.signingName())", AwsV4HttpSigner.class) - .add(".putSignerProperty($T.REGION_SET, regionSet)", AwsV4aHttpSigner.class) - .add(".putSignerProperty($T.DOUBLE_URL_ENCODE, !sigv4aAuthScheme.disableDoubleEncoding())", AwsV4HttpSigner.class); + .add("\n.putSignerProperty($T.SERVICE_SIGNING_NAME, sigv4aAuthScheme.signingName())", AwsV4HttpSigner.class) + .add("\n.putSignerProperty($T.REGION_SET, regionSet)", AwsV4aHttpSigner.class) + .add("\n.putSignerProperty($T.DOUBLE_URL_ENCODE, !sigv4aAuthScheme.disableDoubleEncoding())", AwsV4HttpSigner.class); - if (applyServiceDefaults) { + if (sigV4AuthSchemeCodegenKnowledgeIndex.hasSigV4Overrides()) { spec.addCode("$1T sigv4aAuthSchemeOption = applySigV4FamilyDefaults(", AuthSchemeOption.class) .addCode(block.build()) .addCode(", params)") @@ -240,7 +241,7 @@ private void addAuthSchemeSwitchSigV4aCase(MethodSpec.Builder spec, boolean appl spec.addStatement("break"); } - private void addAuthSchemeSwitchS3ExpressCase(MethodSpec.Builder spec, boolean applyServiceDefaults) { + private void addAuthSchemeSwitchS3ExpressCase(MethodSpec.Builder spec) { spec.addCode("case $S:", "sigv4-s3express"); ClassName s3ExpressEndpointAuthScheme = ClassName.get( authSchemeSpecUtils.baseClientPackageName() + ".endpoints.authscheme", @@ -255,12 +256,12 @@ private void addAuthSchemeSwitchS3ExpressCase(MethodSpec.Builder spec, boolean a CodeBlock.Builder block = CodeBlock.builder(); block.add("$1T.builder().schemeId($2T.SCHEME_ID)", AuthSchemeOption.class, s3ExpressAuthScheme) - .add(".putSignerProperty($T.SERVICE_SIGNING_NAME, s3ExpressAuthScheme.signingName())", AwsV4HttpSigner.class) - .add(".putSignerProperty($T.REGION_NAME, s3ExpressAuthScheme.signingRegion())", AwsV4HttpSigner.class) - .add(".putSignerProperty($T.DOUBLE_URL_ENCODE, !s3ExpressAuthScheme.disableDoubleEncoding())", + .add("\n.putSignerProperty($T.SERVICE_SIGNING_NAME, s3ExpressAuthScheme.signingName())", AwsV4HttpSigner.class) + .add("\n.putSignerProperty($T.REGION_NAME, s3ExpressAuthScheme.signingRegion())", AwsV4HttpSigner.class) + .add("\n.putSignerProperty($T.DOUBLE_URL_ENCODE, !s3ExpressAuthScheme.disableDoubleEncoding())", AwsV4HttpSigner.class); - if (applyServiceDefaults) { + if (sigV4AuthSchemeCodegenKnowledgeIndex.hasSigV4Overrides()) { spec.addCode("$1T s3ExpressAuthSchemeOption = applySigV4FamilyDefaults(", AuthSchemeOption.class) .addCode(block.build()) .addCode(", params)") @@ -281,32 +282,28 @@ private void addAuthSchemeSwitchDefaultCase(MethodSpec.Builder spec) { } - private MethodSpec addV4Defaults(Map, AuthSchemeCodegenMetadata> operationsToSigv4) { + private MethodSpec addV4Defaults() { MethodSpec.Builder spec = MethodSpec.methodBuilder("applySigV4FamilyDefaults") .addModifiers(Modifier.PRIVATE, Modifier.STATIC) .returns(AuthSchemeOption.Builder.class) .addParameter(AuthSchemeOption.Builder.class, "option") .addParameter(authSchemeSpecUtils.parametersInterfaceName(), "params"); - if (operationsToSigv4.isEmpty()) { - return spec.addStatement("return option").build(); - } - // All the operations share the same set of auth schemes, no need to create a switch statement. - if (operationsToSigv4.size() == 1) { - AuthSchemeCodegenMetadata authType = operationsToSigv4.get(Collections.emptyList()); + if (!sigV4AuthSchemeCodegenKnowledgeIndex.hasPerOperationSigV4Overrides()) { + AuthSchemeCodegenMetadata authType = sigV4AuthSchemeCodegenKnowledgeIndex.serviceSigV4Overrides(); addAuthTypeProperties(spec, authType); return spec.build(); } spec.beginControlFlow("switch(params.operation())"); - operationsToSigv4.forEach((ops, scheme) -> { + sigV4AuthSchemeCodegenKnowledgeIndex.forEachOperationsOverridesGroup((ops, scheme) -> { if (!ops.isEmpty()) { addCasesForOperations(spec, ops, scheme); } }); - AuthSchemeCodegenMetadata authType = operationsToSigv4.get(Collections.emptyList()); + AuthSchemeCodegenMetadata authType = sigV4AuthSchemeCodegenKnowledgeIndex.serviceSigV4Overrides(); if (authType != null) { - addCasesForOperations(spec, Collections.emptyList(), authType); + addCasesForDefault(spec, authType); } spec.endControlFlow(); return spec.build(); @@ -314,27 +311,23 @@ private MethodSpec addV4Defaults(Map, AuthSchemeCodegenMetadata> op private void addCasesForOperations(MethodSpec.Builder spec, List operations, AuthSchemeCodegenMetadata metadata) { - if (operations.isEmpty()) { - spec.addCode("default:"); - } else { - for (String name : operations) { - spec.addCode("case $S:", name); - } + for (String name : operations) { + spec.addCode("case $S:\n", name); } addAuthTypeProperties(spec, metadata); } - public void addAuthTypeProperties(MethodSpec.Builder spec, AuthSchemeCodegenMetadata metadata) { - spec.addCode("return option"); - for (AuthSchemeCodegenMetadata.SignerPropertyValueProvider property : metadata.properties()) { - if ("REGION_NAME".equals(property.fieldName())) { - continue; - } - spec.addCode(".putSignerPropertyIfAbsent($T.$N, ", property.containingClass(), property.fieldName()); - property.emitValue(spec, authSchemeSpecUtils); - spec.addCode(")"); - } + private void addCasesForDefault(MethodSpec.Builder spec, + AuthSchemeCodegenMetadata metadata) { + spec.addCode("default:\n"); + addAuthTypeProperties(spec, metadata); + } + + private void addAuthTypeProperties(MethodSpec.Builder spec, AuthSchemeCodegenMetadata metadata) { + spec.addCode("option"); + spec.addCode(AuthSchemeCodegenMetadataExt.codegenSignerPropertiesIfAbsent(authSchemeSpecUtils, metadata.properties())); spec.addStatement(""); + spec.addStatement("return option"); } private Map parameters() { diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java new file mode 100644 index 000000000000..92c6047301c7 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.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.codegen.poet.auth.scheme; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; + +/** + * Knowledge index of the configured auth schemes concrete classes. + */ +public final class ModelAuthSchemeClassesKnowledgeIndex { + private final Set> serviceConcreteAuthSchemeClasses; + + private ModelAuthSchemeClassesKnowledgeIndex(IntermediateModel intermediateModel) { + this.serviceConcreteAuthSchemeClasses = + getServiceConcreteAuthSchemeClasses(ModelAuthSchemeKnowledgeIndex.of(intermediateModel).operationsToMetadata(), + intermediateModel.getCustomizationConfig().isEnableEndpointAuthSchemeParams()); + } + + /** + * Creates a new {@link AuthSchemeCodegenKnowledgeIndex} using the given {@code intermediateModel}.. + */ + public static ModelAuthSchemeClassesKnowledgeIndex of(IntermediateModel intermediateModel) { + return new ModelAuthSchemeClassesKnowledgeIndex(intermediateModel); + } + + /** + * Returns the set of all the service supported concrete auth scheme classes. + */ + public Set> serviceConcreteAuthSchemeClasses() { + return serviceConcreteAuthSchemeClasses; + } + + private static Set> getServiceConcreteAuthSchemeClasses( + Map, List> operationsToAuthSchemes, + boolean useEndpointBasedAuthProvider + ) { + Set> result = operationsToAuthSchemes + .values() + .stream() + .flatMap(Collection::stream) + .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 Collections.unmodifiableSet(result); + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java new file mode 100644 index 000000000000..2b0a39145e43 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java @@ -0,0 +1,165 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.codegen.poet.auth.scheme; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.model.intermediate.OperationModel; +import software.amazon.awssdk.codegen.model.service.AuthType; + +/** + * Knowledge index to get access to the configured service auth schemes and operations overrides. This index is optimized for + * code generation of switch statements therefore the data is grouped by operations that share the same auth schemes. This + * index is a building block for {@link AuthSchemeCodegenKnowledgeIndex} and {@link SigV4AuthSchemeCodegenKnowledgeIndex} + * indexes that have a friendly interface for the codegen use cases. + */ +public final class ModelAuthSchemeKnowledgeIndex { + private final IntermediateModel intermediateModel; + + private ModelAuthSchemeKnowledgeIndex(IntermediateModel intermediateModel) { + this.intermediateModel = intermediateModel; + } + + /** + * Creates a new knowledge index using the given model. + */ + public static ModelAuthSchemeKnowledgeIndex of(IntermediateModel intermediateModel) { + return new ModelAuthSchemeKnowledgeIndex(intermediateModel); + } + + /** + * Returns a map from a list of operations to all the auth schemes that the operations accept. + * + * @return a map from a list of operations to all the auth schemes that the operations accept + */ + public Map, List> operationsToMetadata() { + List serviceDefaults = serviceDefaultAuthTypes(); + if (serviceDefaults.size() == 1) { + String authTypeName = serviceDefaults.get(0).value(); + SigV4SignerDefaults defaults = AuthTypeToSigV4Default.authTypeToDefaults().get(authTypeName); + if (areServiceWide(defaults)) { + return operationsToModeledMetadataFormSigV4Defaults(defaults); + } + } + return operationsToModeledMetadata(); + } + + /** + * Computes a map from operations to codegen metadata objects. The intermediate model is used to compute mappings to + * {@link AuthType} values for the service and for each operation that has an override. Then we group all the operations that + * share the same set of auth types together and finally convert the auth types to their corresponding codegen metadata + * instances that then we can use to codegen switch statements. The service wide codegen metadata instances are keyed using + * {@link Collections#emptyList()}. + */ + private Map, List> operationsToModeledMetadata() { + Map, List> operationsToAuthType = operationsToAuthType(); + Map, List> operationsToMetadata = new LinkedHashMap<>(); + operationsToAuthType.forEach((k, v) -> operationsToMetadata.put(k, authTypeToCodegenMetadata(v))); + return operationsToMetadata; + } + + /** + * Returns a map from list of operations to the list of auth-types modeled for those operations. The values are taken directly + * from the model {@link OperationModel#getAuth()} method. + */ + private Map, List> operationsToAuthType() { + Map, List> authSchemesToOperations = + 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(kvp -> kvp.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; + } + + + /** + * Similar to {@link #operationsToModeledMetadata()} computes a map from operations to codegen metadata objects. The service + * default list of codegen metadata is keyed with {@link Collections#emptyList()}. + */ + private Map, List> operationsToModeledMetadataFormSigV4Defaults( + SigV4SignerDefaults defaults + ) { + Map> defaultsToOperations = + defaults.operations() + .entrySet() + .stream() + .collect(Collectors.groupingBy(Map.Entry::getValue, + Collectors.mapping(Map.Entry::getKey, + Collectors.toList()))); + + Map, SigV4SignerDefaults> operationsToDefaults = + defaultsToOperations.entrySet() + .stream() + .sorted(Comparator.comparing(left -> left.getValue().get(0))) + .collect(Collectors.toMap(Map.Entry::getValue, + Map.Entry::getKey, (a, b) -> b, + LinkedHashMap::new)); + + Map, List> result = new LinkedHashMap<>(); + for (Map.Entry, SigV4SignerDefaults> kvp : operationsToDefaults.entrySet()) { + result.put(kvp.getKey(), + Arrays.asList(AuthSchemeCodegenMetadataExt.fromConstants(kvp.getValue()))); + } + result.put(Collections.emptyList(), Arrays.asList(AuthSchemeCodegenMetadataExt.fromConstants(defaults))); + return result; + } + + /** + * Returns the list of modeled top-level auth-types. + */ + private List serviceDefaultAuthTypes() { + List modeled = intermediateModel.getMetadata().getAuth(); + if (!modeled.isEmpty()) { + return modeled; + } + return Collections.singletonList(intermediateModel.getMetadata().getAuthType()); + } + + private List authTypeToCodegenMetadata(List authTypes) { + return authTypes.stream().map(AuthSchemeCodegenMetadataExt::fromAuthType).collect(Collectors.toList()); + } + + private boolean areServiceWide(SigV4SignerDefaults defaults) { + return defaults != null + && defaults.isServiceOverrideAuthScheme() + && Objects.equals(intermediateModel.getMetadata().getServiceName(), defaults.service()); + } +} 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 index 0d48a62153a7..79d5125e65c5 100644 --- 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 @@ -15,8 +15,6 @@ package software.amazon.awssdk.codegen.poet.auth.scheme; -import static software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeCodegenMetadata.SignerPropertyValueProvider; - import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; @@ -26,7 +24,6 @@ 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; @@ -36,9 +33,11 @@ public class ModelBasedAuthSchemeProviderSpec implements ClassSpec { private final AuthSchemeSpecUtils authSchemeSpecUtils; + private final AuthSchemeCodegenKnowledgeIndex knowledgeIndex; public ModelBasedAuthSchemeProviderSpec(IntermediateModel intermediateModel) { this.authSchemeSpecUtils = new AuthSchemeSpecUtils(intermediateModel); + this.knowledgeIndex = AuthSchemeCodegenKnowledgeIndex.of(intermediateModel); } @Override @@ -91,26 +90,20 @@ private MethodSpec resolveAuthSchemeMethod() { spec.addStatement("$T options = new $T<>()", ParameterizedTypeName.get(List.class, AuthSchemeOption.class), TypeName.get(ArrayList.class)); - Map, List> operationsToAuthType = authSchemeSpecUtils.operationsToMetadata(); - - // 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()); + if (knowledgeIndex.hasPerOperationAuthSchemesOverrides()) { + // We create a switch to return the auth schemes overrides per + // operation. + spec.beginControlFlow("switch(params.operation())"); + knowledgeIndex.forEachOperationsOverridesGroup((ops, schemes) -> addCasesForOperations(spec, ops, schemes)); + addCasesForOperations(spec, Collections.emptyList(), knowledgeIndex.serviceDefaultAuthSchemes()); + spec.endControlFlow(); + } else { + // All the operations share the same set of auth schemes, no need to create a switch statement. + List types = knowledgeIndex.serviceDefaultAuthSchemes(); for (AuthSchemeCodegenMetadata 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(); } @@ -118,10 +111,10 @@ private MethodSpec resolveAuthSchemeMethod() { private void addCasesForOperations(MethodSpec.Builder spec, List operations, List schemes) { if (operations.isEmpty()) { - spec.addCode("default:"); + spec.addCode("default:\n"); } else { for (String name : operations) { - spec.addCode("case $S:", name); + spec.addCode("case $S\n:", name); } } for (AuthSchemeCodegenMetadata metadata : schemes) { @@ -130,14 +123,10 @@ private void addCasesForOperations(MethodSpec.Builder spec, List operati spec.addStatement("break"); } - public void addAuthTypeProperties(MethodSpec.Builder spec, AuthSchemeCodegenMetadata metadata) { - 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"); + private void addAuthTypeProperties(MethodSpec.Builder spec, AuthSchemeCodegenMetadata metadata) { + spec.addCode("options.add("); + spec.addCode(AuthSchemeCodegenMetadataExt.codegenNewAuthOption(metadata, authSchemeSpecUtils)); + spec.addCode(".build()"); + spec.addCode(");\n"); } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/SigV4AuthSchemeCodegenKnowledgeIndex.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/SigV4AuthSchemeCodegenKnowledgeIndex.java new file mode 100644 index 000000000000..832d349dc195 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/SigV4AuthSchemeCodegenKnowledgeIndex.java @@ -0,0 +1,168 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.codegen.poet.auth.scheme; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeCodegenMetadata.SignerPropertyValueProvider; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; + +/** + * Knowledge index to compute the sets of operations that share the same set of sigv4 overrides. + */ +public final class SigV4AuthSchemeCodegenKnowledgeIndex { + private final Map, AuthSchemeCodegenMetadata> operationsToSigv4AuthScheme; + + private SigV4AuthSchemeCodegenKnowledgeIndex(IntermediateModel intermediateModel) { + this.operationsToSigv4AuthScheme = + operationsToSigv4AuthScheme(ModelAuthSchemeKnowledgeIndex.of(intermediateModel).operationsToMetadata()); + } + + /** + * Creates a new knowledge index from the given model. + */ + public static SigV4AuthSchemeCodegenKnowledgeIndex of(IntermediateModel intermediateModel) { + return new SigV4AuthSchemeCodegenKnowledgeIndex(intermediateModel); + } + + /** + * Returns the service overrides for sigv4. This method returns null if there are none configured. The service may or may not + * support sigv4 regardless. + */ + public AuthSchemeCodegenMetadata serviceSigV4Overrides() { + return operationsToSigv4AuthScheme.get(Collections.emptyList()); + } + + /** + * Returns true if there are any sigv4 overrides per operation. + * + * @return true if there are auth scheme overrides per operation + */ + public boolean hasPerOperationSigV4Overrides() { + if (operationsToSigv4AuthScheme.containsKey(Collections.emptyList())) { + return operationsToSigv4AuthScheme.size() > 1; + } + return !operationsToSigv4AuthScheme.isEmpty(); + } + + /** + * Returns true if there are any service wide sigv4 overrides. + */ + public boolean hasServiceSigV4Overrides() { + return serviceSigV4Overrides() != null; + } + + /** + * Returns true if there are sigv4 signer overrides in the model. + */ + public boolean hasSigV4Overrides() { + return hasServiceSigV4Overrides() || hasPerOperationSigV4Overrides(); + } + + + /** + * Traverses each group of operations with the same set of auth schemes. + * + * @param consumer The consumer to call for each group of operations with the same set of auth schemes. + */ + public void forEachOperationsOverridesGroup(BiConsumer, AuthSchemeCodegenMetadata> consumer) { + for (Map.Entry, AuthSchemeCodegenMetadata> kvp : operationsToSigv4AuthScheme.entrySet()) { + if (kvp.getKey().isEmpty()) { + // Ignore service wide defaults. + continue; + } + consumer.accept(kvp.getKey(), kvp.getValue()); + } + } + + /** + * Returns a map that groups all operations that share the ame set of sigv4 signer properties with override values. The + * service wide default values are encoded using {@link Collections#emptyList()} as a key and the value may be null. + */ + private Map, AuthSchemeCodegenMetadata> operationsToSigv4AuthScheme( + Map, List> operationsToMetadata + ) { + Map, AuthSchemeCodegenMetadata> result = new HashMap<>(); + for (Map.Entry, List> kvp : operationsToMetadata.entrySet()) { + AuthSchemeCodegenMetadata sigv4 = sigV4AuthSchemeWithConstantOverrides(kvp.getValue()); + if (sigv4 != null) { + result.put(kvp.getKey(), sigv4); + } + } + return result; + } + + /** + * Finds the sigv4 auth scheme from the list and transforms it to remove any signer property that does not have a constant + * value. Returns null if there are no signer properties with constant values or if the sigv4 auth scheme is not found. + */ + private AuthSchemeCodegenMetadata sigV4AuthSchemeWithConstantOverrides(List authSchemes) { + AuthSchemeCodegenMetadata sigv4 = findSigV4AuthScheme(authSchemes); + if (sigv4 == null) { + return null; + } + List signerPropertiesWithConstantValues = + filterSignerPropertiesWithConstantValues(sigv4.properties()); + + // No signer properties with overrides, we return null: we are only + // interested when there are any properties with constant values for codegen. + if (signerPropertiesWithConstantValues.isEmpty()) { + return null; + } + // Return the auth scheme but only retain the properties with constant values. + return sigv4.toBuilder() + .properties(signerPropertiesWithConstantValues) + .build(); + } + + /** + * Returns a new list of singer properties with only those properties that use a constant value. + */ + private List filterSignerPropertiesWithConstantValues( + List properties + ) { + List result = null; + for (SignerPropertyValueProvider property : properties) { + if (property.isConstant()) { + if (result == null) { + result = new ArrayList<>(); + } + result.add(property); + } + } + if (result != null) { + return result; + } + return Collections.emptyList(); + } + + /** + * Filters out the auth scheme with scheme id "aws.auth#sigv4". Returns {@code null} if not found. + */ + private AuthSchemeCodegenMetadata findSigV4AuthScheme(List authSchemes) { + for (AuthSchemeCodegenMetadata metadata : authSchemes) { + if (metadata.schemeId().equals(AwsV4AuthScheme.SCHEME_ID)) { + return metadata; + } + } + return null; + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/SigV4SignerDefaults.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/SigV4SignerDefaults.java index cb7123351679..4d6a69786ab0 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/SigV4SignerDefaults.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/SigV4SignerDefaults.java @@ -23,9 +23,8 @@ /** * Tracks a set of explicitly enabled signer properties for the family of AWS SigV4 signers. The currently supported attributes - * are {@code doubleUrlEncode}, {@code normalizePath}, {@code payloadSigningEnabled}, {@code chunkEncodingEnabled}. If their - * value is not null then is taken as-is, otherwise is ignored as in the value is not overridden. An auth type can also - * represent a service-wide set of defaults when the set of operations is not empty. + * are {@code doubleUrlEncode}, {@code normalizePath}, {@code payloadSigningEnabled}, {@code chunkEncodingEnabled}. If the + * value is null then is not overridden. An auth type can also represent a service-wide set of defaults. */ public final class SigV4SignerDefaults { private final String service; @@ -37,7 +36,7 @@ public final class SigV4SignerDefaults { private final Boolean chunkEncodingEnabled; private final Map operations; - public SigV4SignerDefaults(Builder builder) { + private SigV4SignerDefaults(Builder builder) { this.service = builder.service; this.authType = Validate.notNull(builder.authType, "authType"); this.schemeId = Validate.notNull(builder.schemeId, "schemeId"); @@ -48,6 +47,10 @@ public SigV4SignerDefaults(Builder builder) { this.operations = Collections.unmodifiableMap(new HashMap<>(builder.operations)); } + public boolean isServiceOverrideAuthScheme() { + return service != null; + } + public String service() { return service; } 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 9c6d540b736e..ed8418e1dc7c 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 @@ -51,6 +51,7 @@ 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.auth.scheme.ModelAuthSchemeClassesKnowledgeIndex; import software.amazon.awssdk.codegen.poet.model.ServiceClientConfigurationUtils; import software.amazon.awssdk.codegen.poet.rules.EndpointRulesSpecUtils; import software.amazon.awssdk.codegen.utils.AuthUtils; @@ -724,7 +725,8 @@ private MethodSpec authSchemesMethod() { .addModifiers(PRIVATE) .returns(returns); - Set> concreteAuthSchemeClasses = authSchemeSpecUtils.allServiceConcreteAuthSchemeClasses(); + ModelAuthSchemeClassesKnowledgeIndex index = ModelAuthSchemeClassesKnowledgeIndex.of(model); + Set> concreteAuthSchemeClasses = index.serviceConcreteAuthSchemeClasses(); builder.addStatement("$T schemes = new $T<>($L + this.additionalAuthSchemes.size())", returns, HashMap.class, concreteAuthSchemeClasses.size()); for (Class concreteAuthScheme : concreteAuthSchemeClasses) { 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 7a72cc7637e9..1d54bd7e7928 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 @@ -58,6 +58,7 @@ 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.auth.scheme.ModelAuthSchemeClassesKnowledgeIndex; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.exception.SdkClientException; @@ -96,7 +97,8 @@ public EndpointResolverInterceptorSpec(IntermediateModel 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(); + Set> supportedAuthSchemes = + ModelAuthSchemeClassesKnowledgeIndex.of(model).serviceConcreteAuthSchemeClasses(); this.dependsOnHttpAuthAws = supportedAuthSchemes.contains(AwsV4AuthScheme.class) || supportedAuthSchemes.contains(AwsV4aAuthScheme.class); diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java index 23eca7eac038..253eadc0f59f 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java @@ -73,47 +73,46 @@ public void syncComposedDefaultClientBuilderClass() { validateBaseClientBuilderClassGeneration(composedClientJsonServiceModels(), "test-composed-sync-default-client-builder.java"); } - - + @Test - public void baseClientBuilderClass_sra() { + void baseClientBuilderClass_sra() { validateBaseClientBuilderClassGeneration(restJsonServiceModels(), "test-client-builder-class.java", true); } @Test - public void baseClientBuilderClassWithBearerAuth_sra() { + void baseClientBuilderClassWithBearerAuth_sra() { validateBaseClientBuilderClassGeneration(bearerAuthServiceModels(), "test-bearer-auth-client-builder-class.java", true); } @Test - public void baseClientBuilderClassWithNoAuthOperation_sra() { + void baseClientBuilderClassWithNoAuthOperation_sra() { validateBaseClientBuilderClassGeneration(operationWithNoAuth(), "test-no-auth-ops-client-builder-class.java", true); } @Test - public void baseClientBuilderClassWithNoAuthService_sra() { + void baseClientBuilderClassWithNoAuthService_sra() { validateBaseClientBuilderClassGeneration(serviceWithNoAuth(), "test-no-auth-service-client-builder-class.java", true); } @Test - public void baseClientBuilderClassWithInternalUserAgent_sra() { + void baseClientBuilderClassWithInternalUserAgent_sra() { validateBaseClientBuilderClassGeneration(internalConfigModels(), "test-client-builder-internal-defaults-class.java", true); } @Test - public void baseQueryClientBuilderClass_sra() { + void baseQueryClientBuilderClass_sra() { validateBaseClientBuilderClassGeneration(queryServiceModels(), "test-query-client-builder-class.java", true); } @Test - public void baseClientBuilderClassWithEndpointsAuthParams_sra() { + void baseClientBuilderClassWithEndpointsAuthParams_sra() { validateBaseClientBuilderClassGeneration(queryServiceModelsEndpointAuthParamsWithAllowList(), "test-client-builder-endpoints-auth-params.java", true); } @Test - public void syncComposedDefaultClientBuilderClass_sra() { + void syncComposedDefaultClientBuilderClass_sra() { validateBaseClientBuilderClassGeneration(composedClientJsonServiceModels(), "test-composed-sync-default-client-builder.java", true); } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/s3express/S3DisableChunkEncodingAuthSchemeProvider.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingAuthSchemeProvider.java similarity index 97% rename from services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/s3express/S3DisableChunkEncodingAuthSchemeProvider.java rename to services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingAuthSchemeProvider.java index 9e683169a3b9..c02814d5273d 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/s3express/S3DisableChunkEncodingAuthSchemeProvider.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingAuthSchemeProvider.java @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package software.amazon.awssdk.services.s3.internal.s3express; +package software.amazon.awssdk.services.s3.internal.plugins; import java.util.ArrayList; import java.util.List; diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPlugin.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPlugin.java index ab48744d16c0..a37ccfa1c8d1 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPlugin.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPlugin.java @@ -24,13 +24,15 @@ import software.amazon.awssdk.services.s3.S3Configuration; import software.amazon.awssdk.services.s3.S3ServiceClientConfiguration; import software.amazon.awssdk.services.s3.auth.scheme.S3AuthSchemeProvider; -import software.amazon.awssdk.services.s3.internal.s3express.S3DisableChunkEncodingAuthSchemeProvider; import software.amazon.awssdk.utils.Logger; /** - * Internal plugin that uses the check if {@link S3Configuration#chunkedEncodingEnabled()} is configured and equals to {@code - * false}, if so, then it installs an instance of {@link S3DisableChunkEncodingAuthSchemeProvider} wrapping the configured + * Internal plugin that uses the check if {@link S3Configuration#chunkedEncodingEnabled()} is configured and equals to + * {@code false}, if so, then it installs an instance of {@link S3DisableChunkEncodingAuthSchemeProvider} wrapping the configured * {@link S3AuthSchemeProvider} that sets {@link AwsV4FamilyHttpSigner#CHUNK_ENCODING_ENABLED} to false. + *

+ * This pre SRA logic was implemented before using an interceptor but now requires wrapping the S3AuthSchemeProvider for it to + * work. */ @SdkInternalApi public final class S3DisableChunkEncodingIfConfiguredPlugin implements SdkPlugin { @@ -72,22 +74,9 @@ public void configureClient(SdkServiceClientConfiguration.Builder config) { + "`S3DisableChunkEncodingAuthSchemeProvider` auth provider wrapper.")); S3ServiceClientConfiguration.Builder s3Config = (S3ServiceClientConfiguration.Builder) config; - // We wrap the default S3AuthSchemeProvider with a S3DisableChunkEncodingAuthSchemeProvider - // instance that sets the AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED signer - // property to false but only if the operations is `"PutObject" or "UploadPart"` - // This legacy logic was implemented before using an interceptor but now requires - // wrapping the S3AuthSchemeProvider for it to work. S3AuthSchemeProvider disablingAuthSchemeProvider = S3DisableChunkEncodingAuthSchemeProvider.create(s3Config.authSchemeProvider()); s3Config.authSchemeProvider(disablingAuthSchemeProvider); - } else { - LOG.debug(() -> String.format("chunkedEncodingEnabled was not explicitly disabled in the configuration, not adding " - + "the `S3DisableChunkEncodingAuthSchemeProvider` auth provider wrapper." - + "[isServiceConfigurationPresent: %s, isChunkedEncodingEnabledConfigured: %s, " - + "isChunkedEncodingEnabledDisabled: %s]", - isServiceConfigurationPresent, - isChunkedEncodingEnabledConfigured, - isChunkedEncodingEnabledDisabled)); } } } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3OverrideAuthSchemePropertiesPlugin.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3OverrideAuthSchemePropertiesPlugin.java index 161843a5a338..ef05dc51df4e 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3OverrideAuthSchemePropertiesPlugin.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3OverrideAuthSchemePropertiesPlugin.java @@ -18,8 +18,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.SdkServiceClientConfiguration; @@ -29,6 +31,7 @@ import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; import software.amazon.awssdk.identity.spi.IdentityProperty; import software.amazon.awssdk.services.s3.S3ServiceClientConfiguration; +import software.amazon.awssdk.services.s3.auth.scheme.S3AuthSchemeParams; import software.amazon.awssdk.services.s3.auth.scheme.S3AuthSchemeProvider; /** @@ -59,6 +62,7 @@ public final class S3OverrideAuthSchemePropertiesPlugin implements SdkPlugin { private final Map, Object> identityProperties; private final Map, Object> signerProperties; + private final Set operationConstraints; private S3OverrideAuthSchemePropertiesPlugin(Builder builder) { if (builder.identityProperties.isEmpty()) { @@ -71,6 +75,11 @@ private S3OverrideAuthSchemePropertiesPlugin(Builder builder) { } else { this.signerProperties = Collections.unmodifiableMap(new HashMap<>(builder.signerProperties)); } + if (builder.operationConstraints.isEmpty()) { + this.operationConstraints = Collections.emptySet(); + } else { + this.operationConstraints = Collections.unmodifiableSet(new HashSet<>(builder.operationConstraints)); + } } @Override @@ -84,10 +93,10 @@ public void configureClient(SdkServiceClientConfiguration.Builder config) { List options = delegate.resolveAuthScheme(params); List result = new ArrayList<>(options.size()); for (AuthSchemeOption option : options) { - String schemeId = option.schemeId(); // We check here that the scheme id is sigV4 or sigV4a or some other in the same family. - // We don't set the overrides for non-sigV4 auth schemes. - if (schemeId.startsWith(AwsV4AuthScheme.SCHEME_ID)) { + // We don't set the overrides for non-sigV4 auth schemes. If the plugin was configured to + // constraint using operations then that's also checked on the call below. + if (addConfiguredProperties(option, params)) { AuthSchemeOption.Builder builder = option.toBuilder(); identityProperties.forEach((k, v) -> putIdentityProperty(builder, k, v)); signerProperties.forEach((k, v) -> putSingerProperty(builder, k, v)); @@ -113,6 +122,18 @@ private void putSingerProperty(AuthSchemeOption.Builder builder, SignerPrope } + private boolean addConfiguredProperties(AuthSchemeOption option, S3AuthSchemeParams params) { + String schemeId = option.schemeId(); + // We check here that the scheme id is sigV4 or sigV4a or some other in the same family. + // We don't set the overrides for non-sigV4 auth schemes. + if (schemeId.startsWith(AwsV4AuthScheme.SCHEME_ID)) { + if (this.operationConstraints.isEmpty() || this.operationConstraints.contains(params.operation())) { + return true; + } + } + return false; + } + /** * Creates a new plugin that enables payload signing. This plugin can be used per client or by per-request. */ @@ -122,6 +143,18 @@ public static SdkPlugin enablePayloadSigningPlugin() { .build(); } + /** + * Creates a new plugin that disables the ChunkEncoding signers property for the `UploadPart` and `PutObject` operations. + * This plugin can be used per client or by per-request. + */ + public static SdkPlugin disableChunkEncodingPlugin() { + return builder() + .chunkEncodingEnabled(false) + .addOperationConstraint("UploadPart") + .addOperationConstraint("PutObject") + .build(); + } + /** * Creates a new builder to configure the plugin. */ @@ -132,6 +165,15 @@ public static Builder builder() { public static class Builder { private final Map, Object> identityProperties = new HashMap<>(); private final Map, Object> signerProperties = new HashMap<>(); + private final Set operationConstraints = new HashSet<>(); + + /** + * Adds an operation constraint to use the configured properties. + */ + public Builder addOperationConstraint(String operation) { + this.operationConstraints.add(operation); + return this; + } /** * Adds the provided property value as an override. diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPluginTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPluginTest.java deleted file mode 100644 index bcfeae126b2c..000000000000 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPluginTest.java +++ /dev/null @@ -1,280 +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.s3.internal.plugins; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -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.AwsCredentialsProvider; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.core.SelectedAuthScheme; -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.sync.RequestBody; -import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; -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.SignerProperty; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.S3ClientBuilder; -import software.amazon.awssdk.services.s3.S3Configuration; -import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; -import software.amazon.awssdk.services.s3.model.UploadPartRequest; - -class S3DisableChunkEncodingIfConfiguredPluginTest { - private static final String DEFAULT_BUCKET = "bucket"; - private static final String DEFAULT_KEY = "key"; - private static final String PUT_BODY = "Hello from Java SDK"; - - private static final AwsCredentialsProvider CREDENTIALS_PROVIDER = - StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")); - - CapturingInterceptor capturingInterceptor = null; - - @BeforeEach - void setup() { - capturingInterceptor = new CapturingInterceptor(); - } - - @Test - void testUploadPartEnablesChunkEncodingByDefault() { - S3Client syncClient = getS3ClientBuilder() - .build(); - UploadPartRequest.Builder requestBuilder = - UploadPartRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY).partNumber(0).uploadId("test"); - assertThatThrownBy(() -> syncClient.uploadPart(requestBuilder.build(), RequestBody.fromString(PUT_BODY))) - .hasMessageContaining("boom"); - - AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() - .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, true) - .build(); - Map, Object> expectedProperties = signerProperties(expectedValues); - assertThat(selectSignerProperties(signerProperties(authSchemeOption()), expectedProperties.keySet())) - .isEqualTo(expectedProperties); - } - - @Test - void testUploadPartDisablesChunkEncodingWhenConfigured() { - S3Client syncClient = getS3ClientBuilder() - .serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(false).build()) - .build(); - UploadPartRequest.Builder requestBuilder = - UploadPartRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY).partNumber(0).uploadId("test"); - assertThatThrownBy(() -> syncClient.uploadPart(requestBuilder.build(), RequestBody.fromString(PUT_BODY))) - .hasMessageContaining("boom"); - - AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() - .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, false) - .build(); - Map, Object> expectedProperties = signerProperties(expectedValues); - assertThat(selectSignerProperties(signerProperties(authSchemeOption()), expectedProperties.keySet())) - .isEqualTo(expectedProperties); - } - - @Test - void testPutObjectEnablesChunkEncodingByDefault() { - S3Client syncClient = getS3ClientBuilder() - .build(); - PutObjectRequest.Builder requestBuilder = - PutObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); - assertThatThrownBy(() -> syncClient.putObject(requestBuilder.build(), RequestBody.fromString(PUT_BODY))) - .hasMessageContaining("boom"); - - AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() - .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, true) - .build(); - Map, Object> expectedProperties = signerProperties(expectedValues); - assertThat(selectSignerProperties(signerProperties(authSchemeOption()), expectedProperties.keySet())) - .isEqualTo(expectedProperties); - } - - @Test - void testPutObjectDisablesChunkEncodingWhenConfigured() { - S3Client syncClient = getS3ClientBuilder() - .serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(false).build()) - .build(); - PutObjectRequest.Builder requestBuilder = - PutObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); - assertThatThrownBy(() -> syncClient.putObject(requestBuilder.build(), RequestBody.fromString(PUT_BODY))) - .hasMessageContaining("boom"); - - AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() - .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, false) - .build(); - Map, Object> expectedProperties = signerProperties(expectedValues); - assertThat(selectSignerProperties(signerProperties(authSchemeOption()), expectedProperties.keySet())) - .isEqualTo(expectedProperties); - } - - @Test - void testGetObjectDoesNotSetChunkEncoding() { - S3Client syncClient = getS3ClientBuilder() - .serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(true).build()) - .build(); - GetObjectRequest.Builder requestBuilder = - GetObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); - assertThatThrownBy(() -> syncClient.getObject(requestBuilder.build())) - .hasMessageContaining("boom"); - - AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() - .build(); - Map, Object> expectedProperties = signerProperties(expectedValues); - Map, Object> givenProperties = signerProperties(authSchemeOption()); - assertThat(selectSignerProperties(givenProperties, expectedProperties.keySet())) - .isEqualTo(expectedProperties); - assertThat(givenProperties.get(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED)).isNull(); - } - - @Test - void testGetObjectDoesNotSetChunkEncodingIfNotConfigured() { - S3Client syncClient = getS3ClientBuilder() - .serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(true).build()) - .build(); - GetObjectRequest.Builder requestBuilder = - GetObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); - assertThatThrownBy(() -> syncClient.getObject(requestBuilder.build())) - .hasMessageContaining("boom"); - - AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() - .build(); - Map, Object> expectedProperties = signerProperties(expectedValues); - Map, Object> givenProperties = signerProperties(authSchemeOption()); - assertThat(selectSignerProperties(givenProperties, expectedProperties.keySet())) - .isEqualTo(expectedProperties); - assertThat(givenProperties.get(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED)).isNull(); - } - - @Test - void testGetObjectDoesNotSetChunkEncodingIfConfiguredAsEnabled() { - S3Client syncClient = getS3ClientBuilder() - .serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(true).build()) - .build(); - GetObjectRequest.Builder requestBuilder = - GetObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); - assertThatThrownBy(() -> syncClient.getObject(requestBuilder.build())) - .hasMessageContaining("boom"); - - AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() - .build(); - Map, Object> expectedProperties = signerProperties(expectedValues); - Map, Object> givenProperties = signerProperties(authSchemeOption()); - assertThat(selectSignerProperties(givenProperties, expectedProperties.keySet())) - .isEqualTo(expectedProperties); - assertThat(givenProperties.get(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED)).isNull(); - } - - @Test - void testGetObjectDoesNotSetChunkEncodingIfConfiguredAsDisabled() { - S3Client syncClient = getS3ClientBuilder() - .serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(false).build()) - .build(); - GetObjectRequest.Builder requestBuilder = - GetObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); - assertThatThrownBy(() -> syncClient.getObject(requestBuilder.build())) - .hasMessageContaining("boom"); - - AuthSchemeOption expectedValues = defaultExpectedAuthSchemeOptionBuilder() - .build(); - Map, Object> expectedProperties = signerProperties(expectedValues); - Map, Object> givenProperties = signerProperties(authSchemeOption()); - assertThat(selectSignerProperties(givenProperties, expectedProperties.keySet())) - .isEqualTo(expectedProperties); - assertThat(givenProperties.get(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED)).isNull(); - } - - private AuthSchemeOption authSchemeOption() { - return capturingInterceptor.authSchemeOption(); - } - - AuthSchemeOption.Builder defaultExpectedAuthSchemeOptionBuilder() { - return AuthSchemeOption.builder() - .schemeId(AwsV4AuthScheme.SCHEME_ID) - // The following properties are always set - .putSignerProperty(AwsV4FamilyHttpSigner.NORMALIZE_PATH, false) - .putSignerProperty(AwsV4FamilyHttpSigner.DOUBLE_URL_ENCODE, false) - .putSignerProperty(AwsV4FamilyHttpSigner.PAYLOAD_SIGNING_ENABLED, false); - } - - Map, Object> signerProperties(AuthSchemeOption option) { - return SignerPropertiesBuilder.from(option).build(); - } - - Map, Object> selectSignerProperties( - Map, Object> signerProperties, - Collection> keys) { - Map, Object> result = new HashMap<>(); - for (SignerProperty key : keys) { - if (signerProperties.containsKey(key)) { - result.put(key, signerProperties.get(key)); - } - } - return result; - } - - S3ClientBuilder getS3ClientBuilder() { - return S3Client.builder() - .region(Region.US_EAST_1) - .overrideConfiguration(c -> c.addExecutionInterceptor(capturingInterceptor)) - .credentialsProvider(CREDENTIALS_PROVIDER); - } - - - static class SignerPropertiesBuilder { - Map, Object> map = new HashMap<>(); - - static SignerPropertiesBuilder from(AuthSchemeOption option) { - SignerPropertiesBuilder builder = new SignerPropertiesBuilder(); - option.forEachSignerProperty(builder::putSignerProperty); - return builder; - } - - public void putSignerProperty(SignerProperty key, T value) { - map.put(key, value); - } - - public Map, Object> build() { - return map; - } - } - - static class CapturingInterceptor implements ExecutionInterceptor { - private static final RuntimeException RTE = new RuntimeException("boom"); - private SelectedAuthScheme selectedAuthScheme; - - @Override - public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { - selectedAuthScheme = executionAttributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); - throw RTE; - } - - public AuthSchemeOption authSchemeOption() { - if (selectedAuthScheme == null) { - return null; - } - return selectedAuthScheme.authSchemeOption(); - } - } -} diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/plugins/S3SignerPropertiesPluginsTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/plugins/S3SignerPropertiesPluginsTest.java new file mode 100644 index 000000000000..89ef600522da --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/plugins/S3SignerPropertiesPluginsTest.java @@ -0,0 +1,418 @@ +/* + * 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.plugins; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +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.core.SelectedAuthScheme; +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.sync.RequestBody; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +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.SignerProperty; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3ClientBuilder; +import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.UploadPartRequest; +import software.amazon.awssdk.utils.Validate; + +class S3SignerPropertiesPluginsTest { + private static final String PUT_BODY = "put body"; + private static String DEFAULT_BUCKET = "bucket"; + private static String DEFAULT_KEY = "key"; + private static final AwsCredentialsProvider CREDENTIALS_PROVIDER = + StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")); + + @ParameterizedTest + @MethodSource("testCases") + public void validateTestCase(TestCase testCase) { + CapturingInterceptor capturingInterceptor = new CapturingInterceptor(); + S3ClientBuilder clientBuilder = getS3ClientBuilder(capturingInterceptor); + testCase.configureClient().accept(clientBuilder); + S3Client client = clientBuilder.build(); + assertThatThrownBy(() -> testCase.useClient().accept(client)) + .hasMessageContaining("boom") + .as(testCase.name()); + + AuthSchemeOption expectedValues = testCase.expectedSignerProperties(); + Map, Object> expectedProperties = signerProperties(expectedValues); + + assertThat(selectSignerProperties(signerProperties(capturingInterceptor.authSchemeOption()), expectedProperties.keySet())) + .isEqualTo(expectedProperties) + .as(testCase.name()); + assertThat(selectSignerProperties(signerProperties(capturingInterceptor.authSchemeOption()), testCase.unsetProperties())) + .isEqualTo(Collections.emptyMap()) + .as(testCase.name()); + } + + static Map, Object> signerProperties(AuthSchemeOption option) { + return SignerPropertiesBuilder.from(option).build(); + } + + static Map, Object> selectSignerProperties( + Map, Object> signerProperties, + Collection> keys + ) { + Map, Object> result = new HashMap<>(); + for (SignerProperty key : keys) { + if (signerProperties.containsKey(key)) { + result.put(key, signerProperties.get(key)); + } + } + return result; + } + + public static Collection testCases() { + return Arrays.asList( + // S3DisableChunkEncodingIfConfiguredPlugin, honors + // S#Configuration.enableChunkEncoding(false) + testUploadPartEnablesChunkEncodingByDefault(), + testUploadPartDisablesChunkEncodingWhenConfigured(), + testPutObjectEnablesChunkEncodingByDefault(), + testPutObjectDisablesChunkEncodingWhenConfigured(), + testGetObjectDoesNotSetChunkEncoding(), + testGetObjectDoesNotSetChunkEncodingIfNotConfigured(), + testGetObjectDoesNotSetChunkEncodingIfConfigured(), + // S3OverrideAuthSchemePropertiesPlugin.enablePayloadSigningPlugin() + testUploadPartDisablesPayloadSigningByDefault(), + testUploadPartEnablesPayloadSigningUsingPlugin(), + // S3OverrideAuthSchemePropertiesPlugin.disableChunkEncoding() + testUploadPartDisablesChunkEncodingUsingPlugin(), + testPutObjectDisablesChunkEncodingUsingPlugin() , + testGetObjectDoesNotDisablesChunkEncodingUsingPlugin() + ); + } + + // S3OverrideAuthSchemePropertiesPlugin.enablePayloadSigningPlugin() + private static TestCase testUploadPartDisablesPayloadSigningByDefault() { + return forUploadPart("UploadPartDisablesPayloadSigningByDefault") + .expectedSignerProperties(defaultExpectedAuthSchemeOptionBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.PAYLOAD_SIGNING_ENABLED, false) + .build()) + .build(); + } + + private static TestCase testUploadPartEnablesPayloadSigningUsingPlugin() { + return forUploadPart("UploadPartEnablesPayloadSigningUsingPlugin") + .configureClient(c -> c.addPlugin(S3OverrideAuthSchemePropertiesPlugin.enablePayloadSigningPlugin())) + .expectedSignerProperties(defaultExpectedAuthSchemeOptionBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.PAYLOAD_SIGNING_ENABLED, true) + .build()) + .build(); + + } + + + // S3OverrideAuthSchemePropertiesPlugin.disableChunkEncoding() + private static TestCase testUploadPartDisablesChunkEncodingUsingPlugin() { + return forUploadPart("UploadPartDisablesChunkEncodingUsingPlugin") + .configureClient(c -> c.addPlugin(S3OverrideAuthSchemePropertiesPlugin.disableChunkEncodingPlugin())) + .expectedSignerProperties(defaultExpectedAuthSchemeOptionBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, false) + .build()) + .build(); + + } + + static TestCase testPutObjectDisablesChunkEncodingUsingPlugin() { + return forPutObject("PutObjectDisablesChunkEncodingUsingPlugin") + .configureClient(c -> c.addPlugin(S3OverrideAuthSchemePropertiesPlugin.disableChunkEncodingPlugin())) + .expectedSignerProperties(defaultExpectedAuthSchemeOptionBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, false) + .build()) + .build(); + } + + static TestCase testGetObjectDoesNotDisablesChunkEncodingUsingPlugin() { + return forGetObject("GetObjectDoesNotDisablesChunkEncodingUsingPlugin") + .configureClient(c -> c.addPlugin(S3OverrideAuthSchemePropertiesPlugin.disableChunkEncodingPlugin())) + .expectedSignerProperties(defaultExpectedAuthSchemeOptionBuilder() + .build()) + .addUnsetProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED) + .build(); + } + + // S3DisableChunkEncodingIfConfiguredPlugin + static TestCase testUploadPartEnablesChunkEncodingByDefault() { + return forUploadPart("UploadPartEnablesChunkEncodingByDefault") + .expectedSignerProperties(defaultExpectedAuthSchemeOptionBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, true) + .build()) + .build(); + } + + static TestCase testUploadPartDisablesChunkEncodingWhenConfigured() { + return forUploadPart("UploadPartDisablesChunkEncodingWhenConfigured") + .configureClient(c -> c.serviceConfiguration(S3Configuration.builder() + .chunkedEncodingEnabled(false) + .build())) + .expectedSignerProperties(defaultExpectedAuthSchemeOptionBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, false) + .build()) + .build(); + + } + + static TestCase testPutObjectEnablesChunkEncodingByDefault() { + return forPutObject("PutObjectEnablesChunkEncodingByDefault") + .expectedSignerProperties(defaultExpectedAuthSchemeOptionBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, true) + .build()) + .build(); + } + + static TestCase testPutObjectDisablesChunkEncodingWhenConfigured() { + return forPutObject("PutObjectDisablesChunkEncodingWhenConfigured") + .configureClient(c -> c.serviceConfiguration(S3Configuration.builder() + .chunkedEncodingEnabled(false) + .build())) + .expectedSignerProperties(defaultExpectedAuthSchemeOptionBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, false) + .build()) + .build(); + } + + static TestCase testGetObjectDoesNotSetChunkEncoding() { + return forGetObject("GetObjectDoesNotSetChunkEncoding") + .expectedSignerProperties(defaultExpectedAuthSchemeOptionBuilder().build()) + .build(); + } + + static TestCase testGetObjectDoesNotSetChunkEncodingIfNotConfigured() { + return forGetObject("GetObjectDoesNotSetChunkEncodingIfNotConfigured") + .configureClient(c -> c.serviceConfiguration(S3Configuration.builder() + .chunkedEncodingEnabled(true) + .build())) + .expectedSignerProperties(defaultExpectedAuthSchemeOptionBuilder().build()) + .build(); + } + + static TestCase testGetObjectDoesNotSetChunkEncodingIfConfigured() { + return forGetObject("GetObjectDoesNotSetChunkEncodingIfConfigured") + .configureClient(c -> c.serviceConfiguration(S3Configuration.builder() + .chunkedEncodingEnabled(false) + .build())) + .expectedSignerProperties(defaultExpectedAuthSchemeOptionBuilder().build()) + .build(); + } + + // End of tests, utils next + static TestCaseBuilder forUploadPart(String name) { + return testCaseBuilder(name) + .useClient(c -> { + UploadPartRequest.Builder requestBuilder = + UploadPartRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY).partNumber(0).uploadId("test"); + c.uploadPart(requestBuilder.build(), RequestBody.fromString(PUT_BODY)); + }); + } + + static TestCaseBuilder forPutObject(String name) { + return testCaseBuilder(name) + .useClient(c -> { + PutObjectRequest.Builder requestBuilder = + PutObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); + c.putObject(requestBuilder.build(), RequestBody.fromString(PUT_BODY)); + }); + } + + static TestCaseBuilder forGetObject(String name) { + return testCaseBuilder(name) + .useClient(c -> { + GetObjectRequest.Builder requestBuilder = + GetObjectRequest.builder().bucket(DEFAULT_BUCKET).key(DEFAULT_KEY); + c.getObject(requestBuilder.build()); + }); + } + + public static TestCaseBuilder testCaseBuilder(String name) { + return new TestCaseBuilder(name); + } + + static AuthSchemeOption.Builder defaultExpectedAuthSchemeOptionBuilder() { + return AuthSchemeOption.builder() + .schemeId(AwsV4AuthScheme.SCHEME_ID) + .putSignerProperty(AwsV4FamilyHttpSigner.NORMALIZE_PATH, false) + .putSignerProperty(AwsV4FamilyHttpSigner.DOUBLE_URL_ENCODE, false) + .putSignerProperty(AwsV4FamilyHttpSigner.PAYLOAD_SIGNING_ENABLED, false); + } + + static S3ClientBuilder getS3ClientBuilder(CapturingInterceptor capturingInterceptor) { + return S3Client.builder() + .region(Region.US_EAST_1) + .overrideConfiguration(c -> c.addExecutionInterceptor(capturingInterceptor)) + .credentialsProvider(CREDENTIALS_PROVIDER); + } + + public static class TestCaseBuilder { + private final String name; + private Consumer configureClient = c -> { + }; + private Consumer useClient; + private AuthSchemeOption expectedSignerProperties = defaultExpectedAuthSchemeOptionBuilder().build(); + private Set> unsetProperties = new HashSet<>(); + + public TestCaseBuilder(String name) { + this.name = name; + } + + public Consumer configureClient() { + return configureClient; + } + + public TestCaseBuilder configureClient(Consumer configureClient) { + this.configureClient = configureClient; + return this; + } + + public Consumer useClient() { + return useClient; + } + + public TestCaseBuilder useClient(Consumer useClient) { + this.useClient = useClient; + return this; + } + + public AuthSchemeOption expectedSignerProperties() { + return expectedSignerProperties; + } + + public TestCaseBuilder expectedSignerProperties(AuthSchemeOption expectedSignerProperties) { + this.expectedSignerProperties = expectedSignerProperties; + return this; + } + + public Set> unsetProperties() { + if (unsetProperties.isEmpty()) { + return Collections.emptySet(); + } + return Collections.unmodifiableSet(new HashSet<>(this.unsetProperties)); + } + + public TestCaseBuilder unsetProperties(Set> unsetProperties) { + this.unsetProperties.clear(); + this.unsetProperties.addAll(unsetProperties); + return this; + } + + public TestCaseBuilder addUnsetProperty(SignerProperty unsetProperty) { + this.unsetProperties.add(unsetProperty); + return this; + } + + public String name() { + return name; + } + + public TestCase build() { + return new TestCase(this); + } + } + + static class TestCase { + private final String name; + private final Consumer configureClient; + private final Consumer useClient; + private final AuthSchemeOption expectedSignerProperties; + private final Set> unsetProperties; + + public TestCase(TestCaseBuilder builder) { + this.name = Validate.paramNotNull(builder.name(), "name"); + this.configureClient = Validate.paramNotNull(builder.configureClient(), "configureClient"); + this.useClient = Validate.paramNotNull(builder.useClient(), "useClient"); + this.expectedSignerProperties = Validate.paramNotNull(builder.expectedSignerProperties(), "expectedSignerProperties"); + this.unsetProperties = Validate.paramNotNull(builder.unsetProperties(), "unsetProperties"); + } + + public String name() { + return name; + } + + public Consumer configureClient() { + return configureClient; + } + + public Consumer useClient() { + return useClient; + } + + public AuthSchemeOption expectedSignerProperties() { + return expectedSignerProperties; + } + + public Set> unsetProperties() { + return unsetProperties; + } + } + + static class SignerPropertiesBuilder { + Map, Object> map = new HashMap<>(); + + static SignerPropertiesBuilder from(AuthSchemeOption option) { + SignerPropertiesBuilder builder = + new SignerPropertiesBuilder(); + option.forEachSignerProperty(builder::putSignerProperty); + return builder; + } + + public void putSignerProperty(SignerProperty key, T value) { + map.put(key, value); + } + + public Map, Object> build() { + return map; + } + } + + static class CapturingInterceptor implements ExecutionInterceptor { + private static final RuntimeException RTE = new RuntimeException("boom"); + private SelectedAuthScheme selectedAuthScheme; + + @Override + public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { + selectedAuthScheme = executionAttributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + throw RTE; + } + + public AuthSchemeOption authSchemeOption() { + if (selectedAuthScheme == null) { + return null; + } + return selectedAuthScheme.authSchemeOption(); + } + } +}