Skip to content

Commit 0a0dd5d

Browse files
committed
Validate one source member for conditionKeyValue
This commit adds validation to ensure that the value for any specific condition key may only be supplied by one member in the operation input.
1 parent 86ffc6a commit 0a0dd5d

File tree

5 files changed

+88
-6
lines changed

5 files changed

+88
-6
lines changed

docs/source-2.0/aws/aws-iam.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,10 @@ Members not annotated with the ``conditionKeyValue`` trait, default to the
605605
condition keys defined with the ``conditionKeyValue`` trait MUST also be
606606
defined via the :ref:`aws.iam#defineConditionKeys-trait` trait.
607607

608+
Any ``conditionKeyValue`` trait applied to a member that is not a top-level
609+
input member to an operation will be ignored. Multiple members within a
610+
top-level input structure MUST NOT supply the value for the same condition key.
611+
608612
The value MUST be a valid IAM identifier or name of a condition key,
609613
meaning it must adhere to the following regular expression:
610614
``"^(([A-Za-z0-9][A-Za-z0-9-\\.]{0,62}:)?[^:\\s]+)$"``. If only a condition key
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.aws.iam.traits;
6+
7+
import static java.lang.String.format;
8+
9+
import java.util.ArrayList;
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
13+
import software.amazon.smithy.aws.traits.ServiceTrait;
14+
import software.amazon.smithy.model.Model;
15+
import software.amazon.smithy.model.knowledge.OperationIndex;
16+
import software.amazon.smithy.model.knowledge.TopDownIndex;
17+
import software.amazon.smithy.model.shapes.MemberShape;
18+
import software.amazon.smithy.model.shapes.OperationShape;
19+
import software.amazon.smithy.model.shapes.ServiceShape;
20+
import software.amazon.smithy.model.validation.AbstractValidator;
21+
import software.amazon.smithy.model.validation.ValidationEvent;
22+
import software.amazon.smithy.model.validation.ValidationUtils;
23+
24+
/**
25+
* Validates that members match 1:1 with supplying a value for a specific condition key.
26+
*/
27+
public class ConditionKeyValueTraitValidator extends AbstractValidator {
28+
@Override
29+
public List<ValidationEvent> validate(Model model) {
30+
List<ValidationEvent> events = new ArrayList<>();
31+
if (!model.getAppliedTraits().contains(ConditionKeyValueTrait.ID)) {
32+
return events;
33+
}
34+
35+
TopDownIndex topDownIndex = TopDownIndex.of(model);
36+
OperationIndex operationIndex = OperationIndex.of(model);
37+
for (ServiceShape service : model.getServiceShapesWithTrait(ServiceTrait.class)) {
38+
for (OperationShape operation : topDownIndex.getContainedOperations(service)) {
39+
Map<String, List<String>> conditionKeyMembers = new HashMap<>();
40+
41+
// Store each member name where the same condition key value is derived.
42+
for (MemberShape memberShape : operationIndex.getInputMembers(operation)
43+
.values()) {
44+
if (memberShape.hasTrait(ConditionKeyValueTrait.ID)) {
45+
ConditionKeyValueTrait trait = memberShape.expectTrait(ConditionKeyValueTrait.class);
46+
String conditionKey = trait.resolveConditionKey(service);
47+
conditionKeyMembers.computeIfAbsent(conditionKey, k -> new ArrayList<>())
48+
.add(memberShape.getMemberName());
49+
}
50+
}
51+
52+
// Emit events if more than one value source member is found for the same key.
53+
for (Map.Entry<String, List<String>> entry : conditionKeyMembers.entrySet()) {
54+
if (entry.getValue().size() > 1) {
55+
events.add(error(operationIndex.expectInputShape(operation),
56+
format("The `%s` condition key has its value supplied in the `%s` operation's input "
57+
+ "shape by multiple members [%s]. This value must only be supplied by one "
58+
+ "member.",
59+
entry.getKey(),
60+
operation.getId(),
61+
ValidationUtils.tickedList(entry.getValue()))));
62+
}
63+
}
64+
}
65+
}
66+
return events;
67+
}
68+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
software.amazon.smithy.aws.iam.traits.ConditionKeysValidator
2+
software.amazon.smithy.aws.iam.traits.ConditionKeyValueTraitValidator
3+
software.amazon.smithy.aws.iam.traits.DefineConditionKeysTraitValidator
24
software.amazon.smithy.aws.iam.traits.IamActionValidator
35
software.amazon.smithy.aws.iam.traits.IamResourceTraitValidator
4-
software.amazon.smithy.aws.iam.traits.DefineConditionKeysTraitValidator
56
software.amazon.smithy.aws.iam.traits.IamResourceTraitConflictingNameValidator
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
[ERROR] smithy.example#EchoInput$id1: This operation `smithy.example#Echo` scoped within the `smithy.example#MyService` service with member `smithy.example#EchoInput$id1` refers to an undefined condition key `smithy:InvalidConditionKey`. Expected one of the following defined condition keys: [`smithy:ActionContextKey1`] | ConditionKeys
1+
[ERROR] smithy.example#EchoInput$id1: This operation `smithy.example#Echo` scoped within the `smithy.example#MyService` service with member `smithy.example#EchoInput$id1` refers to an undefined condition key `myservice:InvalidConditionKey`. Expected one of the following defined condition keys: [`myservice:ActionContextKey1`] | ConditionKeys
2+
[ERROR] smithy.example#EchoInput: The `myservice:ActionContextKey1` condition key has its value supplied in the `smithy.example#Echo` operation's input shape by multiple members [`id2`, `id3`]. This value must only be supplied by one member. | ConditionKeyValueTrait

smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-value.smithy

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,28 @@ $version: "2.0"
22

33
namespace smithy.example
44

5+
use aws.api#service
56
use aws.iam#conditionKeyValue
7+
use aws.iam#defineConditionKeys
68

7-
@aws.iam#defineConditionKeys(
8-
"smithy:ActionContextKey1": { type: "String" }
9+
@defineConditionKeys(
10+
"myservice:ActionContextKey1": { type: "String" }
911
)
10-
@aws.api#service(sdkId: "My")
12+
@service(sdkId: "My", arnNamespace: "myservice")
1113
service MyService {
1214
version: "2019-02-20",
1315
operations: [Echo]
1416
}
1517

1618
operation Echo {
1719
input := {
18-
@aws.iam#conditionKeyValue("smithy:InvalidConditionKey")
20+
@conditionKeyValue("myservice:InvalidConditionKey")
1921
id1: String
22+
23+
@conditionKeyValue("myservice:ActionContextKey1")
24+
id2: String
25+
26+
@conditionKeyValue("myservice:ActionContextKey1")
27+
id3: String
2028
}
2129
}

0 commit comments

Comments
 (0)