From 416da8f69b33b696507c279aa6b4d700c0a94425 Mon Sep 17 00:00:00 2001 From: fossand Date: Wed, 25 Oct 2023 14:59:02 -0700 Subject: [PATCH] Enable `aws.iam#disableConditionKeyInference` trait for service shape --- docs/source-2.0/aws/aws-iam.rst | 39 ++++++++++- .../aws/iam/traits/ConditionKeysIndex.java | 17 +++-- .../resources/META-INF/smithy/aws.iam.json | 4 +- .../iam/traits/ConditionKeysIndexTest.java | 68 +++++++++++++++++-- ...ndition-key-inference-for-resources.smithy | 63 +++++++++++++++++ ...condition-key-inference-for-service.smithy | 63 +++++++++++++++++ 6 files changed, 239 insertions(+), 15 deletions(-) create mode 100644 smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/disable-condition-key-inference-for-resources.smithy create mode 100644 smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/disable-condition-key-inference-for-service.smithy diff --git a/docs/source-2.0/aws/aws-iam.rst b/docs/source-2.0/aws/aws-iam.rst index 06b70d2f100..5ba48d5708c 100644 --- a/docs/source-2.0/aws/aws-iam.rst +++ b/docs/source-2.0/aws/aws-iam.rst @@ -221,10 +221,47 @@ Condition keys in IAM policies can be evaluated with `condition operators`_. Summary Declares that the condition keys of a resource should not be inferred. Trait selector - ``resource`` + ``:test(service, resource)`` Value type Annotation trait +When a service is marked with the ``aws.iam#disableConditionKeyInference`` +trait, all the resources bound to the service will not have condition +keys automatically inferred from its identifiers and the identifiers +of its ancestors. + +The following example shows resources ``MyResource1`` and ``MyResource2`` +have had condition key inference disabled because they are bound to a +service marked with ``aws.iam#disableConditionKeyInference`` trait. + +.. code-block:: smithy + + $version: "2" + + namespace smithy.example + + use aws.api#service + use aws.iam#disableConditionKeyInference + + @service(sdkId: "My Value", arnNamespace: "myservice") + @disableConditionKeyInference + service MyService { + version: "2017-02-11" + resources: [MyResource1, MyResource2] + } + + resource MyResource1 { + identifiers: { + foo: String + } + } + + resource MyResource2 { + identifiers: { + foo: String + } + } + A resource marked with the ``aws.iam#disableConditionKeyInference`` trait will not have its condition keys automatically inferred from its identifiers and the identifiers of its ancestors (if present.) diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndex.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndex.java index 5648ba66472..af5c1ed4b5f 100644 --- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndex.java +++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndex.java @@ -64,14 +64,14 @@ public ConditionKeysIndex(Model model) { service.getResources().stream() .flatMap(id -> OptionalUtils.stream(model.getShape(id))) .forEach(resource -> { - compute(model, service.getId(), arnRoot, resource, null); + compute(model, service, arnRoot, resource, null); }); // Compute the keys of operations of the service. service.getOperations().stream() .flatMap(id -> OptionalUtils.stream(model.getShape(id))) .forEach(operation -> { - compute(model, service.getId(), arnRoot, operation, null); + compute(model, service, arnRoot, operation, null); }); }); }); @@ -153,7 +153,7 @@ public Map getDefinedConditionKeys( private void compute( Model model, - ShapeId service, + ServiceShape service, String arnRoot, Shape subject, ResourceShape parent @@ -163,21 +163,24 @@ private void compute( private void compute( Model model, - ShapeId service, + ServiceShape service, String arnRoot, Shape subject, ResourceShape parent, Set parentDefinitions ) { Set definitions = new HashSet<>(parentDefinitions); - resourceConditionKeys.get(service).put(subject.getId(), definitions); + resourceConditionKeys.get(service.getId()).put(subject.getId(), definitions); subject.getTrait(ConditionKeysTrait.class).ifPresent(trait -> definitions.addAll(trait.getValues())); // Continue recursing into resources and computing keys. subject.asResourceShape().ifPresent(resource -> { + boolean disableConditionKeyInference = resource.hasTrait(DisableConditionKeyInferenceTrait.class) + || service.hasTrait(DisableConditionKeyInferenceTrait.class); + // Add any inferred resource identifiers to the resource and to the service-wide definitions. - Map childIdentifiers = !resource.hasTrait(DisableConditionKeyInferenceTrait.class) - ? inferChildResourceIdentifiers(model, service, arnRoot, resource, parent) + Map childIdentifiers = !disableConditionKeyInference + ? inferChildResourceIdentifiers(model, service.getId(), arnRoot, resource, parent) : MapUtils.of(); // Compute the keys of each child operation, passing no keys. diff --git a/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.json b/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.json index 67e616c2b9a..37e57e63375 100644 --- a/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.json +++ b/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.json @@ -41,9 +41,9 @@ "type": "structure", "traits": { "smithy.api#trait": { - "selector": "resource" + "selector": ":test(service, resource)" }, - "smithy.api#documentation": "Disables the automatic inference of condition keys of a resource." + "smithy.api#documentation": "Disables the automatic inference of condition keys of service's resources or a specific resource." } }, "aws.iam#requiredActions": { diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndexTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndexTest.java index a2047f153d8..9b1d6cfb690 100644 --- a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndexTest.java +++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndexTest.java @@ -22,16 +22,11 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Optional; -import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.validation.Severity; -import software.amazon.smithy.model.validation.ValidatedResult; -import software.amazon.smithy.model.validation.ValidationEvent; public class ConditionKeysIndexTest { @Test @@ -65,4 +60,67 @@ public void successfullyLoadsConditionKeys() { assertThat(index.getDefinedConditionKeys(service, ShapeId.from("smithy.example#GetResource2")).keySet(), is(empty())); } + + @Test + public void disableConditionKeyInferenceForResources() { + Model model = Model.assembler() + .addImport(getClass().getResource("disable-condition-key-inference-for-resources.smithy")) + .discoverModels(getClass().getClassLoader()) + .assemble() + .unwrap(); + + ShapeId service = ShapeId.from("smithy.example#MyService"); + + ConditionKeysIndex index = ConditionKeysIndex.of(model); + + assertThat(index.getConditionKeyNames(service), + containsInAnyOrder("my:service", "aws:operation1", "resource:1", "myservice:Resource1Id1")); + + // Verify inference key myservice:Resource2Id2 does not exist + assertThat(index.getConditionKeyNames(service), not(contains("myservice:Resource2Id2"))); + + assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource1")), + containsInAnyOrder("resource:1", "myservice:Resource1Id1")); + + assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource1")), + not(contains("myservice:Resource2Id2"))); + + assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource2")), + containsInAnyOrder("resource:1", "myservice:Resource1Id1")); + + assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource2")), + not(contains("myservice:Resource2Id2"))); + } + + @Test + public void disableConditionKeyInferenceForService() { + Model model = Model.assembler() + .addImport(getClass().getResource("disable-condition-key-inference-for-service.smithy")) + .discoverModels(getClass().getClassLoader()) + .assemble() + .unwrap(); + + ShapeId service = ShapeId.from("smithy.example#MyService"); + + ConditionKeysIndex index = ConditionKeysIndex.of(model); + + assertThat(index.getConditionKeyNames(service), + containsInAnyOrder("my:service", "aws:operation1", "resource:1")); + + // Verify inference key myservice:Resource1Id1 AND myservice:Resource2Id2 do not exist + assertThat(index.getConditionKeyNames(service), + not(contains("myservice:Resource1Id1", "myservice:Resource2Id2"))); + + assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource1")), + not(contains("myservice:Resource1Id1", "myservice:Resource2Id2"))); + + assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource1")), + contains("resource:1")); + + assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource2")), + contains("resource:1")); + + assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource2")), + not(contains("myservice:Resource1Id1", "myservice:Resource2Id2"))); + } } diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/disable-condition-key-inference-for-resources.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/disable-condition-key-inference-for-resources.smithy new file mode 100644 index 00000000000..d6d8ee9265c --- /dev/null +++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/disable-condition-key-inference-for-resources.smithy @@ -0,0 +1,63 @@ +$version: "1.0" +namespace smithy.example + +@aws.api#service(sdkId: "My") +@aws.iam#defineConditionKeys("my:service": {type: "String", documentation: "Foo baz"}) +service MyService { + version: "2019-02-20", + operations: [Operation1], + resources: [Resource1] +} + +@aws.iam#conditionKeys(["aws:operation1", "my:service"]) +operation Operation1 {} + +@aws.iam#conditionKeys(["resource:1"]) +resource Resource1 { + identifiers: { + id1: ArnString, + }, + resources: [Resource2] +} + +@aws.iam#disableConditionKeyInference +resource Resource2 { + identifiers: { + id1: ArnString, + id2: FooString, + }, + read: GetResource2, + list: ListResource2, +} + +@readonly +operation GetResource2 { + input: GetResource2Input +} + +structure GetResource2Input { + @required + id1: ArnString, + + @required + id2: FooString +} + +@documentation("This is Foo") +string FooString + +@readonly +operation ListResource2 { + input: ListResource2Input, + output: ListResource2Output +} + +structure ListResource2Input { + @required + id1: ArnString, +} + +structure ListResource2Output {} + +@aws.api#arnReference(type: "ec2:Instance") +string ArnString diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/disable-condition-key-inference-for-service.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/disable-condition-key-inference-for-service.smithy new file mode 100644 index 00000000000..2c7396243ac --- /dev/null +++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/disable-condition-key-inference-for-service.smithy @@ -0,0 +1,63 @@ +$version: "1.0" +namespace smithy.example + +@aws.api#service(sdkId: "My") +@aws.iam#defineConditionKeys("my:service": {type: "String", documentation: "Foo baz"}) +@aws.iam#disableConditionKeyInference +service MyService { + version: "2019-02-20", + operations: [Operation1], + resources: [Resource1] +} + +@aws.iam#conditionKeys(["aws:operation1", "my:service"]) +operation Operation1 {} + +@aws.iam#conditionKeys(["resource:1"]) +resource Resource1 { + identifiers: { + id1: ArnString, + }, + resources: [Resource2] +} + +resource Resource2 { + identifiers: { + id1: ArnString, + id2: FooString, + }, + read: GetResource2, + list: ListResource2, +} + +@readonly +operation GetResource2 { + input: GetResource2Input +} + +structure GetResource2Input { + @required + id1: ArnString, + + @required + id2: FooString +} + +@documentation("This is Foo") +string FooString + +@readonly +operation ListResource2 { + input: ListResource2Input, + output: ListResource2Output +} + +structure ListResource2Input { + @required + id1: ArnString, +} + +structure ListResource2Output {} + +@aws.api#arnReference(type: "ec2:Instance") +string ArnString