From 0552501b5bbbacfb568f5dc04b6acc66121beab5 Mon Sep 17 00:00:00 2001 From: Steven Yuan Date: Tue, 19 Sep 2023 08:30:17 -0700 Subject: [PATCH 1/2] feat(experimentalIdentityAndAuth): add `endpointRuleSet` trait to generic client test --- .../build.gradle.kts | 1 + .../model/weather.smithy | 342 ++++++++++++++++++ 2 files changed, 343 insertions(+) diff --git a/codegen/generic-client-test-codegen/build.gradle.kts b/codegen/generic-client-test-codegen/build.gradle.kts index da69f9602b98a..8807dd1a9e7f4 100644 --- a/codegen/generic-client-test-codegen/build.gradle.kts +++ b/codegen/generic-client-test-codegen/build.gradle.kts @@ -37,6 +37,7 @@ plugins { dependencies { implementation("software.amazon.smithy:smithy-aws-protocol-tests:$smithyVersion") implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion") + implementation("software.amazon.smithy:smithy-rules-engine:$smithyVersion") implementation(project(":smithy-aws-typescript-codegen")) } diff --git a/codegen/generic-client-test-codegen/model/weather.smithy b/codegen/generic-client-test-codegen/model/weather.smithy index 904af6edeed86..ad870134704ba 100644 --- a/codegen/generic-client-test-codegen/model/weather.smithy +++ b/codegen/generic-client-test-codegen/model/weather.smithy @@ -3,6 +3,7 @@ $version: "2.0" namespace example.weather use aws.auth#sigv4 +use aws.api#service @authDefinition @trait @@ -12,6 +13,7 @@ structure customAuth {} @protocolDefinition structure fakeProtocol {} +@service(sdkId: "weather") @fakeProtocol @httpApiKeyAuth(name: "X-Api-Key", in: "header") @httpBearerAuth @@ -86,3 +88,343 @@ operation SameAsService { service: String } } + +apply Weather @smithy.rules#endpointRuleSet( { + "version": "1.0", + "parameters": { + "Region": { + "builtIn": "AWS::Region", + "required": false, + "documentation": "The AWS region used to dispatch the request.", + "type": "String" + }, + "UseDualStack": { + "builtIn": "AWS::UseDualStack", + "required": true, + "default": false, + "documentation": "When true, use the dual-stack endpoint. If the configured endpoint does not support dual-stack, dispatching the request MAY return an error.", + "type": "Boolean" + }, + "UseFIPS": { + "builtIn": "AWS::UseFIPS", + "required": true, + "default": false, + "documentation": "When true, send this request to the FIPS-compliant regional endpoint. If the configured endpoint does not have a FIPS compliant endpoint, dispatching the request will return an error.", + "type": "Boolean" + }, + "Endpoint": { + "builtIn": "SDK::Endpoint", + "required": false, + "documentation": "Override the endpoint used to send this request", + "type": "String" + } + }, + "rules": [ + { + "conditions": [ + { + "fn": "isSet", + "argv": [ + { + "ref": "Endpoint" + } + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseFIPS" + }, + true + ] + } + ], + "error": "Invalid Configuration: FIPS and custom endpoint are not supported", + "type": "error" + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseDualStack" + }, + true + ] + } + ], + "error": "Invalid Configuration: Dualstack and custom endpoint are not supported", + "type": "error" + }, + { + "conditions": [], + "endpoint": { + "url": { + "ref": "Endpoint" + }, + "properties": {}, + "headers": {} + }, + "type": "endpoint" + } + ] + }, + { + "conditions": [ + { + "fn": "isSet", + "argv": [ + { + "ref": "Region" + } + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "aws.partition", + "argv": [ + { + "ref": "Region" + } + ], + "assign": "PartitionResult" + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseFIPS" + }, + true + ] + }, + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseDualStack" + }, + true + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsFIPS" + ] + } + ] + }, + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsDualStack" + ] + } + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [], + "endpoint": { + "url": "https://weather-fips.{Region}.{PartitionResult#dualStackDnsSuffix}", + "properties": {}, + "headers": {} + }, + "type": "endpoint" + } + ] + }, + { + "conditions": [], + "error": "FIPS and DualStack are enabled, but this partition does not support one or both", + "type": "error" + } + ] + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseFIPS" + }, + true + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsFIPS" + ] + } + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "stringEquals", + "argv": [ + "aws-us-gov", + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "name" + ] + } + ] + } + ], + "endpoint": { + "url": "https://weather.{Region}.amazonaws.com", + "properties": {}, + "headers": {} + }, + "type": "endpoint" + }, + { + "conditions": [], + "endpoint": { + "url": "https://weather-fips.{Region}.{PartitionResult#dnsSuffix}", + "properties": {}, + "headers": {} + }, + "type": "endpoint" + } + ] + }, + { + "conditions": [], + "error": "FIPS is enabled but this partition does not support FIPS", + "type": "error" + } + ] + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseDualStack" + }, + true + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsDualStack" + ] + } + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [], + "endpoint": { + "url": "https://weather.{Region}.{PartitionResult#dualStackDnsSuffix}", + "properties": {}, + "headers": {} + }, + "type": "endpoint" + } + ] + }, + { + "conditions": [], + "error": "DualStack is enabled but this partition does not support DualStack", + "type": "error" + } + ] + }, + { + "conditions": [], + "endpoint": { + "url": "https://weather.{Region}.{PartitionResult#dnsSuffix}", + "properties": {}, + "headers": {} + }, + "type": "endpoint" + } + ] + } + ] + }, + { + "conditions": [], + "error": "Invalid Configuration: Missing Region", + "type": "error" + } + ] +}) From 129fa2e505214f3e5a24c221cb7f3da8781710c3 Mon Sep 17 00:00:00 2001 From: Steven Yuan Date: Tue, 19 Sep 2023 08:31:13 -0700 Subject: [PATCH 2/2] feat(experimentalIdentityAndAuth): codegen `@endpointRuleSet` `HttpAuthSchemeProvider`s For S3 and EventBridge, use `HttpAuthSchemeProvider`s based on `@smithy.rules#endpointRuleSet`, and also add in a partial `@aws.auth#sigv4a` `HttpAuthScheme` registration. --- ...EndpointRuleSetHttpAuthSchemeProvider.java | 260 ++++++++++++++++++ ....codegen.integration.TypeScriptIntegration | 1 + 2 files changed, 261 insertions(+) create mode 100644 codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/auth/http/integration/AwsCustomizeEndpointRuleSetHttpAuthSchemeProvider.java diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/auth/http/integration/AwsCustomizeEndpointRuleSetHttpAuthSchemeProvider.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/auth/http/integration/AwsCustomizeEndpointRuleSetHttpAuthSchemeProvider.java new file mode 100644 index 0000000000000..c1cedfab39030 --- /dev/null +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/auth/http/integration/AwsCustomizeEndpointRuleSetHttpAuthSchemeProvider.java @@ -0,0 +1,260 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.aws.typescript.codegen.auth.http.integration; + +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.codegen.core.SymbolProvider; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; +import software.amazon.smithy.typescript.codegen.CodegenUtils; +import software.amazon.smithy.typescript.codegen.TypeScriptCodegenContext; +import software.amazon.smithy.typescript.codegen.TypeScriptDependency; +import software.amazon.smithy.typescript.codegen.TypeScriptSettings; +import software.amazon.smithy.typescript.codegen.TypeScriptWriter; +import software.amazon.smithy.typescript.codegen.auth.AuthUtils; +import software.amazon.smithy.typescript.codegen.auth.http.HttpAuthScheme; +import software.amazon.smithy.typescript.codegen.auth.http.SupportedHttpAuthSchemesIndex; +import software.amazon.smithy.typescript.codegen.auth.http.integration.AddSigV4AuthPlugin; +import software.amazon.smithy.typescript.codegen.auth.http.integration.HttpAuthTypeScriptIntegration; +import software.amazon.smithy.typescript.codegen.endpointsV2.EndpointsV2Generator; +import software.amazon.smithy.typescript.codegen.sections.SmithyContextCodeSection; +import software.amazon.smithy.utils.CodeInterceptor; +import software.amazon.smithy.utils.CodeSection; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * Customize default {@code httpAuthSchemeProvider} and {@code httpAuthSchemeParametersProvider} for AWS SDKs. + * + * This code generates `HttpAuthSchemeProvider` interfaces based on {@code @smithy.rules#endpointRuleSet} for + * identity and auth purposes only. + * + * This is the experimental behavior for `experimentalIdentityAndAuth`. + */ +@SmithyInternalApi +public final class AwsCustomizeEndpointRuleSetHttpAuthSchemeProvider implements HttpAuthTypeScriptIntegration { + private static final Set ENDPOINT_RULESET_HTTP_AUTH_SCHEME_SERVICES = Set.of( + ShapeId.from("com.amazonaws.s3#AmazonS3"), + ShapeId.from("com.amazonaws.eventbridge#AWSEvents")); + + /** + * Integration should only be used if `experimentalIdentityAndAuth` flag is true. + */ + @Override + public boolean matchesSettings(TypeScriptSettings settings) { + return settings.getExperimentalIdentityAndAuth() + && ENDPOINT_RULESET_HTTP_AUTH_SCHEME_SERVICES.contains(settings.getService()); + } + + @Override + public List runAfter() { + return List.of(AddSigV4AuthPlugin.class.getCanonicalName()); + } + + @Override + public Optional getDefaultHttpAuthSchemeProvider() { + return Optional.of(Symbol.builder() + .name("defaultEndpointRuleSetHttpAuthSchemeProvider") + .namespace(AuthUtils.AUTH_HTTP_PROVIDER_DEPENDENCY.getPackageName(), "/") + .build()); + } + + @Override + public Optional getDefaultHttpAuthSchemeParametersProvider() { + return Optional.of(Symbol.builder() + .name("defaultEndpointRuleSetHttpAuthSchemeParametersProvider") + .namespace(AuthUtils.AUTH_HTTP_PROVIDER_DEPENDENCY.getPackageName(), "/") + .build()); + } + + @Override + public void customizeSupportedHttpAuthSchemes(SupportedHttpAuthSchemesIndex supportedHttpAuthSchemesIndex) { + // TODO(experimentalIdentityAndAuth): should be removed when @aws.auth#sigv4a is supported + ShapeId sigv4a = ShapeId.from("aws.auth#sigv4a"); + if (supportedHttpAuthSchemesIndex.getHttpAuthScheme(sigv4a) == null) { + ShapeId sigv4 = ShapeId.from("aws.auth#sigv4"); + HttpAuthScheme sigv4Scheme = supportedHttpAuthSchemesIndex.getHttpAuthScheme(sigv4); + supportedHttpAuthSchemesIndex.putHttpAuthScheme(sigv4a, sigv4Scheme.toBuilder() + .schemeId(sigv4a) + .traitId(sigv4) + .build()); + } + } + + @Override + public List> interceptors( + TypeScriptCodegenContext codegenContext + ) { + return List.of(CodeInterceptor.appender(SmithyContextCodeSection.class, (w, s) -> { + if (s.getService().hasTrait(EndpointRuleSetTrait.ID)) { + w.openBlock("endpointRuleSet: {", "},", () -> { + w.write("getEndpointParameterInstructions: $T.getEndpointParameterInstructions,", + codegenContext.symbolProvider().toSymbol(s.getOperation())); + }); + } + })); + } + + @Override + public void customize(TypeScriptCodegenContext codegenContext) { + if (!codegenContext.settings().generateClient()) { + return; + } + + codegenContext.writerDelegator().useFileWriter(AuthUtils.HTTP_AUTH_SCHEME_PROVIDER_PATH, w -> { + Model model = codegenContext.model(); + ServiceShape serviceShape = codegenContext.settings().getService(model); + SymbolProvider symbolProvider = codegenContext.symbolProvider(); + Symbol serviceSymbol = symbolProvider.toSymbol(serviceShape); + String service = CodegenUtils.getServiceName(codegenContext.settings(), model, symbolProvider); + + generateEndpointRuleSetHttpAuthSchemeParametersInterface(w, service); + generateEndpointRuleSetHttpAuthSchemeParametersProviderInterface(w, service, serviceSymbol); + generateDefaultEndpointRuleSetHttpAuthSchemeParametersProviderFunction( + w, symbolProvider, service, serviceShape, model); + generateEndpointRuleSetHttpAuthSchemeProviderInterface(w, service); + generateDefaultEndpointRuleSetHttpAuthSchemeProviderFunction(w, service); + }); + } + + /* + import { EndpointParameters } from "../endpoint/EndpointParameters.ts"; + + // ... + + export interface WeatherEndpointRuleSetHttpAuthSchemeParameters + extends EndpointParameters, WeatherHttpAuthSchemeParameters {} + */ + private void generateEndpointRuleSetHttpAuthSchemeParametersInterface( + TypeScriptWriter w, + String service + ) { + w.addImport("EndpointParameters", null, EndpointsV2Generator.ENDPOINT_PARAMETERS_DEPENDENCY); + w.write(""" + /** + * @internal + */ + export interface $LEndpointRuleSetHttpAuthSchemeParameters extends \ + EndpointParameters, $LHttpAuthSchemeParameters {} + """, + service, service); + } + + /* + import { HttpAuthSchemeParametersProvider } from "@smithy/types"; + + import { WeatherClientResolvedConfig } from "../WeatherClient"; + import { EndpointParameters } from "../endpoint/EndpointParameters.ts"; + + // ... + + export interface WeatherEndpointRuleSetHttpAuthSchemeParametersProvider extends + HttpAuthSchemeParametersProvider {} + */ + private void generateEndpointRuleSetHttpAuthSchemeParametersProviderInterface( + TypeScriptWriter w, + String service, + Symbol serviceSymbol + ) { + w.addRelativeImport(serviceSymbol.getName() + "ResolvedConfig", null, + Paths.get(".", serviceSymbol.getNamespace())); + w.addDependency(TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH); + w.addImport("HttpAuthSchemeParametersProvider", null, TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH); + w.write(""" + /** + * @internal + */ + export interface $LEndpointRuleSetHttpAuthSchemeParametersProvider extends \ + HttpAuthSchemeParametersProvider<$LResolvedConfig, $LEndpointRuleSetHttpAuthSchemeParameters> {} + """, + service, serviceSymbol.getName(), service); + } + + /* + import { createEndpointRuleSetHttpAuthSchemeParametersProvider } from "@smithy/util-identity-and-auth"; + + // ... + + export const defaultEndpointRuleSetHttpAuthSchemeParametersProvider: WeatherEndpointRuleSetHttpAuthSchemeParametersProvider = + createEndpointRuleSetHttpAuthSchemeParametersProvider(defaultWeatherHttpAuthSchemeParametersProvider); + */ + private void generateDefaultEndpointRuleSetHttpAuthSchemeParametersProviderFunction( + TypeScriptWriter w, + SymbolProvider symbolProvider, + String service, + ServiceShape serviceShape, + Model model + ) { + w.addDependency(TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH); + w.addImport("createEndpointRuleSetHttpAuthSchemeParametersProvider", null, + TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH); + w.write(""" + /** + * @internal + */ + export const defaultEndpointRuleSetHttpAuthSchemeParametersProvider: \ + $LEndpointRuleSetHttpAuthSchemeParametersProvider = createEndpointRuleSetHttpAuthSchemeParametersProvider(\ + default$LHttpAuthSchemeParametersProvider); + """, + service, service); + } + + /* + import { EndpointParameters } from "../endpoint/EndpointParameters.ts"; + + // ... + + export interface WeatherEndpointRuleSetHttpAuthSchemeProvider extends + HttpAuthSchemeProvider {} + */ + private void generateEndpointRuleSetHttpAuthSchemeProviderInterface( + TypeScriptWriter w, + String service + ) { + w.addDependency(TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH); + w.addImport("HttpAuthSchemeProvider", null, TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH); + w.write(""" + /** + * @internal + */ + export interface $LEndpointRuleSetHttpAuthSchemeProvider extends \ + HttpAuthSchemeProvider<$LEndpointRuleSetHttpAuthSchemeParameters> {} + """, + service, service); + } + + /* + import { createEndpointRuleSetHttpAuthSchemeProvider } from "@smithy/util-identity-and-auth"; + import { defaultEndpointResolver } from "../endpoint/endpointResolver.ts"; + + // ... + + export const defaultEndpointRuleSetHttpAuthSchemeProvider: WeatherEndpointRuleSetHttpAuthSchemeProvider = + createEndpointRuleSetHttpAuthSchemeProvider(defaultEndpointResolver, defaultWeatherHttpAuthSchemeProvider); + */ + private void generateDefaultEndpointRuleSetHttpAuthSchemeProviderFunction( + TypeScriptWriter w, + String service + ) { + w.addDependency(TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH); + w.addImport("createEndpointRuleSetHttpAuthSchemeProvider", null, + TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH); + w.addImport("defaultEndpointResolver", null, EndpointsV2Generator.ENDPOINT_RESOLVER_DEPENDENCY); + w.write(""" + /** + * @internal + */ + export const defaultEndpointRuleSetHttpAuthSchemeProvider: $LEndpointRuleSetHttpAuthSchemeProvider = \ + createEndpointRuleSetHttpAuthSchemeProvider(defaultEndpointResolver, default$LHttpAuthSchemeProvider); + """, + service, service); + } +} diff --git a/codegen/smithy-aws-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration b/codegen/smithy-aws-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration index 5ab4b6c37bc02..6edb4c05c4f6d 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration +++ b/codegen/smithy-aws-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration @@ -26,3 +26,4 @@ software.amazon.smithy.aws.typescript.codegen.AddHttpChecksumDependency software.amazon.smithy.aws.typescript.codegen.AddEventBridgePlugin software.amazon.smithy.aws.typescript.codegen.auth.http.integration.AwsCustomizeHttpBearerTokenAuthPlugin software.amazon.smithy.aws.typescript.codegen.auth.http.integration.AwsCustomizeSigv4AuthPlugin +software.amazon.smithy.aws.typescript.codegen.auth.http.integration.AwsCustomizeEndpointRuleSetHttpAuthSchemeProvider