Skip to content

Commit b837e38

Browse files
committed
check for sensitive data before generating a filter function
1 parent 52d1190 commit b837e38

File tree

5 files changed

+333
-43
lines changed

5 files changed

+333
-43
lines changed

config/checkstyle/checkstyle.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<!-- Files must contain a copyright header, with or without a year. -->
2323
<module name="RegexpHeader">
2424
<property name="header"
25-
value="/\*\n \* Copyright( 20(19|20|21|22)|) Amazon\.com, Inc\. or its affiliates\. All Rights Reserved\.\n"/>
25+
value="/\*\n \* Copyright( 20(19|20|21|22|23)|) Amazon\.com, Inc\. or its affiliates\. All Rights Reserved\.\n"/>
2626
<property name="fileExtensions" value="java"/>
2727
</module>
2828

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import software.amazon.smithy.typescript.codegen.endpointsV2.RuleSetParameterFinder;
4747
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
4848
import software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin;
49+
import software.amazon.smithy.typescript.codegen.validation.SensitiveDataFinder;
4950
import software.amazon.smithy.utils.OptionalUtils;
5051
import software.amazon.smithy.utils.SmithyInternalApi;
5152

@@ -73,6 +74,7 @@ final class CommandGenerator implements Runnable {
7374
private final Symbol outputType;
7475
private final ProtocolGenerator protocolGenerator;
7576
private final ApplicationProtocol applicationProtocol;
77+
private final SensitiveDataFinder sensitiveDataFinder = new SensitiveDataFinder();
7678

7779
CommandGenerator(
7880
TypeScriptSettings settings,
@@ -313,20 +315,25 @@ private void generateCommandMiddlewareResolver(String configType) {
313315
writer.openBlock("inputFilterSensitiveLog: ", ",", () -> {
314316
OptionalUtils.ifPresentOrElse(operationIndex.getInput(operation),
315317
input -> {
316-
Symbol inputSymbol = symbolProvider.toSymbol(input);
317-
String filterFunctionName = inputSymbol.getName() + "FilterSensitiveLog";
318-
writer.addImport(
319-
filterFunctionName,
320-
filterFunctionName,
321-
inputSymbol.getNamespace()
322-
);
323-
writer.writeInline(filterFunctionName);
318+
if (sensitiveDataFinder.findsSensitiveData(input, model)) {
319+
Symbol inputSymbol = symbolProvider.toSymbol(input);
320+
String filterFunctionName = inputSymbol.getName() + "FilterSensitiveLog";
321+
writer.addImport(
322+
filterFunctionName,
323+
filterFunctionName,
324+
inputSymbol.getNamespace()
325+
);
326+
writer.writeInline(filterFunctionName);
327+
} else {
328+
writer.writeInline("(_: any) => _");
329+
}
324330
},
325-
() -> writer.writeInline("(input: any) => input"));
331+
() -> writer.writeInline("(_: any) => _"));
326332
});
327333
writer.openBlock("outputFilterSensitiveLog: ", ",", () -> {
328334
OptionalUtils.ifPresentOrElse(operationIndex.getOutput(operation),
329335
output -> {
336+
if (sensitiveDataFinder.findsSensitiveData(output, model)) {
330337
Symbol outputSymbol = symbolProvider.toSymbol(output);
331338
String filterFunctionName = outputSymbol.getName() + "FilterSensitiveLog";
332339
writer.addImport(
@@ -335,8 +342,11 @@ private void generateCommandMiddlewareResolver(String configType) {
335342
outputSymbol.getNamespace()
336343
);
337344
writer.writeInline(filterFunctionName);
345+
} else {
346+
writer.writeInline("(_: any) => _");
347+
}
338348
},
339-
() -> writer.writeInline("(output: any) => output"));
349+
() -> writer.writeInline("(_: any) => _"));
340350
});
341351
});
342352
writer.write("const { requestHandler } = configuration;");

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/StructureGenerator.java

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,22 @@
2929
import software.amazon.smithy.model.traits.ErrorTrait;
3030
import software.amazon.smithy.typescript.codegen.TypeScriptSettings.RequiredMemberMode;
3131
import software.amazon.smithy.typescript.codegen.integration.HttpProtocolGeneratorUtils;
32+
import software.amazon.smithy.typescript.codegen.validation.SensitiveDataFinder;
3233
import software.amazon.smithy.utils.SmithyInternalApi;
3334

