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

Improve deprecation of IAM Action traits #2095

Merged
merged 2 commits into from
Jan 10, 2024
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
421 changes: 217 additions & 204 deletions docs/source-2.0/aws/aws-iam.rst

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions docs/source-2.0/guides/generating-cloudformation-resources.rst
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,10 @@ disableHandlerPermissionGeneration (``boolean``)
Sets whether to disable generating ``handler`` ``permission`` lists for
Resource Schemas. By default, handler permissions lists are automatically
added to schemas based on :ref:`lifecycle-operations` and permissions
listed in the :ref:`aws.iam#requiredActions-trait` on the operation. See
`the handlers section`_ in the CloudFormation Resource Schemas
documentation for more information.
listed in the :ref:`requiredActions property of the aws.iam#iamAction
trait <aws.iam#iamAction-trait>` on the operation. See `the handlers
section`_ in the CloudFormation Resource Schemas documentation for more
information.

.. code-block:: json

Expand All @@ -312,7 +313,8 @@ disableHandlerPermissionGeneration (``boolean``)
CloudFormation Resource Schema handlers determine what provisioning actions
can be performed for the resource. The handlers utilized by CloudFormation
align with some :ref:`lifecycle-operations`. These operations can also
define other permission actions required to invoke them with the :ref:`aws.iam#requiredActions-trait`.
define other permission actions required to invoke them with the :ref:`requiredActions
property of the aws.iam#iamAction trait <aws.iam#iamAction-trait>`

When handler permission generation is enabled, all the actions required to
invoke the operations related to the handler, including the actions for the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@
import software.amazon.smithy.aws.cloudformation.schema.model.Handler;
import software.amazon.smithy.aws.cloudformation.schema.model.ResourceSchema;
import software.amazon.smithy.aws.iam.traits.IamActionTrait;
import software.amazon.smithy.aws.iam.traits.RequiredActionsTrait;
import software.amazon.smithy.aws.traits.ServiceTrait;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ResourceShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.NoReplaceTrait;
import software.amazon.smithy.utils.ListUtils;
import software.amazon.smithy.utils.SetUtils;
import software.amazon.smithy.utils.SmithyInternalApi;

