Skip to content

Commit

Permalink
Add trait requirements to auth scheme too
Browse files Browse the repository at this point in the history
  • Loading branch information
mtdowling committed Feb 24, 2020
1 parent e235ad4 commit 6e1274c
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 15 deletions.
22 changes: 18 additions & 4 deletions docs/source/spec/core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4740,7 +4740,7 @@ The following example defines a service that supports both the hypothetical
"type": "structure",
"traits": {
"smithy.api#documentation": "An example JSON protocol."
"smithy.api#protocolDefinition": true,
"smithy.api#protocolDefinition": {},
"smithy.api#trait": {
"selector": "service"
}
Expand All @@ -4750,7 +4750,7 @@ The following example defines a service that supports both the hypothetical
"type": "structure",
"traits": {
"smithy.api#documentation": "An example JSON protocol."
"smithy.api#protocolDefinition": true,
"smithy.api#protocolDefinition": {},
"smithy.api#trait": {
"selector": "service"
}
Expand Down Expand Up @@ -4942,7 +4942,21 @@ Summary
Trait selector
``[trait|trait]``
Value type
Annotation trait.
An object with the following properties:

.. list-table::
:header-rows: 1
:widths: 10 23 67

* - Property
- Type
- Description
* - traits
- [:ref:`shape-id`]
- List of shape IDs that auth scheme implementations MUST
understand in order to successfully use the scheme. Each shape
MUST exist and MUST be a trait. Code generators SHOULD ensure
that they support each listed trait.

Every operation in the closure of a service is expected to support the
authentication schemes applied to a service unless the service or operation
Expand Down Expand Up @@ -4984,7 +4998,7 @@ and the hypothetical ``fooExample`` authentication scheme.
"smithy.example#fooExample": {
"type": "structure",
"traits": {
"smithy.api#authDefinition": true,
"smithy.api#authDefinition": {},
"smithy.api#trait": {
"selector": "service"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
"smithy.api#trait": {
"selector": "service"
},
"smithy.api#authDefinition": true,
"smithy.api#authDefinition": {
"traits": [
"aws.auth#unsignedPayload"
]
},
"smithy.api#documentation": "Signature Version 4 is the process to add authentication information to AWS requests sent by HTTP. For security, most requests to AWS must be signed with an access key, which consists of an access key ID and secret access key. These two keys are commonly referred to as your security credentials.",
"smithy.api#externalDocumentation": "https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
},
"smithy.api#protocolDefinition": {
"traits": [
"aws.api#ec2QueryName",
"aws.protocols#ec2QueryName",
"smithy.api#xmlAttribute",
"smithy.api#xmlFlattened",
"smithy.api#xmlName",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,96 @@

package software.amazon.smithy.model.traits;

import software.amazon.smithy.model.SourceLocation;
import java.util.ArrayList;
import java.util.List;
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.shapes.ShapeId;
import software.amazon.smithy.utils.ListUtils;
import software.amazon.smithy.utils.ToSmithyBuilder;

/**
* This trait is attached to another trait to define an auth scheme.
*/
public final class AuthDefinitionTrait extends BooleanTrait {
public final class AuthDefinitionTrait extends AbstractTrait implements ToSmithyBuilder<AuthDefinitionTrait> {

public static final ShapeId ID = ShapeId.from("smithy.api#authDefinition");
private final List<ShapeId> traits;

public AuthDefinitionTrait(SourceLocation sourceLocation) {
super(ID, sourceLocation);
public AuthDefinitionTrait(Builder builder) {
super(ID, builder.getSourceLocation());
traits = ListUtils.copyOf(builder.traits);
}

public AuthDefinitionTrait() {
this(SourceLocation.NONE);
/**
* Gets the list of shape IDs that auth implementations must know about
* in order to successfully utilize the auth scheme.
*
* @return Returns the auth traits.
*/
public List<ShapeId> getTraits() {
return traits;
}

public static final class Provider extends BooleanTrait.Provider<AuthDefinitionTrait> {
public static Builder builder() {
return new Builder();
}

@Override
protected Node createNode() {
if (traits.isEmpty()) {
return Node.objectNode();
}

ArrayNode ids = traits.stream()
.map(ShapeId::toString)
.map(Node::from)
.collect(ArrayNode.collect());

return Node.objectNode().withMember("traits", ids);
}

@Override
public Builder toBuilder() {
return builder().sourceLocation(getSourceLocation()).traits(traits);
}

public static final class Provider extends AbstractTrait.Provider {
public Provider() {
super(ID, AuthDefinitionTrait::new);
super(ID);
}

@Override
public AuthDefinitionTrait createTrait(ShapeId target, Node value) {
Builder builder = builder().sourceLocation(value);
ObjectNode objectNode = value.expectObjectNode();
objectNode.getArrayMember("traits").ifPresent(traits -> {
for (String string : Node.loadArrayOfString("traits", traits)) {
builder.addTrait(ShapeId.from(string));
}
});
return builder.build();
}
}

public static final class Builder extends AbstractTraitBuilder<AuthDefinitionTrait, Builder> {
private final List<ShapeId> traits = new ArrayList<>();

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

public Builder traits(List<ShapeId> traits) {
this.traits.clear();
this.traits.addAll(traits);
return this;
}

public Builder addTrait(ShapeId trait) {
traits.add(trait);
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ string TraitShapeId
/// shapes, must be a structure, and must have the `trait` trait.
@trait(selector: "structure[trait|trait]")
@tags(["diff.error.add", "diff.error.remove"])
structure authDefinition {}
structure authDefinition {
/// Defines a list of traits that auth implementations must
/// understand in order to successfully use the scheme.
traits: TraitShapeIdList,
}

/// Enables HTTP Basic Authentication as defined in RFC 2617
/// on a service or operation.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package software.amazon.smithy.model.traits;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Optional;
import org.junit.jupiter.api.Test;
import software.amazon.smithy.model.node.ArrayNode;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.shapes.ShapeId;

public class AuthDefinitionTraitTest {
@Test
public void loadsTrait() {
TraitFactory provider = TraitFactory.createServiceFactory();
ArrayNode values = Node.fromStrings(
JsonNameTrait.ID.toString(),
XmlNameTrait.ID.toString());
Node node = Node.objectNode().withMember("traits", values);
Optional<Trait> trait = provider.createTrait(
ShapeId.from("smithy.api#authDefinition"),
ShapeId.from("ns.qux#foo"),
node);

assertTrue(trait.isPresent());
assertThat(trait.get(), instanceOf(AuthDefinitionTrait.class));
AuthDefinitionTrait authDefinitionTrait = (AuthDefinitionTrait) trait.get();
assertThat(authDefinitionTrait.getTraits(), containsInAnyOrder(
JsonNameTrait.ID, XmlNameTrait.ID));
assertThat(authDefinitionTrait.toNode(), equalTo(node));
assertThat(authDefinitionTrait.toBuilder().build(), equalTo(authDefinitionTrait));
}
}

0 comments on commit 6e1274c

Please sign in to comment.