Skip to content

Commit 81b08b6

Browse files
authored
added invalid request on invalid payload deserialization (#310)
1 parent 130da4f commit 81b08b6

4 files changed

Lines changed: 116 additions & 14 deletions

File tree

src/main/java/software/amazon/cloudformation/LambdaWrapper.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import software.amazon.awssdk.http.apache.ApacheHttpClient;
4545
import software.amazon.awssdk.utils.StringUtils;
4646
import software.amazon.cloudformation.exceptions.BaseHandlerException;
47+
import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
4748
import software.amazon.cloudformation.exceptions.FileScrubberException;
4849
import software.amazon.cloudformation.exceptions.TerminalException;
4950
import software.amazon.cloudformation.injection.CloudWatchLogsProvider;
@@ -200,12 +201,19 @@ public void handleRequest(final InputStream inputStream, final OutputStream outp
200201
// deserialize incoming payload to modelled request
201202
try {
202203
request = this.serializer.deserialize(input, typeReference);
204+
205+
handlerResponse = processInvocation(rawInput, request, context);
203206
} catch (MismatchedInputException e) {
204207
JSONObject resourceSchemaJSONObject = provideResourceSchemaJSONObject();
205208
JSONObject rawModelObject = rawInput.getJSONObject("requestData").getJSONObject("resourceProperties");
209+
206210
this.validator.validateObject(rawModelObject, resourceSchemaJSONObject);
211+
212+
handlerResponse = ProgressEvent.defaultFailureHandler(
213+
new CfnInvalidRequestException("Resource properties validation failed with invalid configuration", e),
214+
HandlerErrorCode.InvalidRequest);
207215
}
208-
handlerResponse = processInvocation(rawInput, request, context);
216+
209217
} catch (final ValidationException e) {
210218
String message;
211219
String fullExceptionMessage = ValidationException.buildFullExceptionMessage(e);
@@ -221,7 +229,7 @@ public void handleRequest(final InputStream inputStream, final OutputStream outp
221229
} catch (final Throwable e) {
222230
// Exceptions are wrapped as a consistent error response to the caller (i.e;
223231
// CloudFormation)
224-
e.printStackTrace(); // for root causing - logs to LambdaLogger by default
232+
log(ExceptionUtils.getStackTrace(e)); // for root causing - logs to LambdaLogger by default
225233
handlerResponse = ProgressEvent.defaultFailureHandler(e, HandlerErrorCode.InternalFailure);
226234
if (request != null && request.getRequestData() != null && MUTATING_ACTIONS.contains(request.getAction())) {
227235
handlerResponse.setResourceModel(request.getRequestData().getResourceProperties());

src/test/java/software/amazon/cloudformation/LambdaWrapperTest.java

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public void invokeHandler_nullResponse_returnsFailure(final String requestDataPa
171171

172172
// verify that model validation occurred for CREATE/UPDATE/DELETE
173173
if (action == Action.CREATE || action == Action.UPDATE || action == Action.DELETE) {
174-
verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class));
174+
verify(validator).validateObject(any(JSONObject.class), any(JSONObject.class));
175175
}
176176

177177
verify(providerEventsLogger).refreshClient();
@@ -190,6 +190,36 @@ public void invokeHandler_nullResponse_returnsFailure(final String requestDataPa
190190
}
191191
}
192192

193+
@Test
194+
public void invokeHandler_SchemaFailureOnNestedProperties() throws IOException {
195+
// use actual validator to verify behaviour
196+
final WrapperOverride wrapper = new WrapperOverride(providerLoggingCredentialsProvider, platformEventsLogger,
197+
providerEventsLogger, providerMetricsPublisher, new Validator() {
198+
}, httpClient);
199+
200+
wrapper.setTransformResponse(resourceHandlerRequest);
201+
202+
try (final InputStream in = loadRequestStream("create.request.with-extraneous-model-object.json");
203+
final OutputStream out = new ByteArrayOutputStream()) {
204+
final Context context = getLambdaContext();
205+
206+
wrapper.handleRequest(in, out, context);
207+
// validation failure metric should be published but no others
208+
verify(providerMetricsPublisher).publishExceptionMetric(any(Instant.class), eq(Action.CREATE), any(Exception.class),
209+
any(HandlerErrorCode.class));
210+
211+
// all metrics should be published, even for a single invocation
212+
verify(providerMetricsPublisher).publishInvocationMetric(any(Instant.class), eq(Action.CREATE));
213+
214+
// verify initialiseRuntime was called and initialised dependencies
215+
verifyInitialiseRuntime();
216+
217+
// verify output response
218+
verifyHandlerResponse(out, ProgressEvent.<TestModel, TestContext>builder().errorCode(HandlerErrorCode.InvalidRequest)
219+
.status(OperationStatus.FAILED).message("Resource properties validation failed with invalid configuration").build());
220+
}
221+
}
222+
193223
@Test
194224
public void invokeHandlerForCreate_without_customer_loggingCredentials() throws IOException {
195225
invokeHandler_without_customerLoggingCredentials("create.request-without-logging-credentials.json", Action.CREATE);
@@ -224,7 +254,7 @@ private void invokeHandler_without_customerLoggingCredentials(final String reque
224254

225255
// verify that model validation occurred for CREATE/UPDATE/DELETE
226256
if (action == Action.CREATE || action == Action.UPDATE || action == Action.DELETE) {
227-
verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class));
257+
verify(validator).validateObject(any(JSONObject.class), any(JSONObject.class));
228258
}
229259

230260
verifyNoMoreInteractions(providerEventsLogger);
@@ -259,7 +289,7 @@ public void invokeHandler_handlerFailed_returnsFailure(final String requestDataP
259289

260290
// verify that model validation occurred for CREATE/UPDATE/DELETE
261291
if (action == Action.CREATE || action == Action.UPDATE || action == Action.DELETE) {
262-
verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class));
292+
verify(validator).validateObject(any(JSONObject.class), any(JSONObject.class));
263293
}
264294

265295
// verify output response
@@ -318,7 +348,7 @@ public void invokeHandler_CompleteSynchronously_returnsSuccess(final String requ
318348

319349
// verify that model validation occurred for CREATE/UPDATE/DELETE
320350
if (action == Action.CREATE || action == Action.UPDATE || action == Action.DELETE) {
321-
verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class));
351+
verify(validator).validateObject(any(JSONObject.class), any(JSONObject.class));
322352
}
323353

