Skip to content

Commit 86ffc6a

Browse files
committed
Optional arnNamespace for condition key traits
This commit makes an arnNamespace prefix in a full condition key (`arnNamespace:name`) optional in all IAM traits that support specifying the condition keys. Trait definitions are relaxed, utility methods are provided, and validators and the ConditionKeysIndex are updated to use resolve the full names based on the service in context. Minor optimization of validation and index computation is also done.
1 parent 840f69e commit 86ffc6a

File tree

15 files changed

+248
-132
lines changed

15 files changed

+248
-132
lines changed

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

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,14 @@ Condition keys derived automatically can be applied to a resource or operation
491491
explicitly. Condition keys applied this way MUST be either :ref:`inferred <deriving-condition-keys>`
492492
or explicitly defined via the :ref:`aws.iam#defineConditionKeys-trait` trait.
493493

494+
Values in the list MUST be valid IAM identifiers or names of condition keys,
495+
meaning they must adhere to the following regular expression:
496+
``"^(([A-Za-z0-9][A-Za-z0-9-\\.]{0,62}:)?[^:\\s]+)$"``. If only a condition key
497+
name is specified, the service is inferred to be the ``arnNamespace``.
498+
494499
The following example's ``MyResource`` resource has the
495-
``myservice:MyResourceFoo`` and ``myservice:Bar`` condition keys. The
496-
``MyOperation`` operation has the ``aws:region`` condition key.
500+
``myservice:MyResourceFoo``, ``myservice:Bar``, and ``myservice:Baz`` condition
501+
keys. The ``MyOperation`` operation has the ``aws:region`` condition key.
497502

