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

Enable aws.iam#disableConditionKeyInference trait for service shape #2019

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion docs/source-2.0/aws/aws-iam.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
Expand Down Expand Up @@ -153,7 +153,7 @@ public Map<String, ConditionKeyDefinition> getDefinedConditionKeys(

private void compute(
Model model,
ShapeId service,
ServiceShape service,
String arnRoot,
Shape subject,
ResourceShape parent
Expand All @@ -163,21 +163,24 @@ private void compute(

private void compute(
Model model,
ShapeId service,
ServiceShape service,
String arnRoot,
Shape subject,
ResourceShape parent,
Set<String> parentDefinitions
) {
Set<String> 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<String, String> childIdentifiers = !resource.hasTrait(DisableConditionKeyInferenceTrait.class)
? inferChildResourceIdentifiers(model, service, arnRoot, resource, parent)
Map<String, String> childIdentifiers = !disableConditionKeyInference
? inferChildResourceIdentifiers(model, service.getId(), arnRoot, resource, parent)
: MapUtils.of();

// Compute the keys of each child operation, passing no keys.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -65,4 +60,67 @@ public void successfullyLoadsConditionKeys() {
assertThat(index.getDefinedConditionKeys(service, ShapeId.from("smithy.example#GetResource2")).keySet(),
is(empty()));
}

@Test
AndrewFossAWS marked this conversation as resolved.
Show resolved Hide resolved
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")));
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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