1 /*
2  * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.smithy.aws.go.codegen;
17 
18 import java.util.List;
19 import java.util.Map;
20 import software.amazon.smithy.aws.traits.auth.SigV4Trait;
21 import software.amazon.smithy.codegen.core.Symbol;
22 import software.amazon.smithy.codegen.core.SymbolProvider;
23 import software.amazon.smithy.go.codegen.GoDelegator;
24 import software.amazon.smithy.go.codegen.GoSettings;
25 import software.amazon.smithy.go.codegen.GoWriter;
26 import software.amazon.smithy.go.codegen.SmithyGoDependency;
27 import software.amazon.smithy.go.codegen.SymbolUtils;
28 import software.amazon.smithy.go.codegen.integration.ConfigField;
29 import software.amazon.smithy.go.codegen.integration.ConfigFieldResolver;
30 import software.amazon.smithy.go.codegen.integration.GoIntegration;
31 import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin;
32 import software.amazon.smithy.model.Model;
33 import software.amazon.smithy.model.knowledge.ServiceIndex;
34 import software.amazon.smithy.model.shapes.OperationShape;
35 import software.amazon.smithy.model.shapes.ServiceShape;
36 import software.amazon.smithy.model.shapes.ShapeId;
37 import software.amazon.smithy.model.traits.OptionalAuthTrait;
38 import software.amazon.smithy.model.traits.Trait;
39 import software.amazon.smithy.utils.ListUtils;
40 
41 /**
42  * Generates Client Configuration, Middleware, and Config Resolvers for AWS Signature Version 4 support.
43  */
44 public final class AwsSignatureVersion4 implements GoIntegration {
45     public static final String REGISTER_MIDDLEWARE_FUNCTION = "addHTTPSignerV4Middleware";
46     public static final String SIGNER_INTERFACE_NAME = "HTTPSignerV4";
47     public static final String SIGNER_CONFIG_FIELD_NAME = SIGNER_INTERFACE_NAME;
48     public static final String NEW_SIGNER_FUNC_NAME = "newDefaultV4Signer";
49     public static final String SIGNER_RESOLVER = "resolve" + SIGNER_CONFIG_FIELD_NAME;
50 
51     private static final List<String> DISABLE_URI_PATH_ESCAPE = ListUtils.of("com.amazonaws.s3#AmazonS3");
52 
53     @Override
getOrder()54     public byte getOrder() {
55         return -48;
56     }
57 
58     @Override
writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator goDelegator )59     public void writeAdditionalFiles(
60             GoSettings settings,
61             Model model,
62             SymbolProvider symbolProvider,
63             GoDelegator goDelegator
64     ) {
65         ServiceShape serviceShape = settings.getService(model);
66         if (isSupportedAuthentication(model, serviceShape)) {
67             goDelegator.useShapeWriter(serviceShape, writer -> {
68                 writeMiddlewareRegister(model, writer, serviceShape);
69                 writerSignerInterface(writer);
70                 writerConfigFieldResolver(writer, serviceShape);
71                 writeNewV4SignerFunc(writer, serviceShape);
72             });
73         }
74     }
75 
writerSignerInterface(GoWriter writer)76     private void writerSignerInterface(GoWriter writer) {
77         writer.openBlock("type $L interface {", "}", SIGNER_INTERFACE_NAME, () -> {
78             writer.addUseImports(SmithyGoDependency.CONTEXT);
79             writer.addUseImports(AwsGoDependency.AWS_CORE);
80             writer.addUseImports(AwsGoDependency.AWS_SIGNER_V4);
81             writer.addUseImports(SmithyGoDependency.NET_HTTP);
82             writer.addUseImports(SmithyGoDependency.TIME);
83             writer.write("SignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, "
84                     + "payloadHash string, service string, region string, signingTime time.Time, "
85                     + "optFns ...func(*v4.SignerOptions)) error");
86         });
87     }
88 
writerConfigFieldResolver(GoWriter writer, ServiceShape serviceShape)89     private void writerConfigFieldResolver(GoWriter writer, ServiceShape serviceShape) {
90         writer.openBlock("func $L(o *Options) {", "}", SIGNER_RESOLVER, () -> {
91             writer.openBlock("if o.$L != nil {", "}", SIGNER_CONFIG_FIELD_NAME, () -> writer.write("return"));
92             writer.write("o.$L = $L(*o)", SIGNER_CONFIG_FIELD_NAME, NEW_SIGNER_FUNC_NAME);
93         });
94         writer.write("");
95     }
96 
writeNewV4SignerFunc(GoWriter writer, ServiceShape serviceShape)97     private void writeNewV4SignerFunc(GoWriter writer, ServiceShape serviceShape) {
98         Symbol signerSymbol = SymbolUtils.createValueSymbolBuilder("Signer",
99                 AwsGoDependency.AWS_SIGNER_V4).build();
100         Symbol newSignerSymbol = SymbolUtils.createValueSymbolBuilder("NewSigner",
101                 AwsGoDependency.AWS_SIGNER_V4).build();
102         Symbol signerOptionsSymbol = SymbolUtils.createPointableSymbolBuilder("SignerOptions",
103                 AwsGoDependency.AWS_SIGNER_V4).build();
104 
105         writer.openBlock("func $L(o Options) *$T {", "}", NEW_SIGNER_FUNC_NAME, signerSymbol, () -> {
106             writer.openBlock("return $T(func(so $P) {", "})", newSignerSymbol, signerOptionsSymbol, () -> {
107                 writer.write("so.Logger = o.$L", AddAwsConfigFields.LOGGER_CONFIG_NAME);
108                 writer.write("so.LogSigning = o.$L.IsSigning()", AddAwsConfigFields.LOG_MODE_CONFIG_NAME);
109                 if (DISABLE_URI_PATH_ESCAPE.contains(serviceShape.getId().toString())) {
110                     writer.write("so.DisableURIPathEscaping = true");
111                 }
112             });
113         });
114     }
115 
116     @Override
getClientPlugins()117     public List<RuntimeClientPlugin> getClientPlugins() {
118         return ListUtils.of(RuntimeClientPlugin.builder()
119                 .servicePredicate(AwsSignatureVersion4::isSupportedAuthentication)
120                 .addConfigField(ConfigField.builder()
121                         .name(SIGNER_INTERFACE_NAME)
122                         .type(SymbolUtils.createValueSymbolBuilder(SIGNER_INTERFACE_NAME).build())
123                         .documentation("Signature Version 4 (SigV4) Signer")
124                         .build())
125                 .addConfigFieldResolver(
126                         ConfigFieldResolver.builder()
127                                 .location(ConfigFieldResolver.Location.CLIENT)
128                                 .target(ConfigFieldResolver.Target.INITIALIZATION)
129                                 .resolver(SymbolUtils.createValueSymbolBuilder(SIGNER_RESOLVER).build())
130                                 .build())
131                 .build());
132     }
133 
writeMiddlewareRegister(Model model, GoWriter writer, ServiceShape serviceShape)134     private void writeMiddlewareRegister(Model model, GoWriter writer, ServiceShape serviceShape) {
135         writer.addUseImports(SmithyGoDependency.SMITHY_MIDDLEWARE);
136         writer.openBlock("func $L(stack $P, o Options) error {", "}", REGISTER_MIDDLEWARE_FUNCTION,
137                 SymbolUtils.createPointableSymbolBuilder("Stack", SmithyGoDependency.SMITHY_MIDDLEWARE).build(), () -> {
138                     Symbol newMiddlewareSymbol = SymbolUtils.createValueSymbolBuilder(
139                             "NewSignHTTPRequestMiddleware", AwsGoDependency.AWS_SIGNER_V4).build();
140                     Symbol middlewareOptionsSymbol = SymbolUtils.createValueSymbolBuilder(
141                             "SignHTTPRequestMiddlewareOptions", AwsGoDependency.AWS_SIGNER_V4).build();
142 
143                     writer.openBlock("mw := $T($T{", "})", newMiddlewareSymbol, middlewareOptionsSymbol, () -> {
144                         writer.write("CredentialsProvider: o.$L,", AddAwsConfigFields.CREDENTIALS_CONFIG_NAME);
145                         writer.write("Signer: o.$L,", SIGNER_CONFIG_FIELD_NAME);
146                         writer.write("LogSigning: o.$L.IsSigning(),", AddAwsConfigFields.LOG_MODE_CONFIG_NAME);
147                     });
148                     writer.write("return stack.Finalize.Add(mw, middleware.After)");
149                 });
150         writer.write("");
151     }
152 
153     /**
154      * Returns if the SigV4Trait is a auth scheme supported by the service.
155      *
156      * @param model        model definition
157      * @param serviceShape service shape for the API
158      * @return if the SigV4 trait is used by the service.
159      */
isSupportedAuthentication(Model model, ServiceShape serviceShape)160     public static boolean isSupportedAuthentication(Model model, ServiceShape serviceShape) {
161         return ServiceIndex.of(model).getAuthSchemes(serviceShape).values().stream().anyMatch(trait -> trait.getClass()
162                 .equals(SigV4Trait.class));
163     }
164 
165     /**
166      * Returns if the SigV4Trait is a auth scheme for the service and operation.
167      *
168      * @param model     model definition
169      * @param service   service shape for the API
170      * @param operation operation shape
171      * @return if SigV4Trait is an auth scheme for the operation and service.
172      */
hasSigV4AuthScheme(Model model, ServiceShape service, OperationShape operation)173     public static boolean hasSigV4AuthScheme(Model model, ServiceShape service, OperationShape operation) {
174         Map<ShapeId, Trait> auth = ServiceIndex.of(model).getEffectiveAuthSchemes(service.getId(), operation.getId());
175         return auth.containsKey(SigV4Trait.ID) && !operation.hasTrait(OptionalAuthTrait.class);
176     }
177 }
178