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/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 90149c984fa5..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,87 +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.function.BiConsumer; -import software.amazon.awssdk.codegen.model.service.AuthType; -import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; -import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; -import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; -import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; +import java.util.function.Supplier; 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 SIGV4_UNSIGNED_BODY = - SIGV4.toBuilder() - .addProperty(SignerPropertyValueProvider.builder() - .containingClass(AwsV4HttpSigner.class) - .fieldName("PAYLOAD_SIGNING_ENABLED") - .valueEmitter((spec, utils) -> spec.addCode("$L", false)) - .build()) - .build(); - - static final AuthSchemeCodegenMetadata S3 = - SIGV4.toBuilder() - .addProperty(SignerPropertyValueProvider.builder() - .containingClass(AwsV4HttpSigner.class) - .fieldName("DOUBLE_URL_ENCODE") - .valueEmitter((spec, utils) -> spec.addCode("$L", "false")) - .build()) - .addProperty(SignerPropertyValueProvider.builder() - .containingClass(AwsV4HttpSigner.class) - .fieldName("NORMALIZE_PATH") - .valueEmitter((spec, utils) -> spec.addCode("$L", "false")) - .build()) - .addProperty(SignerPropertyValueProvider.builder() - .containingClass(AwsV4HttpSigner.class) - .fieldName("PAYLOAD_SIGNING_ENABLED") - .valueEmitter((spec, utils) -> spec.addCode("$L", false)) - .build()) - .build(); - - static final AuthSchemeCodegenMetadata S3V4 = - SIGV4.toBuilder() - .addProperty(SignerPropertyValueProvider.builder() - .containingClass(AwsV4HttpSigner.class) - .fieldName("DOUBLE_URL_ENCODE") - .valueEmitter((spec, utils) -> spec.addCode("$L", "false")) - .build()) - .addProperty(SignerPropertyValueProvider.builder() - .containingClass(AwsV4HttpSigner.class) - .fieldName("NORMALIZE_PATH") - .valueEmitter((spec, utils) -> spec.addCode("$L", "false")) - .build()) - .build(); - - static final AuthSchemeCodegenMetadata BEARER = builder() - .schemeId(BearerAuthScheme.SCHEME_ID) - .authSchemeClass(BearerAuthScheme.class) - .build(); - - static final AuthSchemeCodegenMetadata NO_AUTH = builder() - .schemeId(NoAuthAuthScheme.SCHEME_ID) - .authSchemeClass(NoAuthAuthScheme.class) - .build(); - private final String schemeId; private final List properties; private final Class authSchemeClass; @@ -122,30 +53,11 @@ public Builder toBuilder() { return new Builder(this); } - private static Builder builder() { + public static Builder builder() { return new Builder(); } - public static AuthSchemeCodegenMetadata fromAuthType(AuthType type) { - switch (type) { - case BEARER: - return BEARER; - case NONE: - return NO_AUTH; - case V4: - return SIGV4; - case V4_UNSIGNED_BODY: - return SIGV4_UNSIGNED_BODY; - case S3: - return S3; - case S3V4: - return S3V4; - default: - throw new IllegalArgumentException("Unknown auth type: " + type); - } - } - - private static class Builder { + public static class Builder { private String schemeId; private List properties = new ArrayList<>(); private Class authSchemeClass; @@ -169,6 +81,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; @@ -182,12 +100,14 @@ 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) { 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,18 +118,28 @@ public String fieldName() { return fieldName; } - public void emitValue(MethodSpec.Builder spec, AuthSchemeSpecUtils utils) { + public boolean isConstant() { + return valueSupplier != null; + } + + public Object value() { + return valueSupplier.get(); + } + + 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) { this.containingClass = containingClass; @@ -221,11 +151,19 @@ public Builder fieldName(String fieldName) { return this; } - public Builder valueEmitter(BiConsumer valueEmitter) { + public Builder valueEmitter(BiConsumer valueEmitter) { this.valueEmitter = valueEmitter; return this; } + public Builder constantValueSupplier(Supplier valueSupplier) { + this.valueSupplier = valueSupplier; + if (valueEmitter == null) { + valueEmitter = (spec, utils) -> spec.add("$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/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 4c997473960f..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 @@ -19,27 +19,17 @@ import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Set; -import java.util.TreeSet; -import java.util.stream.Collectors; -import java.util.stream.Stream; import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig; import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; -import software.amazon.awssdk.codegen.model.intermediate.OperationModel; -import software.amazon.awssdk.codegen.model.service.AuthType; import software.amazon.awssdk.codegen.utils.AuthUtils; -import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; -import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; public final class AuthSchemeSpecUtils { - private static final Set DEFAULT_AUTH_SCHEME_PARAMS = Collections.unmodifiableSet(setOf("region", "operation")); + private static final Set DEFAULT_AUTH_SCHEME_PARAMS = setOf("region", "operation"); private final IntermediateModel intermediateModel; private final boolean useSraAuth; private final Set allowedEndpointAuthSchemeParams; @@ -161,66 +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; - } - - public List serviceDefaultAuthTypes() { - List modeled = intermediateModel.getMetadata().getAuth(); - if (!modeled.isEmpty()) { - return modeled; - } - return Collections.singletonList(intermediateModel.getMetadata().getAuthType()); - } - - public Set> allServiceConcreteAuthSchemeClasses() { - Set> result = - Stream.concat(intermediateModel.getOperations() - .values() - .stream() - .map(OperationModel::getAuth) - .flatMap(List::stream), - intermediateModel.getMetadata().getAuth().stream()) - .map(AuthSchemeCodegenMetadata::fromAuthType) - .map(AuthSchemeCodegenMetadata::authSchemeClass) - .collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Class::getSimpleName)))); - - if (useEndpointBasedAuthProvider()) { - // sigv4a is not modeled but needed for the endpoints based auth-scheme cases. - result.add(AwsV4aAuthScheme.class); - } - // Make the no-auth scheme available. - result.add(NoAuthAuthScheme.class); - return result; - } - - private static Set setOf(String v1, String v2) { - Set set = new HashSet<>(); - set.add(v1); - set.add(v2); - return set; + 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 new file mode 100644 index 000000000000..02f6a682e3f4 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTypeToSigV4Default.java @@ -0,0 +1,157 @@ +/* + * 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; + +/** + * Contains maps from all sigv4 based {@link AuthType} to {@link SigV4SignerDefaults} that we can then transform for use in + * codegen. + */ +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 the list fo all known auth types to s3v4Defaults instances. + * + * @return + */ + 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() { + return sigv4Default().toBuilder() + .authType("s3v4") + .doubleUrlEncode(false) + .normalizePath(false) + .build(); + } + + + /** + * 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 f680c8307936..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 @@ -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; @@ -49,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 @@ -62,18 +65,23 @@ 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()) - .addMethod(endpointProvider()) - .build(); + 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()) + .addMethod(endpointProvider()); + + boolean applyServiceDefaults = sigV4AuthSchemeCodegenKnowledgeIndex.hasSigV4Overrides(); + if (applyServiceDefaults) { + builder.addMethod(addV4Defaults()); + } + return builder.build(); } private MethodSpec constructor() { @@ -180,12 +188,25 @@ private void addAuthSchemeSwitchSigV4Case(MethodSpec.Builder spec) { 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("$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 (sigV4AuthSchemeCodegenKnowledgeIndex.hasSigV4Overrides()) { + 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"); } @@ -198,12 +219,25 @@ 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("\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 (sigV4AuthSchemeCodegenKnowledgeIndex.hasSigV4Overrides()) { + 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"); } @@ -212,19 +246,34 @@ private void addAuthSchemeSwitchS3ExpressCase(MethodSpec.Builder spec) { 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("\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 (sigV4AuthSchemeCodegenKnowledgeIndex.hasSigV4Overrides()) { + 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) { @@ -232,6 +281,55 @@ private void addAuthSchemeSwitchDefaultCase(MethodSpec.Builder spec) { spec.addStatement("throw new $T($S + name)", IllegalArgumentException.class, "Unknown auth scheme: "); } + + 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"); + + // All the operations share the same set of auth schemes, no need to create a switch statement. + if (!sigV4AuthSchemeCodegenKnowledgeIndex.hasPerOperationSigV4Overrides()) { + AuthSchemeCodegenMetadata authType = sigV4AuthSchemeCodegenKnowledgeIndex.serviceSigV4Overrides(); + addAuthTypeProperties(spec, authType); + return spec.build(); + } + spec.beginControlFlow("switch(params.operation())"); + sigV4AuthSchemeCodegenKnowledgeIndex.forEachOperationsOverridesGroup((ops, scheme) -> { + if (!ops.isEmpty()) { + addCasesForOperations(spec, ops, scheme); + } + }); + AuthSchemeCodegenMetadata authType = sigV4AuthSchemeCodegenKnowledgeIndex.serviceSigV4Overrides(); + if (authType != null) { + addCasesForDefault(spec, authType); + } + spec.endControlFlow(); + return spec.build(); + } + + private void addCasesForOperations(MethodSpec.Builder spec, List operations, + AuthSchemeCodegenMetadata metadata) { + for (String name : operations) { + spec.addCode("case $S:\n", name); + } + addAuthTypeProperties(spec, metadata); + } + + 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() { return endpointRulesSpecUtils.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 9f9f0abcf04b..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,9 +15,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; import com.squareup.javapoet.MethodSpec; @@ -27,20 +24,20 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import javax.lang.model.element.Modifier; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; -import software.amazon.awssdk.codegen.model.service.AuthType; import software.amazon.awssdk.codegen.poet.ClassSpec; import software.amazon.awssdk.codegen.poet.PoetUtils; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; public class ModelBasedAuthSchemeProviderSpec implements ClassSpec { private final AuthSchemeSpecUtils authSchemeSpecUtils; + private final AuthSchemeCodegenKnowledgeIndex knowledgeIndex; public ModelBasedAuthSchemeProviderSpec(IntermediateModel intermediateModel) { this.authSchemeSpecUtils = new AuthSchemeSpecUtils(intermediateModel); + this.knowledgeIndex = AuthSchemeCodegenKnowledgeIndex.of(intermediateModel); } @Override @@ -93,54 +90,43 @@ private MethodSpec resolveAuthSchemeMethod() { spec.addStatement("$T options = new $T<>()", ParameterizedTypeName.get(List.class, AuthSchemeOption.class), TypeName.get(ArrayList.class)); - Map, List> operationsToAuthType = authSchemeSpecUtils.operationsToAuthType(); - - // All the operations share the same set of auth schemes, no need to create a switch statement. - if (operationsToAuthType.size() == 1) { - List types = operationsToAuthType.get(Collections.emptyList()); - for (AuthType authType : types) { + 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(); } - 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:"); + spec.addCode("default:\n"); } else { for (String name : operations) { - spec.addCode("case $S:", name); + spec.addCode("case $S\n:", 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); - 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 new file mode 100644 index 000000000000..4d6a69786ab0 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/SigV4SignerDefaults.java @@ -0,0 +1,243 @@ +/* + * 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 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; + 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; + + private 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 boolean isServiceOverrideAuthScheme() { + return service != null; + } + + 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..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) { @@ -744,7 +746,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 +775,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 +787,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/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 ae6ea5a4df3c..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,12 +73,61 @@ public void syncComposedDefaultClientBuilderClass() { validateBaseClientBuilderClassGeneration(composedClientJsonServiceModels(), "test-composed-sync-default-client-builder.java"); } + + @Test + void baseClientBuilderClass_sra() { + validateBaseClientBuilderClassGeneration(restJsonServiceModels(), "test-client-builder-class.java", true); + } + + @Test + void baseClientBuilderClassWithBearerAuth_sra() { + validateBaseClientBuilderClassGeneration(bearerAuthServiceModels(), "test-bearer-auth-client-builder-class.java", true); + } + + @Test + void baseClientBuilderClassWithNoAuthOperation_sra() { + validateBaseClientBuilderClassGeneration(operationWithNoAuth(), "test-no-auth-ops-client-builder-class.java", true); + } + + @Test + void baseClientBuilderClassWithNoAuthService_sra() { + validateBaseClientBuilderClassGeneration(serviceWithNoAuth(), "test-no-auth-service-client-builder-class.java", true); + } + + @Test + void baseClientBuilderClassWithInternalUserAgent_sra() { + validateBaseClientBuilderClassGeneration(internalConfigModels(), "test-client-builder-internal-defaults-class.java", + true); + } + + @Test + void baseQueryClientBuilderClass_sra() { + validateBaseClientBuilderClassGeneration(queryServiceModels(), "test-query-client-builder-class.java", true); + } + + @Test + void baseClientBuilderClassWithEndpointsAuthParams_sra() { + validateBaseClientBuilderClassGeneration(queryServiceModelsEndpointAuthParamsWithAllowList(), + "test-client-builder-endpoints-auth-params.java", true); + } + + @Test + 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/mini-s3-auth-scheme-default-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/mini-s3-auth-scheme-default-provider.java index dd7224fd84c7..07447fafbad4 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/mini-s3-auth-scheme-default-provider.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/mini-s3-auth-scheme-default-provider.java @@ -40,11 +40,11 @@ public static DefaultMiniS3AuthSchemeProvider create() { public List resolveAuthScheme(MiniS3AuthSchemeParams params) { List options = new ArrayList<>(); options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") - .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "mini-s3-service") - .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()) - .putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, false) - .putSignerProperty(AwsV4HttpSigner.NORMALIZE_PATH, false) - .putSignerProperty(AwsV4HttpSigner.PAYLOAD_SIGNING_ENABLED, false).build()); + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "mini-s3-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()) + .putSignerProperty(AwsV4HttpSigner.PAYLOAD_SIGNING_ENABLED, false) + .putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, false) + .putSignerProperty(AwsV4HttpSigner.NORMALIZE_PATH, false).build()); return Collections.unmodifiableList(options); } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java 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 8aa44f160393..97e874a1cf11 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 589880dbef0d..ab4ed5f48035 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/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/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/S3DisableChunkEncodingAuthSchemeProvider.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingAuthSchemeProvider.java new file mode 100644 index 000000000000..c02814d5273d --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingAuthSchemeProvider.java @@ -0,0 +1,67 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.s3.internal.plugins; + +import java.util.ArrayList; +import java.util.List; +import software.amazon.awssdk.annotations.SdkInternalApi; +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.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.auth.scheme.S3AuthSchemeParams; +import software.amazon.awssdk.services.s3.auth.scheme.S3AuthSchemeProvider; + +/** + * Internal plugin that sets the signer property {@link AwsV4FamilyHttpSigner#CHUNK_ENCODING_ENABLED} to {@code false}. This + * plugin is invoked by the client builder only if {@link S3Configuration#chunkedEncodingEnabled()} is set to {@code false}. + */ +@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; + + // Disables chunk encoding but only for PutObject or UploadPart operations. + String operation = authSchemeParams.operation(); + if ("PutObject".equals(operation) || "UploadPart".equals(operation)) { + 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)) { + result.add(option.toBuilder() + .putSignerProperty(AwsV4FamilyHttpSigner.CHUNK_ENCODING_ENABLED, false) + .build()); + } + } + } + return result; + } +} 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..a37ccfa1c8d1 --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3DisableChunkEncodingIfConfiguredPlugin.java @@ -0,0 +1,82 @@ +/* + * 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.http.auth.aws.signer.AwsV4FamilyHttpSigner; +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.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 + * {@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 { + + 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; + + S3AuthSchemeProvider disablingAuthSchemeProvider = + S3DisableChunkEncodingAuthSchemeProvider.create(s3Config.authSchemeProvider()); + s3Config.authSchemeProvider(disablingAuthSchemeProvider); + } + } +} 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..ef05dc51df4e --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/plugins/S3OverrideAuthSchemePropertiesPlugin.java @@ -0,0 +1,222 @@ +/* + * 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.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; +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.S3AuthSchemeParams; +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 final Set operationConstraints; + + 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)); + } + if (builder.operationConstraints.isEmpty()) { + this.operationConstraints = Collections.emptySet(); + } else { + this.operationConstraints = Collections.unmodifiableSet(new HashSet<>(builder.operationConstraints)); + } + } + + @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) { + // 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 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)); + 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); + } + + + 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. + */ + public static SdkPlugin enablePayloadSigningPlugin() { + return builder() + .payloadSigningEnabled(true) + .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. + */ + 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<>(); + 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. + */ + 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/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..32178fd4ec76 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 @@ -39,7 +39,6 @@ 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.checksums.Algorithm; import software.amazon.awssdk.core.checksums.ChecksumSpecs; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; @@ -47,6 +46,7 @@ import software.amazon.awssdk.core.internal.util.Mimetype; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.regions.Region; +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 +69,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 +153,7 @@ 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"))); 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..47bb384e6fd4 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 @@ -207,7 +207,7 @@ private void createClientAndCallPutObject(ClientType clientType, Protocol protoc } private void createClientAndCallUploadPart(ClientType clientType, Protocol protocol, S3ExpressSessionAuth s3ExpressSessionAuth, - ChecksumAlgorithm checksumAlgorithm, WireMockRuntimeInfo wm) { + 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) { @@ -295,7 +295,7 @@ void verifyUploadPartHeaders(ClientType clientType, Protocol protocol, ChecksumA assertThat(headers.get("x-amz-content-sha256")).isNotNull(); if ((protocol == Protocol.HTTPS || clientType == ClientType.ASYNC) && - checksumAlgorithm == ChecksumAlgorithm.UNKNOWN_TO_SDK_VERSION) { + 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(); @@ -436,4 +436,4 @@ public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttr System.out.println(); } } -} +} \ No newline at end of file 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(); + } + } +}