Skip to content

Commit

Permalink
Add aws.iam#iamAction trait
Browse files Browse the repository at this point in the history
This commit introduces the iamAction trait to both consolidate and
enhance the customization for IAM actions in derived from Smithy
operations. It deprecates the actionName, actionPermissionDescription,
and requiredAction traits - placing their behavior in the name,
documentation, and requiredAction members respectively.

A relativeDocumentation member is new, allowing for linking to docs
within IAM documentation.

A createsResources member is new, allowing for the explicit listing
of resources that performing an IAM action will create.

A resources member is new, allowing for the override configuration
of required and optional resources that an IAM action can be authorized
against (including the condition keys that can be used). This enables
the detachment of an IAM action from being required to authorize against
its resource hierarchy for special cases.

The specification is also reorganized into sections for traits affecting
resources, actions, and condition keys for easier navigation.
  • Loading branch information
kstich committed Nov 8, 2023
1 parent 4678712 commit fdc5f1b
Show file tree
Hide file tree
Showing 17 changed files with 1,159 additions and 240 deletions.
576 changes: 357 additions & 219 deletions docs/source-2.0/aws/aws-iam.rst

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.aws.iam.traits;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import software.amazon.smithy.model.node.ArrayNode;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.node.StringNode;
import software.amazon.smithy.model.node.ToNode;
import software.amazon.smithy.utils.BuilderRef;
import software.amazon.smithy.utils.SmithyBuilder;
import software.amazon.smithy.utils.ToSmithyBuilder;

/**
* Contains information about a resource an IAM action can be authorized against.
*/
public final class ActionResource implements ToNode, ToSmithyBuilder<ActionResource> {
private static final String CONDITION_KEYS = "conditionKeys";

private final List<String> conditionKeys;

private ActionResource(Builder builder) {
this.conditionKeys = builder.conditionKeys.copy();
}

/**
* Gets the condition keys used for authorizing against this resource.
*
* @return the condition keys.
*/
public List<String> getConditionKeys() {
return conditionKeys;
}

public static Builder builder() {
return new Builder();
}

public static ActionResource fromNode(Node value) {
Builder builder = builder();
value.expectObjectNode()
.warnIfAdditionalProperties(Collections.singletonList(CONDITION_KEYS))
.getArrayMember(CONDITION_KEYS, StringNode::getValue, builder::conditionKeys);
return builder.build();
}

@Override
public Node toNode() {
ObjectNode.Builder builder = Node.objectNodeBuilder();
if (!conditionKeys.isEmpty()) {
builder.withMember(CONDITION_KEYS, ArrayNode.fromStrings(conditionKeys));
}
return builder.build();
}

@Override
public SmithyBuilder<ActionResource> toBuilder() {
return builder().conditionKeys(conditionKeys);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o == null || getClass() != o.getClass()) {
return false;
}
ActionResource that = (ActionResource) o;
return Objects.equals(conditionKeys, that.conditionKeys);
}

@Override
public int hashCode() {
return Objects.hash(conditionKeys);
}

