diff --git a/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddAuthorizers.java b/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddAuthorizers.java index 1e38ca684b0..021fc7f8d2b 100644 --- a/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddAuthorizers.java +++ b/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddAuthorizers.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.logging.Logger; import software.amazon.smithy.aws.traits.apigateway.AuthorizerDefinition; import software.amazon.smithy.aws.traits.apigateway.AuthorizerIndex; @@ -24,6 +25,7 @@ import software.amazon.smithy.aws.traits.apigateway.AuthorizersTrait; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.openapi.fromsmithy.Context; @@ -32,7 +34,9 @@ import software.amazon.smithy.openapi.fromsmithy.mappers.RemoveUnusedComponents; import software.amazon.smithy.openapi.model.ComponentsObject; import software.amazon.smithy.openapi.model.OpenApi; +import software.amazon.smithy.openapi.model.OperationObject; import software.amazon.smithy.openapi.model.SecurityScheme; +import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.MapUtils; /** @@ -79,6 +83,25 @@ public Map> updateSecurity( .orElse(requirement); } + @Override + public OperationObject updateOperation(Context context, OperationShape shape, OperationObject operation) { + ServiceShape service = context.getService(); + AuthorizerIndex authorizerIndex = context.getModel().getKnowledge(AuthorizerIndex.class); + + // Get the resolved security schemes of the service and operation, and + // only add security if it's different than the service. + String serviceAuth = authorizerIndex.getAuthorizer(service).orElse(null); + String operationAuth = authorizerIndex.getAuthorizer(service, shape).orElse(null); + + if (operationAuth == null || Objects.equals(operationAuth, serviceAuth)) { + return operation; + } + + return operation.toBuilder() + .addSecurity(MapUtils.of(operationAuth, ListUtils.of())) + .build(); + } + @Override public OpenApi after(Context context, OpenApi openapi) { return context.getService().getTrait(AuthorizersTrait.class) diff --git a/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/AddAuthorizersTest.java b/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/AddAuthorizersTest.java index fe695a8c8d0..128b1167dc6 100644 --- a/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/AddAuthorizersTest.java +++ b/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/AddAuthorizersTest.java @@ -16,10 +16,14 @@ package software.amazon.smithy.aws.apigateway.openapi; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertFalse; +import java.util.Optional; import org.junit.jupiter.api.Test; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.Node; @@ -28,6 +32,8 @@ import software.amazon.smithy.openapi.fromsmithy.OpenApiConverter; import software.amazon.smithy.openapi.model.OpenApi; import software.amazon.smithy.openapi.model.SecurityScheme; +import software.amazon.smithy.utils.ListUtils; +import software.amazon.smithy.utils.MapUtils; public class AddAuthorizersTest { @Test @@ -95,4 +101,26 @@ public void addsCustomAuthType() { assertThat(sigV4.getExtension("x-amazon-apigateway-authtype").get(), equalTo(Node.from("myCustomType"))); assertFalse(sigV4.getExtension("x-amazon-apigateway-authorizer").isPresent()); } + + @Test + public void resolvesEffectiveAuthorizersForEachOperation() { + Model model = Model.assembler() + .discoverModels(getClass().getClassLoader()) + .addImport(getClass().getResource("effective-authorizers.smithy")) + .assemble() + .unwrap(); + OpenApi result = OpenApiConverter.create() + .classLoader(getClass().getClassLoader()) + .convert(model, ShapeId.from("smithy.example#ServiceA")); + + // The security of the service is just "foo". + assertThat(result.getSecurity(), contains(MapUtils.of("foo", ListUtils.of()))); + // The "baz" and "foo" securitySchemes must be present. + assertThat(result.getComponents().getSecuritySchemes().keySet(), containsInAnyOrder("baz", "foo")); + // The security schemes of operationA must be empty. + assertThat(result.getPaths().get("/operationA").getGet().get().getSecurity(), is(Optional.empty())); + // The security schemes of operationB must be "baz". + assertThat(result.getPaths().get("/operationB").getGet().get().getSecurity(), + is(Optional.of(ListUtils.of(MapUtils.of("baz", ListUtils.of()))))); + } } diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/effective-authorizers.smithy b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/effective-authorizers.smithy new file mode 100644 index 00000000000..6ce06635721 --- /dev/null +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/effective-authorizers.smithy @@ -0,0 +1,20 @@ +namespace smithy.example + +@protocols([{name: "aws.rest-json-1.1", auth: ["aws.v4"]}]) +@aws.apigateway#authorizer("foo") +@aws.apigateway#authorizers( + foo: {scheme: "aws.v4", type: "aws", uri: "arn:foo"}, + baz: {scheme: "aws.v4", type: "aws", uri: "arn:baz"}) +service ServiceA { + version: "2019-06-17", + operations: [OperationA, OperationB] +} + +// Inherits the authorizer of ServiceA +@http(method: "GET", uri: "/operationA") +operation OperationA {} + +// Overrides the authorizer of ServiceA +@aws.apigateway#authorizer("baz") +@http(method: "GET", uri: "/operationB") +operation OperationB {}