324354
// verify output response
@@ -467,7 +497,7 @@ public void reInvokeHandler_InProgress_returnsInProgress(final String requestDat
467497

468498
// verify that model validation occurred for CREATE/UPDATE/DELETE
469499
if (action == Action.CREATE || action == Action.UPDATE || action == Action.DELETE) {
470-
verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class));
500+
verify(validator).validateObject(any(JSONObject.class), any(JSONObject.class));
471501
}
472502

473503
// verify output response
@@ -481,6 +511,7 @@ public void reInvokeHandler_InProgress_returnsInProgress(final String requestDat
481511
public void invokeHandler_SchemaValidationFailure(final String requestDataPath, final String actionAsString)
482512
throws IOException {
483513
final Action action = Action.valueOf(actionAsString);
514+
484515
doThrow(ValidationException.class).when(validator).validateObject(any(JSONObject.class), any(JSONObject.class));
485516

486517
wrapper.setTransformResponse(resourceHandlerRequest);
@@ -502,7 +533,7 @@ public void invokeHandler_SchemaValidationFailure(final String requestDataPath,
502533

503534
// verify that model validation occurred for CREATE/UPDATE/DELETE
504535
if (action == Action.CREATE || action == Action.UPDATE || action == Action.DELETE) {
505-
verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class));
536+
verify(validator).validateObject(any(JSONObject.class), any(JSONObject.class));
506537
}
507538

508539
// verify output response
@@ -548,7 +579,6 @@ providerEventsLogger, providerMetricsPublisher, new Validator() {
548579
final Context context = getLambdaContext();
549580

550581
wrapper.handleRequest(in, out, context);
551-
552582
// validation failure metric should be published but no others
553583
verify(providerMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), eq(Action.CREATE),
554584
any(Exception.class), any(HandlerErrorCode.class));
@@ -711,7 +741,7 @@ public void invokeHandler_throwsAmazonServiceException_returnsServiceException()
711741
any(AmazonServiceException.class), any(HandlerErrorCode.class));
712742

713743
// verify that model validation occurred for CREATE/UPDATE/DELETE
714-
verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class));
744+
verify(validator).validateObject(any(JSONObject.class), any(JSONObject.class));
715745

