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