diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitValidator.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitValidator.java new file mode 100644 index 00000000000..2b35343c406 --- /dev/null +++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitValidator.java @@ -0,0 +1,64 @@ +/* + * Copyright 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.aws.iam.traits; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import software.amazon.smithy.aws.traits.ArnTrait; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ResourceShape; +import software.amazon.smithy.model.validation.AbstractValidator; +import software.amazon.smithy.model.validation.ValidationEvent; +import software.amazon.smithy.utils.SmithyInternalApi; +import software.amazon.smithy.utils.StringUtils; + +/** + * Ensures that any resource name defined in the {@link IamResourceTrait} is + * consistent with the resource name used in any {@link ArnTrait} definition + * applied to the resource. + */ +@SmithyInternalApi +public class IamResourceTraitValidator extends AbstractValidator { + @Override + public List validate(Model model) { + List results = new ArrayList<>(); + for (ResourceShape resource : model.getResourceShapesWithTrait(IamResourceTrait.class)) { + // If the resource has both the IamResourceTrait and Arn trait, + // check that the resource name is consistent between the two traits + if (resource.hasTrait(ArnTrait.class)) { + String resourceName = resource.expectTrait(IamResourceTrait.class).getName() + .orElseGet(() -> StringUtils.lowerCase(resource.getId().getName())); + ArnTrait arnTrait = resource.expectTrait(ArnTrait.class); + List arnComponents = parseArnComponents(arnTrait.getTemplate()); + + // Do not check for a matching resource name when the arn is marked as absolute + if (!arnComponents.contains(resourceName) && !arnTrait.isAbsolute()) { + results.add(danger(resource, String.format( + "The `@aws.iam#iamResource` trait applied to the resource " + + "defines an IAM resource name, `%s`, that does not match the `@arn` template, " + + "`%s`, of the resource.", + resourceName, arnTrait.getTemplate()))); + } + } + } + return results; + } + + private List parseArnComponents(String arnTemplate) { + return Arrays.asList(arnTemplate.split("/")); + } +} diff --git a/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator index c77a40ad52b..d03291f5128 100644 --- a/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator +++ b/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator @@ -1 +1,2 @@ software.amazon.smithy.aws.iam.traits.ConditionKeysValidator +software.amazon.smithy.aws.iam.traits.IamResourceTraitValidator 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 4e928105997..a2047f153d8 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 @@ -65,18 +65,4 @@ public void successfullyLoadsConditionKeys() { assertThat(index.getDefinedConditionKeys(service, ShapeId.from("smithy.example#GetResource2")).keySet(), is(empty())); } - - @Test - public void detectsUnknownConditionKeys() { - ValidatedResult result = Model.assembler() - .addImport(getClass().getResource("invalid-condition-keys.smithy")) - .discoverModels(getClass().getClassLoader()) - .assemble(); - - assertTrue(result.isBroken()); - assertThat(result.getValidationEvents(Severity.ERROR).stream() - .map(ValidationEvent::getId) - .collect(Collectors.toSet()), - contains("ConditionKeys")); - } } diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/TestRunnerTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/TestRunnerTest.java new file mode 100644 index 00000000000..aa2c66b47c2 --- /dev/null +++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/TestRunnerTest.java @@ -0,0 +1,21 @@ +package software.amazon.smithy.aws.iam.traits; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.smithy.model.validation.testrunner.SmithyTestCase; +import software.amazon.smithy.model.validation.testrunner.SmithyTestSuite; + +import java.util.concurrent.Callable; +import java.util.stream.Stream; + +public class TestRunnerTest { + @ParameterizedTest(name = "{0}") + @MethodSource("source") + public void testRunner(String filename, Callable callable) throws Exception { + callable.call(); + } + + public static Stream source() { + return SmithyTestSuite.defaultParameterizedTestSource(TestRunnerTest.class); + } +} diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/condition-keys/invalid-condition-keys.errors b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/condition-keys/invalid-condition-keys.errors new file mode 100644 index 00000000000..a347362d072 --- /dev/null +++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/condition-keys/invalid-condition-keys.errors @@ -0,0 +1 @@ +[ERROR] smithy.example#Operation: This operation scoped within the `smithy.example#MyService` service refers to an undefined condition key `foo:qux`. Expected one of the following defined condition keys: [`foo:baz`] | ConditionKeys diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/invalid-condition-keys.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/condition-keys/invalid-condition-keys.smithy similarity index 100% rename from smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/invalid-condition-keys.smithy rename to smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/condition-keys/invalid-condition-keys.smithy diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/invalid-iam-resources.errors b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/invalid-iam-resources.errors new file mode 100644 index 00000000000..4278821c860 --- /dev/null +++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/invalid-iam-resources.errors @@ -0,0 +1,3 @@ +[DANGER] smithy.example#BadIamResourceName: The `@aws.iam#iamResource` trait applied to the resource defines an IAM resource name, `bad-iam-resourceName`, that does not match the `@arn` template, `bad-iam-resource-name/{id}`, of the resource. | IamResourceTrait +[DANGER] smithy.example#IncompatibleResourceName: The `@aws.iam#iamResource` trait applied to the resource defines an IAM resource name, `IncompatibleResourceName`, that does not match the `@arn` template, `beer/{beerId}/incompatible-resource-name`, of the resource. | IamResourceTrait +[DANGER] smithy.example#InvalidResource: The `@aws.iam#iamResource` trait applied to the resource defines an IAM resource name, `invalidResource`, that does not match the `@arn` template, `invalid-resource`, of the resource. | IamResourceTrait diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/invalid-iam-resources.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/invalid-iam-resources.smithy new file mode 100644 index 00000000000..26cbc3b9610 --- /dev/null +++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/invalid-iam-resources.smithy @@ -0,0 +1,53 @@ +$version: "2" +namespace smithy.example + +use aws.api#arn + +@aws.api#service(sdkId: "My") +@aws.iam#defineConditionKeys("foo:baz": {type: "String", documentation: "Foo baz"}) +service MyService { + version: "2019-02-20", + resources: [ + BadIamResourceName, + Beer, + InvalidResource, + ShouldNotThrowAnError + ] +} + +@aws.iam#iamResource(name: "bad-iam-resourceName") +@arn(template: "bad-iam-resource-name/{id}") +resource BadIamResourceName { + identifiers: { + id: String + } +} + +@aws.iam#iamResource(name: "beer") +@arn(template: "beer/{beerId}") +resource Beer { + identifiers: { + beerId: String + } + resources: [IncompatibleResourceName] +} + +@arn(template: "beer/{beerId}/incompatible-resource-name") +@aws.iam#iamResource(name: "IncompatibleResourceName") +resource IncompatibleResourceName { + identifiers: { + beerId: String + } +} + +@aws.iam#iamResource(name: "invalidResource") +@arn(template: "invalid-resource") +resource InvalidResource {} + +@aws.iam#iamResource(name: "shouldNotThrowError") +@arn(template: "{arn}", absolute: true) +resource ShouldNotThrowAnError { + identifiers: { + arn: String + } +} diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/iam-resource.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/iam-resource.smithy index b7fb78baad3..76bfb1e2df7 100644 --- a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/iam-resource.smithy +++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/iam-resource.smithy @@ -2,6 +2,8 @@ $version: "1.0" namespace smithy.example +use aws.api#arn + @aws.api#service(sdkId: "My") service MyService { version: "2020-07-02", @@ -9,6 +11,7 @@ service MyService { } @aws.iam#iamResource(name: "super") +@arn(template: "super/{id1}") resource SuperResource { identifiers: { id1: String,