public static final class Builder implements SmithyBuilder<ActionResource> {
private final BuilderRef<List<String>> conditionKeys = BuilderRef.forList();

@Override
public ActionResource build() {
return new ActionResource(this);
}

public Builder conditionKeys(List<String> conditionKeys) {
clearConditionKeys();
this.conditionKeys.get().addAll(conditionKeys);
return this;
}

public Builder clearConditionKeys() {
conditionKeys.get().clear();
return this;
}

public Builder addConditionKey(String conditionKey) {
conditionKeys.get().add(conditionKey);
return this;
}

public Builder removeConditionKey(String conditionKey) {
conditionKeys.get().remove(conditionKey);
return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.aws.iam.traits;

import java.util.Map;
import java.util.Objects;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.node.ToNode;
import software.amazon.smithy.utils.BuilderRef;
import software.amazon.smithy.utils.ListUtils;
import software.amazon.smithy.utils.SmithyBuilder;
import software.amazon.smithy.utils.ToSmithyBuilder;

/**
* Contains information about the resources an IAM action can be authorized against.
*/
public final class ActionResources implements ToNode, ToSmithyBuilder<ActionResources> {
private static final String REQUIRED = "required";
private static final String OPTIONAL = "optional";

private final Map<String, ActionResource> required;
private final Map<String, ActionResource> optional;

private ActionResources(Builder builder) {
required = builder.required.copy();
optional = builder.optional.copy();
}

/**
* Gets the resources that will always be authorized against for
* functionality of the IAM action.
*
* @return the required resources.
*/
public Map<String, ActionResource> getRequired() {
return required;
}

/**
* Gets the resources that will be authorized against based on
* optional behavior of the IAM action.
*
* @return the optional resources.
*/
public Map<String, ActionResource> getOptional() {
return optional;
}

private static Builder builder() {
return new Builder();
}

public static ActionResources fromNode(Node value) {
Builder builder = builder();
ObjectNode node = value.expectObjectNode()
.warnIfAdditionalProperties(ListUtils.of(REQUIRED, OPTIONAL));
if (node.containsMember(REQUIRED)) {
for (Map.Entry<String, Node> entry : node.expectObjectMember(REQUIRED).getStringMap().entrySet()) {
builder.putRequired(entry.getKey(), ActionResource.fromNode(entry.getValue()));
}
}
if (node.containsMember(OPTIONAL)) {
for (Map.Entry<String, Node> entry : node.expectObjectMember(OPTIONAL).getStringMap().entrySet()) {
builder.putOptional(entry.getKey(), ActionResource.fromNode(entry.getValue()));
}
}
return builder.build();
}

@Override
public Node toNode() {
ObjectNode.Builder builder = Node.objectNodeBuilder();
if (!required.isEmpty()) {
ObjectNode.Builder requiredBuilder = Node.objectNodeBuilder();
for (Map.Entry<String, ActionResource> requiredEntry : required.entrySet()) {
requiredBuilder.withMember(requiredEntry.getKey(), requiredEntry.getValue().toNode());
}
}
if (!optional.isEmpty()) {
ObjectNode.Builder optionalBuilder = Node.objectNodeBuilder();
for (Map.Entry<String, ActionResource> optionalEntry : optional.entrySet()) {
optionalBuilder.withMember(optionalEntry.getKey(), optionalEntry.getValue().toNode());
}
}
return builder.build();
}

@Override
public SmithyBuilder<ActionResources> toBuilder() {
return builder().required(required).optional(optional);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o == null || getClass() != o.getClass()) {
return false;
}
ActionResources that = (ActionResources) o;
return Objects.equals(required, that.required)
&& Objects.equals(optional, that.optional);
}

@Override
public int hashCode() {
return Objects.hash(required, optional);
}

public static final class Builder implements SmithyBuilder<ActionResources> {
private final BuilderRef<Map<String, ActionResource>> required = BuilderRef.forOrderedMap();
private final BuilderRef<Map<String, ActionResource>> optional = BuilderRef.forOrderedMap();

@Override
public ActionResources build() {
return new ActionResources(this);
}

public Builder clearRequired() {
required.get().clear();
return this;
}

public Builder required(Map<String, ActionResource> required) {
clearRequired();
this.required.get().putAll(required);
return this;
}

public Builder putRequired(String resourceName, ActionResource actionResource) {
required.get().put(resourceName, actionResource);
return this;
}

public Builder removeRequired(String resourceName) {
required.get().remove(resourceName);
return this;
}

public Builder clearOptional() {
optional.get().clear();
return this;
}

public Builder optional(Map<String, ActionResource> optional) {
clearOptional();
this.optional.get().putAll(optional);
return this;
}

public Builder putOptional(String resourceName, ActionResource actionResource) {
optional.get().put(resourceName, actionResource);
return this;
}

public Builder removeOptional(String resourceName) {
optional.get().remove(resourceName);
return this;
}
}
}
Loading

0 comments on commit fdc5f1b

Please sign in to comment.