From 767167bbf9806d32607f15abc74ecd0c2e3c3212 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 26 Mar 2020 19:34:44 -0700 Subject: [PATCH] Adds security to individual operations We previously weren't adding security to individual operations if the operation differend from the security of the operation differed from the service because of an applied authorizer trait. --- .../apigateway/openapi/AddAuthorizers.java | 23 +++++++++++++++ .../openapi/AddAuthorizersTest.java | 28 +++++++++++++++++++ .../openapi/effective-authorizers.smithy | 20 +++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/effective-authorizers.smithy 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 {}