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.GoIntegration;
30 import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin;
31 import software.amazon.smithy.model.Model;
32 import software.amazon.smithy.model.knowledge.ServiceIndex;
33 import software.amazon.smithy.model.shapes.OperationShape;
34 import software.amazon.smithy.model.shapes.ServiceShape;
35 import software.amazon.smithy.model.shapes.ShapeId;
36 import software.amazon.smithy.model.traits.OptionalAuthTrait;
37 import software.amazon.smithy.model.traits.Trait;
38 import software.amazon.smithy.utils.ListUtils;
39 
40 /**
41  * Generates Client Configuration, Middleware, and Config Resolvers for AWS Signature Version 4 support.
42  */
43 public final class AwsSignatureVersion4 implements GoIntegration {
44     public static final String REGISTER_MIDDLEWARE_FUNCTION = "addHTTPSignerV4Middleware";
45     public static final String SIGNER_INTERFACE_NAME = "HTTPSignerV4";
46     public static final String SIGNER_CONFIG_FIELD_NAME = SIGNER_INTERFACE_NAME;
47     public static final String NEW_SIGNER_FUNC_NAME = "newDefaultV4Signer";
48     public static final String SIGNER_RESOLVER = "resolve" + SIGNER_CONFIG_FIELD_NAME;
49 
50     private static final List<String> DISABLE_URI_PATH_ESCAPE = ListUtils.of("com.amazonaws.s3#AmazonS3");
51 
52     @Override
getOrder()53     public byte getOrder() {
54         return -48;
55     }
56 
57     @Override
writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator goDelegator )58     public void writeAdditionalFiles(
59             GoSettings settings,
60             Model model,
61             SymbolProvider symbolProvider,
62             GoDelegator goDelegator
63     ) {
64         ServiceShape serviceShape = settings.getService(model);
65         if (isSupportedAuthentication(model, serviceShape)) {
66             goDelegator.useShapeWriter(serviceShape, writer -> {
67                 writeMiddlewareRegister(model, writer, serviceShape);
68                 writerSignerInterface(writer);
69                 writerConfigFieldResolver(writer, serviceShape);
70                 writeNewV4SignerFunc(writer, serviceShape);
71             });
72         }
73     }
74 
writerSignerInterface(GoWriter writer)75     private void writerSignerInterface(GoWriter writer) {
76         writer.openBlock("type $L interface {", "}", SIGNER_INTERFACE_NAME, () -> {
77             writer.addUseImports(SmithyGoDependency.CONTEXT);
78             writer.addUseImports(AwsGoDependency.AWS_CORE);
79             writer.addUseImports(AwsGoDependency.AWS_SIGNER_V4);
80             writer.addUseImports(SmithyGoDependency.NET_HTTP);
81             writer.addUseImports(SmithyGoDependency.TIME);
82             writer.write("SignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, "
83                     + "payloadHash string, service string, region string, signingTime time.Time, "
84                     + "optFns ...func(*v4.SignerOptions)) error");
85         });
86     }
87 
writerConfigFieldResolver(GoWriter writer, ServiceShape serviceShape)88     private void writerConfigFieldResolver(GoWriter writer, ServiceShape serviceShape) {
89         writer.openBlock("func $L(o *Options) {", "}", SIGNER_RESOLVER, () -> {
90             writer.openBlock("if o.$L != nil {", "}", SIGNER_CONFIG_FIELD_NAME, () -> writer.write("return"));
91             writer.write("o.$L = $L(*o)", SIGNER_CONFIG_FIELD_NAME, NEW_SIGNER_FUNC_NAME);
92         });
93         writer.write("");
94     }
writeNewV4SignerFunc(GoWriter writer, ServiceShape serviceShape)95     private void writeNewV4SignerFunc(GoWriter writer, ServiceShape serviceShape) {
96         Symbol signerSymbol = SymbolUtils.createValueSymbolBuilder("Signer",
97                 AwsGoDependency.AWS_SIGNER_V4).build();
98         Symbol newSignerSymbol = SymbolUtils.createValueSymbolBuilder("NewSigner",
99                 AwsGoDependency.AWS_SIGNER_V4).build();
100         Symbol signerOptionsSymbol = SymbolUtils.createPointableSymbolBuilder("SignerOptions",
101                 AwsGoDependency.AWS_SIGNER_V4).build();
102 
103         writer.openBlock("func $L(o Options) *$T {", "}", NEW_SIGNER_FUNC_NAME, signerSymbol, () -> {
104            writer.openBlock("return $T(func(so $P) {", "})", newSignerSymbol, signerOptionsSymbol, () -> {
105                writer.write("so.Logger = o.$L", AddAwsConfigFields.LOGGER_CONFIG_NAME);
106                writer.write("so.LogSigning = o.$L.IsSigning()", AddAwsConfigFields.LOG_MODE_CONFIG_NAME);
107                if (DISABLE_URI_PATH_ESCAPE.contains(serviceShape.getId().toString())) {
108                    writer.write("so.DisableURIPathEscaping = true");
109                }
110            });
111         });
112     }
113 
114     @Override
getClientPlugins()115     public List<RuntimeClientPlugin> getClientPlugins() {
116         return ListUtils.of(RuntimeClientPlugin.builder()
117                 .servicePredicate(AwsSignatureVersion4::isSupportedAuthentication)
118                 .addConfigField(ConfigField.builder()
119                         .name(SIGNER_INTERFACE_NAME)
120                         .type(SymbolUtils.createValueSymbolBuilder(SIGNER_INTERFACE_NAME).build())
121                         .documentation("Signature Version 4 (SigV4) Signer")
122                         .build())
123                 .resolveFunction(SymbolUtils.createValueSymbolBuilder(SIGNER_RESOLVER).build())
124                 .build());
125     }
126 
writeMiddlewareRegister(Model model, GoWriter writer, ServiceShape serviceShape)127     private void writeMiddlewareRegister(Model model, GoWriter writer, ServiceShape serviceShape) {
128         writer.addUseImports(SmithyGoDependency.SMITHY_MIDDLEWARE);
129         writer.openBlock("func $L(stack $P, o Options) error {", "}", REGISTER_MIDDLEWARE_FUNCTION,
130                 SymbolUtils.createPointableSymbolBuilder("Stack", SmithyGoDependency.SMITHY_MIDDLEWARE).build(), () -> {
131                     Symbol newMiddlewareSymbol = SymbolUtils.createValueSymbolBuilder(
132                             "NewSignHTTPRequestMiddleware", AwsGoDependency.AWS_SIGNER_V4).build();
133                     Symbol middlewareOptionsSymbol = SymbolUtils.createValueSymbolBuilder(
134                             "SignHTTPRequestMiddlewareOptions", AwsGoDependency.AWS_SIGNER_V4).build();
135 
136                     writer.openBlock("mw := $T($T{", "})", newMiddlewareSymbol, middlewareOptionsSymbol, () -> {
137                         writer.write("CredentialsProvider: o.$L,", AddAwsConfigFields.CREDENTIALS_CONFIG_NAME);
138                         writer.write("Signer: o.$L,", SIGNER_CONFIG_FIELD_NAME);
139                         writer.write("LogSigning: o.$L.IsSigning(),", AddAwsConfigFields.LOG_MODE_CONFIG_NAME);
140                     });
141                     writer.write("return stack.Finalize.Add(mw, middleware.After)");
142                 });
143         writer.write("");
144     }
145 
146     /**
147      * Returns if the SigV4Trait is a auth scheme supported by the service.
148      *
149      * @param model        model definition
150      * @param serviceShape service shape for the API
151      * @return if the SigV4 trait is used by the service.
152      */
isSupportedAuthentication(Model model, ServiceShape serviceShape)153     public static boolean isSupportedAuthentication(Model model, ServiceShape serviceShape) {
154         return ServiceIndex.of(model).getAuthSchemes(serviceShape).values().stream().anyMatch(trait -> trait.getClass()
155                 .equals(SigV4Trait.class));
156     }
157 
158     /**
159      * Returns if the SigV4Trait is a auth scheme for the service and operation.
160      *
161      * @param model     model definition
162      * @param service   service shape for the API
163      * @param operation operation shape
164      * @return if SigV4Trait is an auth scheme for the operation and service.
165      */
hasSigV4AuthScheme(Model model, ServiceShape service, OperationShape operation)166     public static boolean hasSigV4AuthScheme(Model model, ServiceShape service, OperationShape operation) {
167         Map<ShapeId, Trait> auth = ServiceIndex.of(model).getEffectiveAuthSchemes(service.getId(), operation.getId());
168         return auth.containsKey(SigV4Trait.ID) && !operation.hasTrait(OptionalAuthTrait.class);
169     }
170 }
171