498503
.. code-block:: smithy
499504
@@ -506,13 +511,16 @@ The following example's ``MyResource`` resource has the
506511
use aws.iam#conditionKeys
507512
508513
@service(sdkId: "My Value", arnNamespace: "myservice")
509-
@defineConditionKeys("myservice:Bar": { type: "String" })
514+
@defineConditionKeys(
515+
"myservice:Bar": { type: "String" }
516+
"myservice:Baz": { type: "String" }
517+
)
510518
service MyService {
511519
version: "2017-02-11"
512520
resources: [MyResource]
513521
}
514522
515-
@conditionKeys(["myservice:Bar"])
523+
@conditionKeys(["myservice:Bar", "Baz"])
516524
resource MyResource {
517525
identifiers: {foo: String}
518526
operations: [MyOperation]
@@ -547,6 +555,11 @@ MUST also be defined via the :ref:`aws.iam#defineConditionKeys-trait` trait.
547555
:ref:`Inferred resource condition keys <deriving-condition-keys>` MUST NOT be
548556
included with the ``serviceResolvedConditionKeys`` trait.
549557

558+
Values in the list MUST be valid IAM identifiers or names of condition keys,
559+
meaning they must adhere to the following regular expression:
560+
``"^(([A-Za-z0-9][A-Za-z0-9-\\.]{0,62}:)?[^:\\s]+)$"``. If only a condition key
561+
name is specified, the service is inferred to be the ``arnNamespace``.
562+
550563
The following example defines two service-specific condition keys:
551564

552565
* ``myservice:ActionContextKey1`` is expected to be resolved by the service.
@@ -565,8 +578,9 @@ The following example defines two service-specific condition keys:
565578
@defineConditionKeys(
566579
"myservice:ActionContextKey1": { type: "String" },
567580
"myservice:ActionContextKey2": { type: "String" }
581+
"myservice:AnotherContextKey": { type: "String" }
568582
)
569-
@serviceResolvedConditionKeys(["myservice:ActionContextKey1"])
583+
@serviceResolvedConditionKeys(["myservice:ActionContextKey1", "AnotherContextKey"])
570584
@service(sdkId: "My Value", arnNamespace: "myservice")
571585
service MyService {
572586
version: "2018-05-10"
@@ -591,6 +605,11 @@ Members not annotated with the ``conditionKeyValue`` trait, default to the
591605
condition keys defined with the ``conditionKeyValue`` trait MUST also be
592606
defined via the :ref:`aws.iam#defineConditionKeys-trait` trait.
593607

608+
The value MUST be a valid IAM identifier or name of a condition key,
609+
meaning it must adhere to the following regular expression:
610+
``"^(([A-Za-z0-9][A-Za-z0-9-\\.]{0,62}:)?[^:\\s]+)$"``. If only a condition key
611+
name is specified, the service is inferred to be the ``arnNamespace``.
612+
594613
In the input shape for ``OperationA``, the trait ``conditionKeyValue``
595614
explicitly binds ``ActionContextKey1`` to the field ``key``.
596615

@@ -607,6 +626,7 @@ explicitly binds ``ActionContextKey1`` to the field ``key``.
607626
608627
@defineConditionKeys(
609628
"myservice:ActionContextKey1": { type: "String" }
629+
"myservice:AnotherContextKey": { type: "String" }
610630
)
611631
@service(sdkId: "My Value", arnNamespace: "myservice")
612632
service MyService {
@@ -619,6 +639,9 @@ explicitly binds ``ActionContextKey1`` to the field ``key``.
619639
input := {
620640
@conditionKeyValue("myservice:ActionContextKey1")
621641
key: String
642+
643+
@conditionKeyValue("AnotherContextKey")
644+
key: String
622645
}
623646
output := {
624647
out: String

smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeyValueTrait.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import software.amazon.smithy.model.FromSourceLocation;
88
import software.amazon.smithy.model.SourceLocation;
9+
import software.amazon.smithy.model.shapes.ServiceShape;
910
import software.amazon.smithy.model.shapes.ShapeId;
1011
import software.amazon.smithy.model.traits.StringTrait;
1112

@@ -20,6 +21,16 @@ public ConditionKeyValueTrait(String conditionKey, FromSourceLocation sourceLoca
2021
super(ID, conditionKey, sourceLocation);
2122
}
2223

24+
/**
25+
* Gets the fully resolved condition key based on the service's ARN namespace.
26+
*
27+
* @param service The service to resolve no-prefix condition key name to.
28+
* @return the resolved condition key.
29+
*/
30+
public String resolveConditionKey(ServiceShape service) {
31+
return ConditionKeysIndex.resolveFullConditionKey(service, getValue());
32+
}
33+
2334
public static final class Provider extends StringTrait.Provider<ConditionKeyValueTrait> {
2435
public Provider() {
2536
super(ID, ConditionKeyValueTrait::new);

smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndex.java

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.HashMap;
99
import java.util.HashSet;
1010
import java.util.Map;
11+
import java.util.Optional;
1112
import java.util.Set;
1213
import software.amazon.smithy.aws.traits.ArnReferenceTrait;
1314
import software.amazon.smithy.aws.traits.ServiceTrait;
@@ -21,7 +22,6 @@
2122
import software.amazon.smithy.model.shapes.ToShapeId;
2223
import software.amazon.smithy.model.traits.DocumentationTrait;
2324
import software.amazon.smithy.utils.MapUtils;
24-
import software.amazon.smithy.utils.OptionalUtils;
2525
import software.amazon.smithy.utils.SetUtils;
2626
import software.amazon.smithy.utils.StringUtils;
2727

@@ -47,13 +47,7 @@ public ConditionKeysIndex(Model model) {
4747
if (service.hasTrait(DefineConditionKeysTrait.ID)) {
4848
DefineConditionKeysTrait trait = service.expectTrait(DefineConditionKeysTrait.class);
4949
for (Map.Entry<String, ConditionKeyDefinition> entry : trait.getConditionKeys().entrySet()) {
50-
// If no colon is present, we infer that this condition key is for the
51-
// current service and apply its ARN namespace.
52-
String key = entry.getKey();
53-
if (!key.contains(":")) {
54-
key = arnNamespace + ":" + key;
55-
}
56-
serviceKeys.put(key, entry.getValue());
50+
serviceKeys.put(resolveFullConditionKey(service, entry.getKey()), entry.getValue());
5751
}
5852
}
5953
serviceConditionKeys.put(service.getId(), serviceKeys);
@@ -75,11 +69,20 @@ public static ConditionKeysIndex of(Model model) {
7569
return model.getKnowledge(ConditionKeysIndex.class, ConditionKeysIndex::new);
7670
}
7771

72+
static String resolveFullConditionKey(ServiceShape service, String conditionKey) {
73+
// If no colon is present, we infer that this condition key is for the
74+
// current service and apply its ARN namespace.
75+
if (conditionKey.contains(":")) {
76+
return conditionKey;
77+
}
78+
return service.expectTrait(ServiceTrait.class).getArnNamespace() + ":" + conditionKey;
79+
}
80+
7881
/**
79-
* Get all of the explicit and inferred condition keys used in the entire service.
82+
* Get all the explicit and inferred condition keys used in the entire service.
8083
*
8184
* <p>The result does not include global condition keys like "aws:accountId".
82-
* Use {@link #getConditionKeyNames} to find all of the condition keys used
85+
* Use {@link #getConditionKeyNames} to find all the condition keys used
8386
* but not necessarily defined for a service.
8487
*
8588
* @param service Service shape/shapeId to get.
@@ -90,21 +93,25 @@ public Map<String, ConditionKeyDefinition> getDefinedConditionKeys(ToShapeId ser
9093
}
9194

9295
/**
93-
* Get all of the condition key names used in a service.
96+
* Get all the condition key names used in a service.
9497
*
9598
* @param service Service shape/shapeId use to scope the result.
9699
* @return Returns the conditions keys of the service or an empty map when not found.
97100
*/
98101
public Set<String> getConditionKeyNames(ToShapeId service) {
99-
return resourceConditionKeys.getOrDefault(service.toShapeId(), MapUtils.of())
100-
.values()
101-
.stream()
102-
.flatMap(Set::stream)
103-
.collect(SetUtils.toUnmodifiableSet());
102+
if (!resourceConditionKeys.containsKey(service.toShapeId())) {
103+
return SetUtils.of();
104+
}
105+
106+
Set<String> names = new HashSet<>();
107+
for (Set<String> resourceKeyNames : resourceConditionKeys.get(service.toShapeId()).values()) {
108+
names.addAll(resourceKeyNames);
109+
}
110+
return names;
104111
}
105112

106113
/**
107-
* Get all of the defined condition keys used in an operation or resource, including
114+
* Get all the defined condition keys used in an operation or resource, including
108115
* any inferred keys and keys inherited by parent resource bindings.
109116
*
110117
* @param service Service shape/shapeId use to scope the result.
@@ -119,11 +126,11 @@ public Set<String> getConditionKeyNames(ToShapeId service, ToShapeId resourceOrO
119126
}
120127

121128
/**
122-
* Get all of the defined condition keys used in an operation or resource, including
129+
* Get all the defined condition keys used in an operation or resource, including
123130
* any inferred keys and keys inherited by parent resource bindings.
124131
*
125132
* <p>The result does not include global condition keys like "aws:accountId".
126-
* Use {@link #getConditionKeyNames} to find all of the condition keys used
133+
* Use {@link #getConditionKeyNames} to find all the condition keys used
127134
* but not necessarily defined for a resource or operation.
128135
*
129136
* @param service Service shape/shapeId use to scope the result.
@@ -170,7 +177,9 @@ private void compute(
170177
definitions.addAll(parentDefinitions);
171178
}
172179
resourceConditionKeys.get(service.getId()).put(subject.getId(), definitions);
173-
subject.getTrait(ConditionKeysTrait.class).ifPresent(trait -> definitions.addAll(trait.getValues()));
180+
if (subject.hasTrait(ConditionKeysTrait.ID)) {
181+
definitions.addAll(subject.expectTrait(ConditionKeysTrait.class).resolveConditionKeys(service));
182+
}
174183

175184
// Continue recursing into resources and computing keys.
176185
subject.asResourceShape().ifPresent(resource -> {
@@ -183,18 +192,23 @@ private void compute(
183192
: MapUtils.of();
184193

185194
// Compute the keys of each child operation, passing no keys.
186-
resource.getAllOperations()
187-
.stream()
188-
.flatMap(id -> OptionalUtils.stream(model.getShape(id)))
189-
.forEach(child -> compute(model, service, arnRoot, child, resource));
195+
for (ShapeId operationId : resource.getOperations()) {
196+
Optional<Shape> operationOptional = model.getShape(operationId);
197+
if (operationOptional.isPresent()) {
198+
compute(model, service, arnRoot, operationOptional.get(), resource);
199+
}
200+
}
190201

191202
// Child resources always inherit the identifiers of the parent.
192203
definitions.addAll(childIdentifiers.values());
193204

194205
// Compute the keys of each child resource.
195-
resource.getResources().stream().flatMap(id -> OptionalUtils.stream(model.getShape(id))).forEach(child -> {
196-
compute(model, service, arnRoot, child, resource, definitions);
197-
});
206+
for (ShapeId resourceId : resource.getResources()) {
207+
Optional<Shape> resourceOptional = model.getShape(resourceId);
208+
if (resourceOptional.isPresent()) {
209+
compute(model, service, arnRoot, resourceOptional.get(), resource, definitions);
210+
}
211+
}
198212
});
199213
}
200214

@@ -230,7 +244,7 @@ private Map<String, String> inferChildResourceIdentifiers(
230244
builder.documentation(shape.getTrait(DocumentationTrait.class)
231245
.map(DocumentationTrait::getValue)
232246
.orElse(computeIdentifierDocs(resource, childId)));
233-
// The identifier name is comprised of "[arn service]:[Resource name][uppercase identifier name]
247+
// The identifier name consists of "[arn service]:[Resource name][uppercase identifier name]".
234248
String computeIdentifierName = computeIdentifierName(arnRoot, resource, childId);
235249
// Add the computed identifier binding and resolved context key to the result map.
236250
result.put(childId, computeIdentifierName);

smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysTrait.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,22 @@
44
*/
55
package software.amazon.smithy.aws.iam.traits;
66

7+
import java.util.ArrayList;
78
import java.util.List;
89
import software.amazon.smithy.model.FromSourceLocation;
910
import software.amazon.smithy.model.SourceLocation;
11+
import software.amazon.smithy.model.shapes.ServiceShape;
1012
import software.amazon.smithy.model.shapes.ShapeId;
1113
import software.amazon.smithy.model.traits.StringListTrait;
14+
import software.amazon.smithy.utils.ListUtils;
1215
import software.amazon.smithy.utils.ToSmithyBuilder;
1316

1417
/**
1518
* Applies condition keys to an operation or resource.
1619
*/
1720
public final class ConditionKeysTrait extends StringListTrait implements ToSmithyBuilder<ConditionKeysTrait> {
1821
public static final ShapeId ID = ShapeId.from("aws.iam#conditionKeys");
22+
private List<String> resolvedConditionKeys;
1923

2024
public ConditionKeysTrait(List<String> keys, FromSourceLocation sourceLocation) {
2125
super(ID, keys, sourceLocation);
@@ -29,6 +33,23 @@ public static Builder builder() {
2933
return new Builder();
3034
}
3135

36+
/**
37+
* Gets the fully resolved condition key names based on the service's ARN namespace.
38+
*
39+
* @param service The service to resolve no-prefix condition key names to.
40+
* @return the resolved condition key names.
41+
*/
42+
public List<String> resolveConditionKeys(ServiceShape service) {
43+
if (resolvedConditionKeys == null) {
44+
List<String> keys = new ArrayList<>();
45+
for (String value : getValues()) {
46+
keys.add(ConditionKeysIndex.resolveFullConditionKey(service, value));
47+
}
48+
resolvedConditionKeys = ListUtils.copyOf(keys);
49+
}
50+
return resolvedConditionKeys;
51+
}
52+
3253
public static final class Provider extends StringListTrait.Provider<ConditionKeysTrait> {
3354
public Provider() {
3455
super(ID, ConditionKeysTrait::new);

0 commit comments

Comments
 (0)