Skip to content

Commit dc9eb9f

Browse files
committed
Use sets for OpenAPI security settings
This commit updates the OpenAPI document and Operation object builders to use sets for storing their security settings instead of lists. Each entry in these collections can be safely made unique, as duplicates would represent an "A or A" posture. Having multiple entries can cause issues with consuming the output document.
1 parent bd14b65 commit dc9eb9f

File tree

4 files changed

+128
-12
lines changed

4 files changed

+128
-12
lines changed

smithy-openapi/src/main/java/software/amazon/smithy/openapi/model/OpenApi.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
import java.util.ArrayList;
1919
import java.util.Collection;
2020
import java.util.Comparator;
21+
import java.util.LinkedHashSet;
2122
import java.util.List;
2223
import java.util.Map;
2324
import java.util.Optional;
25+
import java.util.Set;
2426
import java.util.TreeMap;
2527
import software.amazon.smithy.model.node.ArrayNode;
2628
import software.amazon.smithy.model.node.Node;
@@ -143,7 +145,9 @@ public static final class Builder extends Component.Builder<Builder, OpenApi> {
143145
private final List<ServerObject> servers = new ArrayList<>();
144146
private Map<String, PathItem> paths = new TreeMap<>();
145147
private ComponentsObject components;
146-
private final List<Map<String, List<String>>> security = new ArrayList<>();
148+
// Use a set for security as duplicate entries are unnecessary (effectively
149+
// represent an "A or A" security posture) and can cause downstream issues.
150+
private final Set<Map<String, List<String>>> security = new LinkedHashSet<>();
147151
private final List<TagObject> tags = new ArrayList<>();
148152
private ExternalDocumentation externalDocs;
149153

smithy-openapi/src/main/java/software/amazon/smithy/openapi/model/OperationObject.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
import java.util.Collection;
2020
import java.util.Collections;
2121
import java.util.Comparator;
22+
import java.util.LinkedHashSet;
2223
import java.util.List;
2324
import java.util.Map;
2425
import java.util.Optional;
26+
import java.util.Set;
2527
import java.util.TreeMap;
2628
import software.amazon.smithy.model.node.ArrayNode;
2729
import software.amazon.smithy.model.node.Node;
@@ -189,7 +191,9 @@ public static final class Builder extends Component.Builder<Builder, OperationOb
189191
private final List<ParameterObject> parameters = new ArrayList<>();
190192
private final Map<String, ResponseObject> responses = new TreeMap<>();
191193
private final Map<String, CallbackObject> callbacks = new TreeMap<>();
192-
private List<Map<String, List<String>>> security;
194+
// Use a set for security as duplicate entries are unnecessary (effectively
195+
// represent an "A or A" security posture) and can cause downstream issues.
196+
private Set<Map<String, List<String>>> security;
193197
private final List<ServerObject> servers = new ArrayList<>();
194198
private String summary;
195199
private String description;
@@ -280,14 +284,14 @@ public Builder deprecated(boolean deprecated) {
280284
}
281285

282286
public Builder security(Collection<Map<String, List<String>>> security) {
283-
this.security = new ArrayList<>();
287+
this.security = new LinkedHashSet<>();
284288
this.security.addAll(security);
285289
return this;
286290
}
287291

288292
public Builder addSecurity(Map<String, List<String>> security) {
289293
if (this.security == null) {
290-
this.security = new ArrayList<>();
294+
this.security = new LinkedHashSet<>();
291295
}
292296
this.security.add(security);
293297
return this;

smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverterTest.java

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static org.hamcrest.Matchers.empty;
2222
import static org.hamcrest.Matchers.equalTo;
2323
import static org.hamcrest.Matchers.not;
24+
import static org.junit.jupiter.api.Assertions.assertFalse;
2425

2526
import java.util.Collections;
2627
import java.util.List;
@@ -254,14 +255,14 @@ public void protocolsCanOmitOperations() {
254255
.convert(model);
255256

256257
for (PathItem pathItem : result.getPaths().values()) {
257-
Assertions.assertFalse(pathItem.getGet().isPresent());
258-
Assertions.assertFalse(pathItem.getHead().isPresent());
259-
Assertions.assertFalse(pathItem.getDelete().isPresent());
260-
Assertions.assertFalse(pathItem.getPatch().isPresent());
261-
Assertions.assertFalse(pathItem.getPost().isPresent());
262-
Assertions.assertFalse(pathItem.getPut().isPresent());
263-
Assertions.assertFalse(pathItem.getTrace().isPresent());
264-
Assertions.assertFalse(pathItem.getOptions().isPresent());
258+
assertFalse(pathItem.getGet().isPresent());
259+
assertFalse(pathItem.getHead().isPresent());
260+
assertFalse(pathItem.getDelete().isPresent());
261+
assertFalse(pathItem.getPatch().isPresent());
262+
assertFalse(pathItem.getPost().isPresent());
263+
assertFalse(pathItem.getPut().isPresent());
264+
assertFalse(pathItem.getTrace().isPresent());
265+
assertFalse(pathItem.getOptions().isPresent());
265266
}
266267
}
267268

@@ -353,6 +354,34 @@ public void canChangeSecurityRequirementName() {
353354
assertThat(result.getPaths().get("/2").getGet().get().getSecurity().get().get(0).keySet(), contains("foo_baz"));
354355
}
355356

357+
@Test
358+
public void consolidatesSameSecurityRequirements() {
359+
// This service model has multiple auth types throughout it for both
360+
// the top level and operation level security setting. Validate that,
361+
// after they're set to use the same name, they're consolidated for
362+
// being the same.
363+
Model model = Model.assembler()
364+
.addImport(getClass().getResource("consolidates-security-service.json"))
365+
.discoverModels()
366+
.assemble()
367+
.unwrap();
368+
OpenApiConfig config = new OpenApiConfig();
369+
config.setService(ShapeId.from("smithy.example#Service"));
370+
OpenApi result = OpenApiConverter.create()
371+
.addOpenApiMapper(new ConstantSecurity())
372+
.config(config)
373+
.convert(model);
374+
375+
assertThat(result.getSecurity().size(), equalTo(1));
376+
assertThat(result.getSecurity().get(0).keySet(), contains("foo_baz"));
377+
// This security matches the service, so isn't applied.
378+
assertFalse(result.getPaths().get("/1").getGet().get().getSecurity().isPresent());
379+
assertThat(result.getPaths().get("/2").getGet().get().getSecurity().get().size(), equalTo(1));
380+
assertThat(result.getPaths().get("/2").getGet().get().getSecurity().get().get(0).keySet(), contains("foo_baz"));
381+
assertThat(result.getPaths().get("/3").getGet().get().getSecurity().get().size(), equalTo(1));
382+
assertThat(result.getPaths().get("/3").getGet().get().getSecurity().get().get(0).keySet(), contains("foo_baz"));
383+
}
384+
356385
@Test
357386
public void mergesInSchemaDocumentExtensions() {
358387
OpenApiConfig config = new OpenApiConfig();
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{
2+
"smithy": "1.0",
3+
"shapes": {
4+
"smithy.example#Service": {
5+
"type": "service",
6+
"version": "2006-03-01",
7+
"operations": [
8+
{
9+
"target": "smithy.example#Operation1"
10+
},
11+
{
12+
"target": "smithy.example#Operation2"
13+
},
14+
{
15+
"target": "smithy.example#Operation3"
16+
},
17+
{
18+
"target": "smithy.example#UnauthenticatedOperation"
19+
}
20+
],
21+
"traits": {
22+
"aws.protocols#restJson1": {},
23+
"aws.auth#sigv4": {
24+
"name": "example"
25+
},
26+
"smithy.api#httpBasicAuth": {},
27+
"smithy.api#httpDigestAuth": {},
28+
"smithy.api#auth": [
29+
"aws.auth#sigv4",
30+
"smithy.api#httpDigestAuth"
31+
]
32+
}
33+
},
34+
"smithy.example#Operation1": {
35+
"type": "operation",
36+
"traits": {
37+
"smithy.api#http": {
38+
"uri": "/1",
39+
"method": "GET"
40+
}
41+
}
42+
},
43+
"smithy.example#Operation2": {
44+
"type": "operation",
45+
"traits": {
46+
"smithy.api#http": {
47+
"uri": "/2",
48+
"method": "GET"
49+
},
50+
"smithy.api#auth": [
51+
"smithy.api#httpBasicAuth"
52+
]
53+
}
54+
},
55+
"smithy.example#Operation3": {
56+
"type": "operation",
57+
"traits": {
58+
"smithy.api#http": {
59+
"uri": "/3",
60+
"method": "GET"
61+
},
62+
"smithy.api#auth": [
63+
"smithy.api#httpBasicAuth",
64+
"aws.auth#sigv4"
65+
]
66+
}
67+
},
68+
"smithy.example#UnauthenticatedOperation": {
69+
"type": "operation",
70+
"traits": {
71+
"smithy.api#http": {
72+
"uri": "/4",
73+
"method": "GET"
74+
},
75+
"smithy.api#auth": []
76+
}
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)