716746
// verify output response
717747
verifyHandlerResponse(out,
@@ -728,7 +758,7 @@ public void invokeHandler_throwsSDK2ServiceException_returnsServiceException() t
728758
wrapper.setTransformResponse(resourceHandlerRequest);
729759

730760
try (final InputStream in = loadRequestStream("create.request.json");
731-
final OutputStream out = new ByteArrayOutputStream()) {
761+
final OutputStream out = new ByteArrayOutputStream()) {
732762
final Context context = getLambdaContext();
733763

734764
wrapper.handleRequest(in, out, context);
@@ -745,7 +775,7 @@ public void invokeHandler_throwsSDK2ServiceException_returnsServiceException() t
745775
any(CloudWatchLogsException.class), any(HandlerErrorCode.class));
746776

747777
// verify that model validation occurred for CREATE/UPDATE/DELETE
748-
verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class));
778+
verify(validator).validateObject(any(JSONObject.class), any(JSONObject.class));
749779

750780
// verify output response
751781
verifyHandlerResponse(out,
@@ -780,7 +810,7 @@ public void invokeHandler_throwsResourceAlreadyExistsException_returnsAlreadyExi
780810
any(ResourceAlreadyExistsException.class), any(HandlerErrorCode.class));
781811

782812
// verify that model validation occurred for CREATE/UPDATE/DELETE
783-
verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class));
813+
verify(validator).validateObject(any(JSONObject.class), any(JSONObject.class));
784814

785815
// verify output response
786816
verifyHandlerResponse(out,
@@ -815,7 +845,7 @@ public void invokeHandler_throwsResourceNotFoundException_returnsNotFound() thro
815845
any(ResourceNotFoundException.class), any(HandlerErrorCode.class));
816846

817847
// verify that model validation occurred for CREATE/UPDATE/DELETE
818-
verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class));
848+
verify(validator).validateObject(any(JSONObject.class), any(JSONObject.class));
819849

820850
// verify output response
821851
verifyHandlerResponse(out,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"awsAccountId": "123456789012",
3+
"bearerToken": "123456",
4+
"region": "us-east-1",
5+
"action": "CREATE",
6+
"responseEndpoint": "https://cloudformation.us-west-2.amazonaws.com",
7+
"resourceType": "AWS::Test::TestModel",
8+
"resourceTypeVersion": "1.0",
9+
"requestContext": {},
10+
"requestData": {
11+
"callerCredentials": {
12+
"accessKeyId": "IASAYK835GAIFHAHEI23",
13+
"secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0",
14+
"sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg"
15+
},
16+
"providerCredentials": {
17+
"accessKeyId": "HDI0745692Y45IUTYR78",
18+
"secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3",
19+
"sessionToken": "842HYOFIQAEUDF78R8T7IU43HSADYGIFHBJSDHFA87SDF9PYvN1CEYASDUYFT5TQ97YASIHUDFAIUEYRISDKJHFAYSUDTFSDFADS"
20+
},
21+
"providerLogGroupName": "providerLoggingGroupName",
22+
"logicalResourceId": "myBucket",
23+
"resourceProperties": {
24+
"property1": "abc",
25+
"property2": 123,
26+
"property3": {
27+
"subProperty": {
28+
"propertyArray": "singleValue"
29+
}
30+
}
31+
},
32+
"systemTags": {
33+
"aws:cloudformation:stack-id": "SampleStack"
34+
},
35+
"stackTags": {
36+
"tag1": "abc"
37+
},
38+
"previousStackTags": {
39+
"tag1": "def"
40+
}
41+
},
42+
"stackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/SampleStack/e722ae60-fe62-11e8-9a0e-0ae8cc519968"
43+
}

src/test/resources/software/amazon/cloudformation/wrapper-override.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
11
{
22
"typeName": "Test::Resource::Type",
33
"description": "Description",
4+
"definitions": {
5+
"subProperty": {
6+
"type": "object",
7+
"properties": {
8+
"propertyArray": {
9+
"type": "array",
10+
"items": {
11+
"type": "string"
12+
}
13+
}
14+
}
15+
}
16+
},
417
"properties": {
518
"property1": {
619
"type": "string"
720
},
821
"property2": {
922
"type": "integer"
23+
},
24+
"property3": {
25+
"type": "object",
26+
"properties": {
27+
"subProperty": {
28+
"$ref": "#/definitions/subProperty"
29+
}
30+
}
1031
}
1132
},
1233
"additionalProperties": false,

0 commit comments

Comments
 (0)