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