3435
/**
3536
* Generates normal structures and error structures.
3637
*
3738
* Renders structures as interfaces.
3839
*
39-
* <p>A namespace is created with the same name as the structure to
40+
* <p>
41+
* A namespace is created with the same name as the structure to
4042
* provide helper functionality for checking if a given value is
4143
* known to be of the same type as the structure. This will be
4244
* even more useful if/when inheritance is added to Smithy.
4345
*
44-
* <p>Note that the {@code required} trait on structures is used to
46+
* <p>
47+
* Note that the {@code required} trait on structures is used to
4548
* determine whether or not a generated TypeScript interface uses
4649
* required members. This is typically not recommended in other languages
4750
* since it's documented as backward-compatible for a model to migrate a
@@ -55,7 +58,8 @@
5558
* deserializers will need to set previously required properties to
5659
* undefined too.
5760
*
58-
* <p>The generator will explicitly state that a required property can
61+
* <p>
62+
* The generator will explicitly state that a required property can
5963
* be set to {@code undefined}. This makes it clear that undefined checks
6064
* need to be made when using {@code --strictNullChecks}, but has no
6165
* effect otherwise.
@@ -69,22 +73,23 @@ final class StructureGenerator implements Runnable {
6973
private final StructureShape shape;
7074
private final boolean includeValidation;
7175
private final RequiredMemberMode requiredMemberMode;
76+
private final SensitiveDataFinder sensitiveDataFinder = new SensitiveDataFinder();
7277

7378
/**
7479
* sets 'includeValidation' to 'false' and requiredMemberMode
7580
* to {@link RequiredMemberMode#NULLABLE}.
7681
*/
7782
StructureGenerator(Model model, SymbolProvider symbolProvider, TypeScriptWriter writer, StructureShape shape) {
7883
this(model, symbolProvider, writer, shape, false,
79-
RequiredMemberMode.NULLABLE);
84+
RequiredMemberMode.NULLABLE);
8085
}
8186

