Skip to content

Commit

Permalink
add modeled endpoint resolver generation (#456)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucix-aws authored Oct 4, 2023
1 parent 07b8b5c commit 33bc804
Show file tree
Hide file tree
Showing 24 changed files with 813 additions and 18 deletions.
3 changes: 3 additions & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Package auth defines protocol-agnostic authentication types for smithy
// clients.
package auth
26 changes: 26 additions & 0 deletions auth/identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package auth

import (
"context"
"time"

"github.com/aws/smithy-go"
)

// Identity contains information that identifies who the user making the
// request is.
type Identity interface {
Expiration() time.Time
}

// IdentityResolver defines the interface through which an Identity is
// retrieved.
type IdentityResolver interface {
GetIdentity(ctx context.Context, params *smithy.Properties) (Identity, error)
}

// IdentityResolverOptions defines the interface through which an entity can be
// queried to retrieve an IdentityResolver for a given auth scheme.
type IdentityResolverOptions interface {
GetIdentityResolver(schemeID string) IdentityResolver
}
10 changes: 10 additions & 0 deletions auth/option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package auth

import "github.com/aws/smithy-go"

// Option represents a possible authentication method for an operation.
type Option struct {
SchemeID string
IdentityProperties smithy.Properties
SignerProperties smithy.Properties
}
1 change: 1 addition & 0 deletions codegen/smithy-go-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ extra["moduleName"] = "software.amazon.smithy.go.codegen"

dependencies {
api("software.amazon.smithy:smithy-codegen-core:$smithyVersion")
api("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
implementation("software.amazon.smithy:smithy-waiters:$smithyVersion")
api("com.atlassian.commonmark:commonmark:0.15.2")
api("org.jsoup:jsoup:1.14.1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,10 @@ public ChainWritable() {
writables = new ArrayList<>();
}

public boolean isEmpty() {
return writables.isEmpty();
}

public ChainWritable add(GoWriter.Writable writable) {
writables.add(writable);
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public final class SmithyGoDependency {
public static final GoDependency SMITHY_DOCUMENT = smithy("document", "smithydocument");
public static final GoDependency SMITHY_DOCUMENT_JSON = smithy("document/json", "smithydocumentjson");
public static final GoDependency SMITHY_SYNC = smithy("sync", "smithysync");
public static final GoDependency SMITHY_AUTH = smithy("auth", "smithyauth");
public static final GoDependency SMITHY_AUTH_BEARER = smithy("auth/bearer");
public static final GoDependency SMITHY_ENDPOINTS = smithy("endpoints", "smithyendpoints");
public static final GoDependency SMITHY_ENDPOINT_RULESFN = smithy("endpoints/private/rulesfn");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
public final class SmithyGoTypes {
private SmithyGoTypes() { }

public static final class Smithy {
public static final Symbol Properties = SmithyGoDependency.SMITHY.pointableSymbol("Properties");
}

public static final class Ptr {
public static final Symbol String = SmithyGoDependency.SMITHY_PTR.valueSymbol("String");
public static final Symbol Bool = SmithyGoDependency.SMITHY_PTR.valueSymbol("Bool");
Expand All @@ -40,6 +44,18 @@ public static final class Middleware {
public static final class Transport {
public static final class Http {
public static final Symbol Request = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.pointableSymbol("Request");

public static final Symbol NewSigV4Option = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("NewSigV4Option");
public static final Symbol NewSigV4AOption = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("NewSigV4AOption");
public static final Symbol NewBearerOption = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("NewBearerOption");
public static final Symbol NewAnonymousOption = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("NewAnonymousOption");

public static final Symbol SigV4Properties = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.pointableSymbol("SigV4Properties");
public static final Symbol SigV4AProperties = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.pointableSymbol("SigV4AProperties");
}
}

public static final class Auth {
public static final Symbol Option = SmithyGoDependency.SMITHY_AUTH.pointableSymbol("Option");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ public void generate() {
context.getWriter().get()
.write("$W", new AuthParametersGenerator(context).generate())
.write("")
.write("$W", new AuthParametersResolverGenerator(context).generate());
.write("$W", new AuthParametersResolverGenerator(context).generate())
.write("")
.write("$W", getResolverGenerator().generate());
}

// TODO(i&a): allow consuming generators to overwrite
private AuthSchemeResolverGenerator getResolverGenerator() {
return new AuthSchemeResolverGenerator(context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*
* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.smithy.go.codegen.auth;

import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate;
import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate;
import static software.amazon.smithy.go.codegen.GoWriter.goTemplate;

import java.util.Map;
import java.util.stream.Collectors;
import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait;
import software.amazon.smithy.go.codegen.GoStdlibTypes;
import software.amazon.smithy.go.codegen.GoWriter;
import software.amazon.smithy.go.codegen.SmithyGoTypes;
import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition;
import software.amazon.smithy.go.codegen.integration.ProtocolGenerator;
import software.amazon.smithy.model.knowledge.ServiceIndex;
import software.amazon.smithy.model.knowledge.TopDownIndex;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.utils.MapUtils;

/**
* Implements modeled auth scheme resolver generation.
*/
public class AuthSchemeResolverGenerator {
public static final String INTERFACE_NAME = "AuthSchemeResolver";
public static final String DEFAULT_NAME = "defaultAuthSchemeResolver";

private final ProtocolGenerator.GenerationContext context;
private final ServiceIndex serviceIndex;
private final Map<ShapeId, AuthSchemeDefinition> schemeDefinitions;

public AuthSchemeResolverGenerator(ProtocolGenerator.GenerationContext context) {
this.context = context;
this.serviceIndex = ServiceIndex.of(context.getModel());
this.schemeDefinitions = context.getIntegrations().stream()
.flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream())
.flatMap(it -> it.getAuthSchemeDefinitions().entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

// an operation has auth overrides if any of the following are true:
// 1. its list of supported schemes differs from that of the service
// 2. its auth optionality differs from that of the service (covered by checking [1] w/ NO_AUTH_AWARE)
// 3. it has an unsigned payload
private boolean hasAuthOverrides(OperationShape operation) {
var serviceSchemes = serviceIndex
.getEffectiveAuthSchemes(context.getService(), ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE)
.keySet();
var operationSchemes = serviceIndex
.getEffectiveAuthSchemes(context.getService(), operation, ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE)
.keySet();
return !serviceSchemes.equals(operationSchemes) || operation.hasTrait(UnsignedPayloadTrait.class);
}

public GoWriter.Writable generate() {
return goTemplate("""
$W
$W
""", generateInterface(), generateDefault());
}

private GoWriter.Writable generateInterface() {
return goTemplate("""
$W
type $L interface {
ResolveAuthSchemes($T, *$L) ([]$P, error)
}
""",
generateDocs(),
INTERFACE_NAME,
GoStdlibTypes.Context.Context,
AuthParametersGenerator.STRUCT_NAME,
SmithyGoTypes.Auth.Option);
}

private GoWriter.Writable generateDocs() {
return goDocTemplate("AuthSchemeResolver returns a set of possible authentication options for an "
+ "operation.");
}

private GoWriter.Writable generateDefault() {
return goTemplate("""
$W
$W
""",
generateDefaultStruct(),
generateDefaultResolve());
}

private GoWriter.Writable generateDefaultStruct() {
return goTemplate("""
type $1L struct{}
var _ $2L = (*$1L)(nil)
""", DEFAULT_NAME, INTERFACE_NAME);
}

private GoWriter.Writable generateDefaultResolve() {
return goTemplate("""
func (*$receiver:L) ResolveAuthSchemes(ctx $ctx:L, params *$params:L) ([]$options:P, error) {
if overrides, ok := operationAuthOptions[params.Operation]; ok {
return overrides(params), nil
}
return serviceAuthOptions(params), nil
}
$opAuthOptions:W
$svcAuthOptions:W
""", MapUtils.of(
"receiver", DEFAULT_NAME,
"ctx", GoStdlibTypes.Context.Context,
"params", AuthParametersGenerator.STRUCT_NAME,
"options", SmithyGoTypes.Auth.Option,
"opAuthOptions", generateOperationAuthOptions(),
"svcAuthOptions", generateServiceAuthOptions()));
}

private GoWriter.Writable generateOperationAuthOptions() {
var options = new GoWriter.ChainWritable();
TopDownIndex.of(context.getModel())
.getContainedOperations(context.getService()).stream()
.filter(this::hasAuthOverrides)
.forEach(it -> {
options.add(generateOperationAuthOptionsEntry(it));
});

return goTemplate("""
var operationAuthOptions = map[string]func(*$L) []$P{
$W
}
""",
AuthParametersGenerator.STRUCT_NAME,
SmithyGoTypes.Auth.Option,
options.compose());
}

private GoWriter.Writable generateOperationAuthOptionsEntry(OperationShape operation) {
var options = new GoWriter.ChainWritable();
serviceIndex
.getEffectiveAuthSchemes(context.getService(), operation, ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE)
.entrySet().stream()
.filter(it -> schemeDefinitions.containsKey(it.getKey()))
.forEach(it -> {
var definition = schemeDefinitions.get(it.getKey());
options.add(definition.generateOperationOption(context, operation));
});

return options.isEmpty()
? emptyGoTemplate()
: goTemplate("""
$1S: func(params *$2L) []$3P {
return []$3P{
$4W
}
},""",
operation.getId().getName(),
AuthParametersGenerator.STRUCT_NAME,
SmithyGoTypes.Auth.Option,
options.compose());
}

private GoWriter.Writable generateServiceAuthOptions() {
var options = new GoWriter.ChainWritable();
serviceIndex
.getEffectiveAuthSchemes(context.getService(), ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE)
.entrySet().stream()
.filter(it -> schemeDefinitions.containsKey(it.getKey()))
.forEach(it -> {
var definition = schemeDefinitions.get(it.getKey());
options.add(definition.generateServiceOption(context, context.getService()));
});

return goTemplate("""
func serviceAuthOptions(params *$1L) []$2P {
return []$2P{
$3W
}
}
""",
AuthParametersGenerator.STRUCT_NAME,
SmithyGoTypes.Auth.Option,
options.compose());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import software.amazon.smithy.go.codegen.GoWriter;
import software.amazon.smithy.go.codegen.integration.ProtocolGenerator;
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter;
Expand All @@ -32,8 +34,14 @@
public class EndpointParameterBindingsGenerator {
private final ProtocolGenerator.GenerationContext context;

private final Map<String, GoWriter.Writable> builtinBindings;

public EndpointParameterBindingsGenerator(ProtocolGenerator.GenerationContext context) {
this.context = context;
this.builtinBindings = context.getIntegrations().stream()
.flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream())
.flatMap(it -> it.getEndpointBuiltinBindings().entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

public GoWriter.Writable generate() {
Expand Down Expand Up @@ -81,7 +89,7 @@ private GoWriter.Writable generateBuiltinBindings() {
writer.write(
"params.$L = $W",
EndpointParametersGenerator.getExportedParameterName(param),
bindings.get(param.getBuiltIn().get()));
builtinBindings.get(param.getBuiltIn().get()));
}
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*
*
*/

package software.amazon.smithy.go.codegen.integration;

import software.amazon.smithy.go.codegen.GoWriter;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;

/**
* Defines code generation for a modeled auth scheme.
*/
public interface AuthSchemeDefinition {
/**
* Generates the service default option for this scheme.
*/
GoWriter.Writable generateServiceOption(ProtocolGenerator.GenerationContext context, ServiceShape service);

/**
* Generates an operation-specific option for this scheme. This will only be called when the generator encounters
* an operation with auth overrides.
*/
GoWriter.Writable generateOperationOption(ProtocolGenerator.GenerationContext context, OperationShape operation);
}
Loading

0 comments on commit 33bc804

Please sign in to comment.