Skip to content

Added scheme property to HttpApiKeyAuth trait #893

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

Merged
merged 17 commits into from
Sep 1, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
11 changes: 11 additions & 0 deletions docs/source/1.0/spec/core/auth-traits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ properties:
- ``string``
- **Required**. Defines the location of where the key is serialized.
This value can be set to ``header`` or ``query``.
* - scheme
- ``string``
- Defines the ApiKey scheme to set on the intended Authoriation header

The following example defines a service that accepts an API key in the "X-Api-Key"
HTTP header:
Expand All @@ -228,6 +231,14 @@ HTTP header:
}


.. code-block:: smithy

@httpApiKeyAuth(scheme: "ApiKey", name: "Authorization", in: "header")
service WeatherService {
version: "2017-02-11",
}


.. smithy-trait:: smithy.api#optionalAuth
.. _optionalAuth-trait:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[ERROR] ns.foo#MyService: httpApiKeyAuth trait must use in="header" when scheme is specified. | HttpApiKeyAuthTrait
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"smithy": "1.0",
"shapes": {
"ns.foo#MyService": {
"type": "service",
"version": "2017-01-17",
"operations": [
{
"target": "ns.foo#A"
}
],
"traits": {
"smithy.api#httpApiKeyAuth": {
"scheme": "Baz",
"name": "ApiKeyName",
"in": "query"
}
}
},
"ns.foo#A": {
"type": "operation",
"input": {
"target": "ns.foo#AInput"
},
"output": {
"target": "ns.foo#AOutput"
},
"traits": {
"smithy.api#readonly": { }
}
},
"ns.foo#AInput": {
"type": "structure"
},
"ns.foo#AOutput": {
"type": "structure"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

package software.amazon.smithy.model.traits;

import java.util.Optional;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.shapes.ShapeId;
Expand All @@ -29,15 +30,21 @@ public final class HttpApiKeyAuthTrait extends AbstractTrait implements ToSmithy

public static final ShapeId ID = ShapeId.from("smithy.api#httpApiKeyAuth");

private final String scheme;
private final String name;
private final Location in;

private HttpApiKeyAuthTrait(Builder builder) {
super(ID, builder.getSourceLocation());
scheme = builder.scheme;
name = SmithyBuilder.requiredState("name", builder.name);
in = SmithyBuilder.requiredState("in", builder.in);
}

public Optional<String> getScheme() {
return Optional.ofNullable(scheme);
}

public String getName() {
return name;
}
Expand All @@ -50,17 +57,21 @@ public Location getIn() {
public Builder toBuilder() {
return builder()
.sourceLocation(getSourceLocation())
.scheme(getScheme().orElse(null))
.name(getName())
.in(getIn());
}

@Override
protected Node createNode() {
return Node.objectNodeBuilder()
ObjectNode.Builder builder = Node.objectNodeBuilder()
.sourceLocation(getSourceLocation())
.withMember("name", getName())
.withMember("in", getIn().toString())
.build();
.withMember("in", getIn().toString());
getScheme().ifPresent(scheme -> {
builder.withMember("scheme", getScheme().get());
});
return builder.build();
}

public static Builder builder() {
Expand Down Expand Up @@ -101,13 +112,15 @@ public Provider() {
public Trait createTrait(ShapeId target, Node value) {
ObjectNode objectNode = value.expectObjectNode();
Builder builder = builder().sourceLocation(value.getSourceLocation());
builder.scheme(objectNode.getStringMemberOrDefault("scheme", null));
builder.name(objectNode.expectStringMember("name").getValue());
builder.in(Location.from(objectNode.expectStringMember("in").expectOneOf("header", "query")));
return builder.build();
}
}

public static final class Builder extends AbstractTraitBuilder<HttpApiKeyAuthTrait, Builder> {
private String scheme;
private String name;
private Location in;

Expand All @@ -118,6 +131,11 @@ public HttpApiKeyAuthTrait build() {
return new HttpApiKeyAuthTrait(this);
}

public Builder scheme(String scheme) {
this.scheme = scheme;
return this;
}

public Builder name(String name) {
this.name = name;
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2019 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.model.validation.validators;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.traits.HttpApiKeyAuthTrait;
import software.amazon.smithy.model.validation.AbstractValidator;
import software.amazon.smithy.model.validation.ValidationEvent;

/**
* Validates that if an HttpApiKeyAuth trait's scheme field is present then
* the 'in' field must specify "header".
*/
public final class HttpApiKeyAuthTraitValidator extends AbstractValidator {
@Override
public List<ValidationEvent> validate(Model model) {
Set<ServiceShape> serviceShapesWithTrait = model.getServiceShapesWithTrait(HttpApiKeyAuthTrait.class);
List<ValidationEvent> events = new ArrayList<>();

for (ServiceShape serviceShape:serviceShapesWithTrait) {
HttpApiKeyAuthTrait trait = serviceShape.expectTrait(HttpApiKeyAuthTrait.class);
trait.getScheme().ifPresent(scheme -> {
if (trait.getIn() != HttpApiKeyAuthTrait.Location.HEADER) {
events.add(error(serviceShape,
"httpApiKeyAuth trait must use in=\"header\" when scheme is specified."));
}
});
}

return events;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ software.amazon.smithy.model.validation.validators.EventPayloadTraitValidator
software.amazon.smithy.model.validation.validators.ExamplesTraitValidator
software.amazon.smithy.model.validation.validators.ExclusiveStructureMemberTraitValidator
software.amazon.smithy.model.validation.validators.HostLabelTraitValidator
software.amazon.smithy.model.validation.validators.HttpApiKeyAuthTraitValidator
software.amazon.smithy.model.validation.validators.HttpBindingsMissingValidator
software.amazon.smithy.model.validation.validators.HttpChecksumTraitValidator
software.amazon.smithy.model.validation.validators.HttpHeaderTraitValidator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ structure httpApiKeyAuth {
/// can be set to `"header"` or `"query"`.
@required
in: HttpApiKeyLocations,

/// Defines an ApiKey scheme when Authorization header is used
scheme: NonEmptyString,
}

@private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Optional;
Expand All @@ -39,6 +40,7 @@ public void loadsTraitWithHeader() {
assertThat(trait.get(), instanceOf(HttpApiKeyAuthTrait.class));
HttpApiKeyAuthTrait auth = (HttpApiKeyAuthTrait) trait.get();

assertThat(auth.getScheme().isPresent(), is(Boolean.FALSE));
assertThat(auth.getName(), equalTo("X-Foo"));
assertThat(auth.getIn(), equalTo(HttpApiKeyAuthTrait.Location.HEADER));
assertThat(auth.toNode(), equalTo(node));
Expand All @@ -57,6 +59,47 @@ public void loadsTraitWithQuery() {
assertThat(trait.get(), instanceOf(HttpApiKeyAuthTrait.class));
HttpApiKeyAuthTrait auth = (HttpApiKeyAuthTrait) trait.get();

assertThat(auth.getScheme().isPresent(), is(Boolean.FALSE));
assertThat(auth.getName(), equalTo("blerg"));
assertThat(auth.getIn(), equalTo(HttpApiKeyAuthTrait.Location.QUERY));
assertThat(auth.toNode(), equalTo(node));
assertThat(auth.toBuilder().build(), equalTo(auth));
}

@Test
public void loadsTraitWithHeaderAndScheme() {
TraitFactory provider = TraitFactory.createServiceFactory();
ObjectNode node = Node.objectNode()
.withMember("scheme", "fenty")
.withMember("name", "X-Foo")
.withMember("in", "header");
Optional<Trait> trait = provider.createTrait(
HttpApiKeyAuthTrait.ID, ShapeId.from("ns.qux#foo"), node);
assertTrue(trait.isPresent());
assertThat(trait.get(), instanceOf(HttpApiKeyAuthTrait.class));
HttpApiKeyAuthTrait auth = (HttpApiKeyAuthTrait) trait.get();

assertThat(auth.getScheme().get(), equalTo("fenty"));
assertThat(auth.getName(), equalTo("X-Foo"));
assertThat(auth.getIn(), equalTo(HttpApiKeyAuthTrait.Location.HEADER));
assertThat(auth.toNode(), equalTo(node));
assertThat(auth.toBuilder().build(), equalTo(auth));
}

@Test
public void loadsTraitWithQueryAndScheme() {
TraitFactory provider = TraitFactory.createServiceFactory();
ObjectNode node = Node.objectNode()
.withMember("scheme", "fenty")
.withMember("name", "blerg")
.withMember("in", "query");
Optional<Trait> trait = provider.createTrait(
HttpApiKeyAuthTrait.ID, ShapeId.from("ns.qux#foo"), node);
assertTrue(trait.isPresent());
assertThat(trait.get(), instanceOf(HttpApiKeyAuthTrait.class));
HttpApiKeyAuthTrait auth = (HttpApiKeyAuthTrait) trait.get();

assertThat(auth.getScheme().get(), equalTo("fenty"));
assertThat(auth.getName(), equalTo("blerg"));
assertThat(auth.getIn(), equalTo(HttpApiKeyAuthTrait.Location.QUERY));
assertThat(auth.toNode(), equalTo(node));
Expand Down