8287
StructureGenerator(Model model,
83-
SymbolProvider symbolProvider,
84-
TypeScriptWriter writer,
85-
StructureShape shape,
86-
boolean includeValidation,
87-
RequiredMemberMode requiredMemberMode) {
88+
SymbolProvider symbolProvider,
89+
TypeScriptWriter writer,
90+
StructureShape shape,
91+
boolean includeValidation,
92+
RequiredMemberMode requiredMemberMode) {
8893
this.model = model;
8994
this.symbolProvider = symbolProvider;
9095
this.writer = writer;
@@ -105,20 +110,24 @@ public void run() {
105110
/**
106111
* Renders a normal, non-error structure.
107112
*
108-
* <p>For example, given the following Smithy model:
113+
* <p>
114+
* For example, given the following Smithy model:
109115
*
110-
* <pre>{@code
116+
* <pre>
117+
* {@code
111118
* namespace smithy.example
112119
*
113120
* structure Person {
114-
* @required
121+
* &#64;required
115122
* name: String,
116-
* @range(min: 1)
123+
* &#64;range(min: 1)
117124
* age: Integer,
118125
* }
119-
* }</pre>
126+
* }
127+
* </pre>
120128
*
121-
* <p>The following TypeScript is rendered:
129+
* <p>
130+
* The following TypeScript is rendered:
122131
*
123132
* <pre>{@code
124133
* export interface Person {
@@ -129,7 +138,8 @@ public void run() {
129138
* export const PersonFilterSensitiveLog = (obj: Person): any => ({...obj});
130139
* }</pre>
131140
*
132-
* <p>If validation is enabled, it generates the following:
141+
* <p>
142+
* If validation is enabled, it generates the following:
133143
*
134144
* <pre>{@code
135145
* export interface Person {
@@ -174,15 +184,17 @@ private void renderNonErrorStructure() {
174184
private void renderStructureNamespace(StructuredMemberWriter structuredMemberWriter, boolean includeValidation) {
175185
Symbol symbol = symbolProvider.toSymbol(shape);
176186
String objectParam = "obj";
177-
writer.writeDocs("@internal");
178-
writer.openBlock("export const $LFilterSensitiveLog = ($L: $L): any => ({", "})",
179-
symbol.getName(),
180-
objectParam,
181-
symbol.getName(),
182-
() -> {
183-
structuredMemberWriter.writeFilterSensitiveLog(writer, objectParam);
184-
}
185-
);
187+
188+
if (sensitiveDataFinder.findsSensitiveData(shape, model)) {
189+
writer.writeDocs("@internal");
190+
writer.openBlock("export const $LFilterSensitiveLog = ($L: $L): any => ({", "})",
191+
symbol.getName(),
192+
objectParam,
193+
symbol.getName(),
194+
() -> {
195+
structuredMemberWriter.writeFilterSensitiveLog(writer, objectParam);
196+
});
197+
}
186198

187199
if (!includeValidation) {
188200
return;
@@ -212,19 +224,23 @@ private void renderStructureNamespace(StructuredMemberWriter structuredMemberWri
212224
* (ServiceException in case of server SDK), and add the appropriate fault
213225
* property.
214226
*
215-
* <p>Given the following Smithy structure:
227+
* <p>
228+
* Given the following Smithy structure:
216229
*
217-
* <pre>{@code
230+
* <pre>
231+
* {@code
218232
* namespace smithy.example
219233
*
220-
* @error("client")
234+
* &#64;error("client")
221235
* structure NoSuchResource {
222-
* @required
236+
* &#64;required
223237
* resourceType: String
224238
* }
225-
* }</pre>
239+
* }
240+
* </pre>
226241
*
227-
* <p>The following TypeScript is generated:
242+
* <p>
243+
* The following TypeScript is generated:
228244
*
229245
* <pre>{@code
230246
* import { ExceptionOptionType as __ExceptionOptionType } from "@aws-sdk/smithy-client";
@@ -262,7 +278,8 @@ private void renderErrorStructure() {
262278
}
263279
StructuredMemberWriter structuredMemberWriter = new StructuredMemberWriter(model, symbolProvider,
264280
shape.getAllMembers().values(), this.requiredMemberMode);
265-
// since any error interface must extend from JavaScript Error interface, message member is already
281+
// since any error interface must extend from JavaScript Error interface,
282+
// message member is already
266283
// required in the JavaScript Error interface
267284
structuredMemberWriter.skipMembers.add("message");
268285
structuredMemberWriter.writeMembers(writer, shape);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.smithy.typescript.codegen.validation;
17+
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
import software.amazon.smithy.model.Model;
21+
import software.amazon.smithy.model.shapes.CollectionShape;
22+
import software.amazon.smithy.model.shapes.MapShape;
23+
import software.amazon.smithy.model.shapes.MemberShape;
24+
import software.amazon.smithy.model.shapes.Shape;
25+
import software.amazon.smithy.model.shapes.SimpleShape;
26+
import software.amazon.smithy.model.traits.SensitiveTrait;
27+
28+
public class SensitiveDataFinder {
29+
private Map<Shape, Boolean> cache = new HashMap<>();
30+
31+
public boolean findsSensitiveData(Shape shape, Model model) {
32+
boolean found = findRecursive(shape, model);
33+
cache.put(shape, found);
34+
return found;
35+
}
36+
37+
private boolean findRecursive(Shape shape, Model model) {
38+
if (cache.containsKey(shape)) {
39+
return cache.get(shape);
40+
}
41+
42+
if (shape.hasTrait(SensitiveTrait.class)) {
43+
cache.put(shape, true);
44+
return true;
45+
}
46+
47+
if (shape instanceof MemberShape) {
48+
MemberShape memberShape = (MemberShape) shape;
49+
if (memberShape.getMemberTrait(model, SensitiveTrait.class).isPresent()) {
50+
cache.put(shape, true);
51+
return true;
52+
}
53+
Shape memberTarget = model.expectShape(memberShape.getTarget());
54+
return findRecursive(memberTarget, model);
55+
}
56+
57+
if (shape.getMemberTrait(model, SensitiveTrait.class).isPresent()) {
58+
cache.put(shape, true);
59+
return true;
60+
} else if (shape instanceof SimpleShape) {
61+
cache.put(shape, false);
62+
return false;
63+
} else if (shape.isStructureShape() || shape.isUnionShape()) {
64+
boolean found = shape.getAllMembers()
65+
.values()
66+
.stream()
67+
.anyMatch(m -> findRecursive(m, model));
68+
69+
cache.put(shape, found);
70+
return found;
71+
} else if (shape instanceof CollectionShape) {
72+
MemberShape collectionMember = ((CollectionShape) shape).getMember();
73+
return findRecursive(collectionMember, model);
74+
} else if (shape instanceof MapShape) {
75+
MemberShape keyMember = ((MapShape) shape).getKey();
76+
MemberShape valMember = ((MapShape) shape).getValue();
77+
return findRecursive(keyMember, model) || findRecursive(valMember, model);
78+
}
79+
80+
cache.put(shape, false);
81+
return false;
82+
}
83+
}

0 commit comments

Comments
 (0)