Skip to content

Commit

Permalink
Restrict httpPayload targets for AWS protocols
Browse files Browse the repository at this point in the history
The AWS protocols do not currently support http payloads that target
sets, lists, or maps. This adds validators to ensure that such
bindings do not get used.
  • Loading branch information
JordonPhillips committed Jan 5, 2021
1 parent 2ba1e6a commit 63b5277
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.HttpBinding;
import software.amazon.smithy.model.knowledge.HttpBinding.Location;
import software.amazon.smithy.model.knowledge.HttpBindingIndex;
import software.amazon.smithy.model.knowledge.ServiceIndex;
import software.amazon.smithy.model.knowledge.TopDownIndex;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.ShapeType;
import software.amazon.smithy.model.shapes.ToShapeId;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.model.validation.AbstractValidator;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.utils.SetUtils;
import software.amazon.smithy.utils.SmithyInternalApi;

/**
* Ensures that the http payload trait is only bound to structures, unions,
* documents, blobs, or strings for AWS protocols.
*/
@SmithyInternalApi
public final class ProtocolHttpPayloadValidator extends AbstractValidator {
private static final Set<ShapeType> VALID_HTTP_PAYLOAD_TYPES = SetUtils.of(
ShapeType.STRUCTURE, ShapeType.UNION, ShapeType.DOCUMENT, ShapeType.BLOB, ShapeType.STRING
);

@Override
public List<ValidationEvent> validate(Model model) {
ServiceIndex serviceIndex = ServiceIndex.of(model);
HttpBindingIndex bindingIndex = HttpBindingIndex.of(model);
TopDownIndex topDownIndex = TopDownIndex.of(model);
return model.shapes(ServiceShape.class)
.filter(service -> usesAwsProtocol(service, serviceIndex))
.flatMap(service -> validateService(model, service, bindingIndex, topDownIndex).stream())
.collect(Collectors.toList());
}

private boolean usesAwsProtocol(ServiceShape service, ServiceIndex index) {
for (Trait protocol : index.getProtocols(service).values()) {
if (protocol instanceof AwsProtocolTrait) {
return true;
}
}
return false;
}

private List<ValidationEvent> validateService(
Model model,
ServiceShape service,
HttpBindingIndex bindingIndex,
TopDownIndex topDownIndex
) {
List<ValidationEvent> events = new ArrayList<>();

for (OperationShape operation : topDownIndex.getContainedOperations(service)) {
List<HttpBinding> requestBindings = bindingIndex.getRequestBindings(operation, Location.PAYLOAD);
validateBindings(model, requestBindings).ifPresent(events::add);

List<HttpBinding> responseBindings = bindingIndex.getResponseBindings(operation, Location.PAYLOAD);
validateBindings(model, responseBindings).ifPresent(events::add);

for (ShapeId error : operation.getErrors()) {
List<HttpBinding> errorBindings = bindingIndex.getResponseBindings(error, Location.PAYLOAD);
validateBindings(model, errorBindings).ifPresent(events::add);
}
}

return events;
}

private Optional<ValidationEvent> validateBindings(Model model, Collection<HttpBinding> payloadBindings) {
for (HttpBinding binding : payloadBindings) {
if (!payloadBoundToValidType(model, binding.getMember().getTarget())) {
return Optional.of(error(binding.getMember(), "AWS Protocols do not support applying the httpPayload "
+ "trait to members that target sets, lists, or maps."));
}
}
return Optional.empty();
}

private boolean payloadBoundToValidType(Model model, ToShapeId payloadShape) {
return model.getShape(payloadShape.toShapeId())
.map(shape -> VALID_HTTP_PAYLOAD_TYPES.contains(shape.getType()))
.orElse(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ software.amazon.smithy.aws.traits.SdkServiceIdValidator
software.amazon.smithy.aws.traits.clientendpointdiscovery.ClientEndpointDiscoveryValidator
software.amazon.smithy.aws.traits.protocols.ProtocolHttpValidator
software.amazon.smithy.aws.traits.EventSourceValidator
software.amazon.smithy.aws.traits.protocols.ProtocolHttpPayloadValidator
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[ERROR] smithy.example#InvalidBindingInput$listBinding: AWS Protocols do not support applying the httpPayload trait to members that target sets, lists, or maps. | ProtocolHttpPayload
[ERROR] smithy.example#InvalidBindingOutput$mapBinding: AWS Protocols do not support applying the httpPayload trait to members that target sets, lists, or maps. | ProtocolHttpPayload
[ERROR] smithy.example#InvalidBindingError$setBinding: AWS Protocols do not support applying the httpPayload trait to members that target sets, lists, or maps. | ProtocolHttpPayload
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// AWS protocols do not currently support applying the http payload trait to
// sets, lists, or maps.

namespace smithy.example

use aws.protocols#restJson1
use smithy.api#http
use smithy.api#httpPayload

@restJson1
service InvalidExample {
version: "2020-12-29",
operations: [InvalidBindingOperation],
}

@http(method: "POST", uri: "/invalid-payload")
operation InvalidBindingOperation {
input: InvalidBindingInput,
output: InvalidBindingOutput,
errors: [InvalidBindingError],
}

structure InvalidBindingInput {
@httpPayload
listBinding: StringList,
}

structure InvalidBindingOutput {
@httpPayload
mapBinding: StringMap,
}

@error("client")
structure InvalidBindingError {
@httpPayload
setBinding: StringSet
}

list StringList {
member: String
}

set StringSet {
member: String
}

map StringMap {
key: String,
value: String,
}

0 comments on commit 63b5277

Please sign in to comment.