Expand Down Expand Up @@ -114,11 +112,7 @@ private Set<String> getPermissionsEntriesForOperation(Model model, ServiceShape
permissionsEntries.add(operationActionName);

// Add all the other required actions for the operation.
permissionsEntries.addAll(operation.getTrait(IamActionTrait.class)
.map(IamActionTrait::getRequiredActions)
.orElseGet(() -> operation.getTrait(RequiredActionsTrait.class)
.map(RequiredActionsTrait::getValues)
.orElse(ListUtils.of())));
permissionsEntries.addAll(IamActionTrait.resolveRequiredActions(operation));
return permissionsEntries;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.StringTrait;

/**
* Use the {@code @iamAction} trait's {@code name} property instead.
*
* @deprecated As of release 1.44.0, replaced by {@link IamActionTrait#resolveActionName}.
*/
@Deprecated
public final class ActionNameTrait extends StringTrait {

public static final ShapeId ID = ShapeId.from("aws.iam#actionName");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
import software.amazon.smithy.model.traits.StringTrait;

/**
* Defines the description of what providing access to an operation entails.
* Use the {@code @iamAction} trait's {@code documentation} property instead.
*
* @deprecated As of release 1.44.0, replaced by {@link IamActionTrait#resolveActionDocumentation}.
*/
@Deprecated
public final class ActionPermissionDescriptionTrait extends StringTrait {
public static final ShapeId ID = ShapeId.from("aws.iam#actionPermissionDescription");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@
import java.util.Optional;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.NodeMapper;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.AbstractTrait;
import software.amazon.smithy.model.traits.AbstractTraitBuilder;
import software.amazon.smithy.model.traits.DocumentationTrait;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.utils.BuilderRef;
import software.amazon.smithy.utils.FunctionalUtils;
import software.amazon.smithy.utils.ListUtils;
import software.amazon.smithy.utils.SmithyBuilder;
import software.amazon.smithy.utils.ToSmithyBuilder;

Expand Down Expand Up @@ -60,6 +64,27 @@ public Optional<String> getName() {
return Optional.ofNullable(name);
}

/**
* Resolves the IAM action name for the given operation. Uses the following
* resolution order:
*
* <ol>
* <li>Value of the {@code @iamAction} trait's {@code name} property</li>
* <li>Value of the {@code @actionName} trait</li>
* <li>The operation's name</li>
* </ol>
*
* @param operation the operation to resolve a name for.
* @return The resolved action name.
*/
@SuppressWarnings("deprecation")
public static String resolveActionName(OperationShape operation) {
return operation.getTrait(IamActionTrait.class).flatMap(IamActionTrait::getName)
.orElseGet(() -> operation.getTrait(ActionNameTrait.class)
.map(ActionNameTrait::getValue)
.orElseGet(() -> operation.getId().getName()));
}

/**
* Gets the description of what granting the user permission to
* invoke an operation would entail.
Expand All @@ -70,6 +95,29 @@ public Optional<String> getDocumentation() {
return Optional.ofNullable(documentation);
}

/**
* Resolves the IAM action documentation for the given operation. Uses the following
* resolution order:
*
* <ol>
* <li>Value of the {@code @iamAction} trait's {@code documentation} property</li>
* <li>Value of the {@code @actionPermissionDescription} trait</li>
* <li>Value of the {@code @documentation} trait</li>
* </ol>
*
* @param operation the operation to resolve documentation for.
* @return The resolved action documentation.
*/
@SuppressWarnings("deprecation")
public static String resolveActionDocumentation(OperationShape operation) {
return operation.getTrait(IamActionTrait.class).flatMap(IamActionTrait::getDocumentation)
.orElseGet(() -> operation.getTrait(ActionPermissionDescriptionTrait.class)
.map(ActionPermissionDescriptionTrait::getValue)
.orElseGet(() -> operation.getTrait(DocumentationTrait.class)
.map(DocumentationTrait::getValue)
.orElse(null)));
}

/**
* Gets the relative URL path for the action within a set of
* IAM-related documentation.
Expand All @@ -90,6 +138,28 @@ public List<String> getRequiredActions() {
return requiredActions;
}

/**
* Resolves the IAM action required actions for the given operation. Uses the following
* resolution order:
*
* <ol>
* <li>Value of the {@code @iamAction} trait's {@code requiredActions} property</li>
* <li>Value of the {@code @requiredActions} trait</li>
* <li>An empty list.</li>
* </ol>
*
* @param operation the operation to resolve required actions for.
* @return The resolved required actions.
*/
@SuppressWarnings("deprecation")
public static List<String> resolveRequiredActions(OperationShape operation) {
return operation.getTrait(IamActionTrait.class).map(IamActionTrait::getRequiredActions)
.filter(FunctionalUtils.not(List::isEmpty))
.orElseGet(() -> operation.getTrait(RequiredActionsTrait.class)
.map(RequiredActionsTrait::getValues)
.orElse(ListUtils.of()));
}

/**
* Gets the resources an IAM action can be authorized against.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public List<ValidationEvent> validate(Model model) {
return events;
}

@SuppressWarnings("deprecation")
private List<ValidationEvent> validateDuplicateTraits(OperationShape operation, IamActionTrait trait) {
List<ValidationEvent> events = new ArrayList<>();
if (operation.hasTrait(ActionNameTrait.ID) && trait.getName().isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.Optional;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.NodeMapper;
import software.amazon.smithy.model.shapes.ResourceShape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.AbstractTrait;
import software.amazon.smithy.model.traits.AbstractTraitBuilder;
Expand Down Expand Up @@ -52,6 +53,26 @@ public Optional<String> getName() {
return Optional.ofNullable(name);
}



/**
* Resolves the IAM resource name for the given resource. Uses the following
* resolution order:
*
* <ol>
* <li>Value of the {@code @iamResource} trait's {@code name} property</li>
* <li>The resource's name</li>
* </ol>
*
* @param resource the resource to resolve a name for.
* @return The resolved resource name.
*/
public static String resolveResourceName(ResourceShape resource) {
return resource.getTrait(IamResourceTrait.class)
.flatMap(IamResourceTrait::getName)
.orElse(resource.getId().getName());
}

/**
* Get the relative URL path that defines more information about the resource
* within a set of IAM-related documentation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
import software.amazon.smithy.model.traits.StringListTrait;
import software.amazon.smithy.utils.ToSmithyBuilder;

/**
* Use the {@code @iamAction} trait's {@code requiredActions} property instead.
*
* @deprecated As of release 1.44.0, replaced by {@link IamActionTrait#resolveRequiredActions}.
*/
@Deprecated
public final class RequiredActionsTrait extends StringListTrait implements ToSmithyBuilder<RequiredActionsTrait> {
public static final ShapeId ID = ShapeId.from("aws.iam#requiredActions");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@

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

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

import org.junit.jupiter.api.Test;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

public class ActionNameTraitTest {
@Test
@SuppressWarnings("deprecation")
public void loadsFromModel() {
Model result = Model.assembler()
.discoverModels(getClass().getClassLoader())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

public class ActionPermissionDescriptionTraitTest {
@Test
@SuppressWarnings("deprecation")
public void createsTrait() {
Node node = Node.from("Foo baz bar");
TraitFactory provider = TraitFactory.createServiceFactory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.DocumentationTrait;

public class IamActionTraitTest {
private static final ShapeId ID = ShapeId.from("smithy.example#Foo");

@Test
public void loadsFromModel() {
Model result = Model.assembler()
Expand All @@ -35,7 +41,7 @@ public void loadsFromModel() {
.assemble()
.unwrap();

Shape fooOperation = result.expectShape(ShapeId.from("smithy.example#Foo"));
Shape fooOperation = result.expectShape(ID);

assertTrue(fooOperation.hasTrait(IamActionTrait.class));
IamActionTrait trait = fooOperation.expectTrait(IamActionTrait.class);
Expand All @@ -54,4 +60,99 @@ public void loadsFromModel() {
assertTrue(actionResources.getOptional().containsKey("baz"));
assertTrue(actionResources.getOptional().get("baz").getConditionKeys().isEmpty());
}

@Test
@SuppressWarnings("deprecation")
public void prefersIamActionTraitName() {
OperationShape op = OperationShape.builder().id(ID)
.addTrait(IamActionTrait.builder().name("ThisOne").build())
.addTrait(new ActionNameTrait("Unused"))
.build();

assertEquals("ThisOne", IamActionTrait.resolveActionName(op));
}

@Test
@SuppressWarnings("deprecation")
public void usesDeprecatedActionNameTrait() {
OperationShape op = OperationShape.builder().id(ID)
.addTrait(new ActionNameTrait("ThisOne"))
.build();

assertEquals("ThisOne", IamActionTrait.resolveActionName(op));
}

@Test
public void defaultsToOperationName() {
OperationShape op = OperationShape.builder().id(ID).build();

assertEquals("Foo", IamActionTrait.resolveActionName(op));
}

@Test
@SuppressWarnings("deprecation")
public void prefersIamActionTraitDocumentation() {
OperationShape op = OperationShape.builder().id(ID)
.addTrait(IamActionTrait.builder().documentation("ThisOne").build())
.addTrait(new ActionPermissionDescriptionTrait("Unused"))
.addTrait(new DocumentationTrait("Unused"))
.build();

assertEquals("ThisOne", IamActionTrait.resolveActionDocumentation(op));
}

@Test
@SuppressWarnings("deprecation")
public void usesDeprecatedActionPermissionDescriptionTrait() {
OperationShape op = OperationShape.builder().id(ID)
.addTrait(new ActionPermissionDescriptionTrait("ThisOne"))
.addTrait(new DocumentationTrait("Unused"))
.build();

assertEquals("ThisOne", IamActionTrait.resolveActionDocumentation(op));
}

@Test
public void usesDocumentationTrait() {
OperationShape op = OperationShape.builder().id(ID)
.addTrait(new DocumentationTrait("ThisOne"))
.build();

assertEquals("ThisOne", IamActionTrait.resolveActionDocumentation(op));
}

@Test
public void defaultsToNoDocumentation() {
OperationShape op = OperationShape.builder().id(ID).build();

assertNull(IamActionTrait.resolveActionDocumentation(op));
}

@Test
@SuppressWarnings("deprecation")
public void prefersIamActionTraitRequiredActions() {
OperationShape op = OperationShape.builder().id(ID)
.addTrait(IamActionTrait.builder().addRequiredAction("ThisOne").build())
.addTrait(new ActionNameTrait("Unused"))
.build();

assertThat(IamActionTrait.resolveRequiredActions(op), contains("ThisOne"));
}

@Test
@SuppressWarnings("deprecation")
public void usesDeprecatedRequiredActionsTrait() {
OperationShape op = OperationShape.builder().id(ID)
.addTrait(RequiredActionsTrait.builder().addValue("ThisOne").build())
.build();

assertThat(IamActionTrait.resolveRequiredActions(op), contains("ThisOne"));
}

@Test
public void defaultsToEmptyRequiredActions() {
OperationShape op = OperationShape.builder().id(ID).build();

assertThat(IamActionTrait.resolveRequiredActions(op), empty());
}
}
Loading
Loading