Skip to content

Commit

Permalink
Re-add validator to check consistency of resource name used for IamRe…
Browse files Browse the repository at this point in the history
…source (#1954)

Adds validator to check for consistency between resource names, iamResource traits, and arn traits.
  • Loading branch information
hpmellema committed Aug 28, 2023
1 parent 92c5768 commit ce4cf82
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -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<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());

// 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<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,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
}
}
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

0 comments on commit ce4cf82

Please sign in to comment.