Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validator to check consistency of resource name used for IamResource #1819

Merged
merged 11 commits into from
Jun 20, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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<ValidationEvent> validate(Model model) {
List<ValidationEvent> 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<String> arnComponents = parseArnComponents(arnTrait.getTemplate());
if (!arnComponents.contains(resourceName)) {
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<String> parseArnComponents(String arnTemplate) {
return Arrays.asList(arnTemplate.split("/"));
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
software.amazon.smithy.aws.iam.traits.ConditionKeysValidator
software.amazon.smithy.aws.iam.traits.IamResourceTraitValidator
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,4 @@ public void successfullyLoadsConditionKeys() {
assertThat(index.getDefinedConditionKeys(service, ShapeId.from("smithy.example#GetResource2")).keySet(),
is(empty()));
}

@Test
public void detectsUnknownConditionKeys() {
ValidatedResult<Model> 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"));
}
}
Original file line number Diff line number Diff line change
@@ -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<SmithyTestCase.Result> callable) throws Exception {
callable.call();
}

public static Stream<?> source() {
return SmithyTestSuite.defaultParameterizedTestSource(TestRunnerTest.class);
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
$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
]
}

@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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ $version: "1.0"

namespace smithy.example

use aws.api#arn

@aws.api#service(sdkId: "My")
service MyService {
version: "2020-07-02",
resources: [SuperResource]
}

@aws.iam#iamResource(name: "super")
@arn(template: "super/{id1}")
resource SuperResource {
identifiers: {
id1: String,
Expand Down