Skip to content

Commit 018582b

Browse files
authored
Adds a client builder option to disable the default MD5 checksum vali… (#4729)
* Adds a client builder option to disable the default MD5 checksum validation * Changing name of validation parameter and inverting the boolean
1 parent 0e3b14d commit 018582b

File tree

8 files changed

+205
-7
lines changed

8 files changed

+205
-7
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "Amazon SQS",
4+
"contributor": "",
5+
"description": "Adds a client builder option to disable the default MD5 checksum validation for SendMessage, ReceiveMessage and SendMessageBatch"
6+
}

codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/EndpointProviderTasks.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ private boolean shouldGenerateClientEndpointTests() {
128128

129129
private boolean hasClientContextParams() {
130130
Map<String, ClientContextParam> clientContextParams = model.getClientContextParams();
131-
return clientContextParams != null && !clientContextParams.isEmpty();
131+
Map<String, ClientContextParam> customClientContextParams = model.getCustomizationConfig().getCustomClientContextParams();
132+
return (clientContextParams != null && !clientContextParams.isEmpty()) ||
133+
(customClientContextParams != null && !customClientContextParams.isEmpty());
132134
}
133135

134136
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/ClientContextParamsClassSpec.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@ public TypeSpec poetSpec() {
5050

5151
b.addMethod(ctor());
5252

53-
model.getClientContextParams().forEach((n, m) -> {
54-
b.addField(paramDeclaration(n, m));
55-
});
53+
if (model.getClientContextParams() != null) {
54+
model.getClientContextParams().forEach((n, m) -> {
55+
b.addField(paramDeclaration(n, m));
56+
});
57+
}
5658

5759
if (model.getCustomizationConfig() != null && model.getCustomizationConfig().getCustomClientContextParams() != null) {
5860
model.getCustomizationConfig().getCustomClientContextParams().forEach((n, m) -> {

services/sqs/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,11 @@
8585
<artifactId>http-auth-aws</artifactId>
8686
<version>${awsjavasdk.version}</version>
8787
</dependency>
88+
<dependency>
89+
<groupId>software.amazon.awssdk</groupId>
90+
<artifactId>service-test-utils</artifactId>
91+
<version>${awsjavasdk.version}</version>
92+
<scope>test</scope>
93+
</dependency>
8894
</dependencies>
8995
</project>

services/sqs/src/main/java/software/amazon/awssdk/services/sqs/internal/MessageMD5ChecksumInterceptor.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import software.amazon.awssdk.core.interceptor.Context;
3232
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
3333
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
34+
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
35+
import software.amazon.awssdk.services.sqs.endpoints.SqsClientContextParams;
3436
import software.amazon.awssdk.services.sqs.model.Message;
3537
import software.amazon.awssdk.services.sqs.model.MessageAttributeValue;
3638
import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest;
@@ -41,6 +43,7 @@
4143
import software.amazon.awssdk.services.sqs.model.SendMessageBatchResultEntry;
4244
import software.amazon.awssdk.services.sqs.model.SendMessageRequest;
4345
import software.amazon.awssdk.services.sqs.model.SendMessageResponse;
46+
import software.amazon.awssdk.utils.AttributeMap;
4447
import software.amazon.awssdk.utils.BinaryUtils;
4548
import software.amazon.awssdk.utils.Logger;
4649
import software.amazon.awssdk.utils.Md5Utils;
@@ -77,7 +80,8 @@ public final class MessageMD5ChecksumInterceptor implements ExecutionInterceptor
7780
public void afterExecution(Context.AfterExecution context, ExecutionAttributes executionAttributes) {
7881
SdkResponse response = context.response();
7982
SdkRequest originalRequest = context.request();
80-
if (response != null) {
83+
84+
if (response != null && validateMessageMD5Enabled(executionAttributes)) {
8185
if (originalRequest instanceof SendMessageRequest) {
8286
SendMessageRequest sendMessageRequest = (SendMessageRequest) originalRequest;
8387
SendMessageResponse sendMessageResult = (SendMessageResponse) response;
@@ -95,6 +99,12 @@ public void afterExecution(Context.AfterExecution context, ExecutionAttributes e
9599
}
96100
}
97101

102+
private static boolean validateMessageMD5Enabled(ExecutionAttributes executionAttributes) {
103+
AttributeMap clientContextParams = executionAttributes.getAttribute(SdkInternalExecutionAttribute.CLIENT_CONTEXT_PARAMS);
104+
Boolean enableMd5Validation = clientContextParams.get(SqsClientContextParams.CHECKSUM_VALIDATION_ENABLED);
105+
return enableMd5Validation == null || enableMd5Validation;
106+
}
107+
98108
/**
99109
* Throw an exception if the MD5 checksums returned in the SendMessageResponse do not match the
100110
* client-side calculation based on the original message in the SendMessageRequest.

services/sqs/src/main/resources/codegen-resources/customization.config

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,11 @@
44
],
55
"interceptors": [
66
"software.amazon.awssdk.services.sqs.internal.MessageMD5ChecksumInterceptor"
7-
]
7+
],
8+
"customClientContextParams":{
9+
"checksumValidationEnabled":{
10+
"documentation":"Enable message MD5 checksum validation.<p>Checksum validation for messages defaults to true. Only set to false if required, for instance if your cryptographic library does not support MD5.<p>Supported operations are SendMessage, ReceiveMessage and SendMessageBatch.",
11+
"type":"boolean"
12+
}
13+
}
814
}

services/sqs/src/test/java/software/amazon/awssdk/services/sqs/MessageMD5ChecksumInterceptorTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import software.amazon.awssdk.core.exception.SdkClientException;
2727
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
2828
import software.amazon.awssdk.core.interceptor.InterceptorContext;
29+
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
30+
import software.amazon.awssdk.services.sqs.endpoints.SqsClientContextParams;
2931
import software.amazon.awssdk.services.sqs.internal.MessageMD5ChecksumInterceptor;
3032
import software.amazon.awssdk.services.sqs.model.Message;
3133
import software.amazon.awssdk.services.sqs.model.MessageAttributeValue;
@@ -37,6 +39,7 @@
3739
import software.amazon.awssdk.services.sqs.model.SendMessageBatchResultEntry;
3840
import software.amazon.awssdk.services.sqs.model.SendMessageRequest;
3941
import software.amazon.awssdk.services.sqs.model.SendMessageResponse;
42+
import software.amazon.awssdk.utils.AttributeMap;
4043

4144
/**
4245
* Verifies the functionality of {@link MessageMD5ChecksumInterceptor}.
@@ -236,11 +239,13 @@ private void assertFailure(SdkRequest request, SdkResponse response) {
236239
}
237240

238241
private void callInterceptor(SdkRequest request, SdkResponse response) {
242+
ExecutionAttributes executionAttributes = new ExecutionAttributes();
243+
executionAttributes.putAttribute(SdkInternalExecutionAttribute.CLIENT_CONTEXT_PARAMS, AttributeMap.builder().build());
239244
new MessageMD5ChecksumInterceptor().afterExecution(InterceptorContext.builder()
240245
.request(request)
241246
.response(response)
242247
.build(),
243-
new ExecutionAttributes());
248+
executionAttributes);
244249
}
245250

246251
private String messageBody() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* Copyright 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.awssdk.services.sqs;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
20+
21+
import java.util.Collections;
22+
import java.util.HashMap;
23+
import java.util.Map;
24+
import org.junit.jupiter.api.AfterEach;
25+
import org.junit.jupiter.api.BeforeEach;
26+
import org.junit.jupiter.api.Test;
27+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
28+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
29+
import software.amazon.awssdk.http.AbortableInputStream;
30+
import software.amazon.awssdk.http.HttpExecuteResponse;
31+
import software.amazon.awssdk.http.SdkHttpResponse;
32+
import software.amazon.awssdk.services.sqs.internal.MessageMD5ChecksumInterceptor;
33+
import software.amazon.awssdk.services.sqs.model.MessageAttributeValue;
34+
import software.amazon.awssdk.services.sqs.model.SendMessageResponse;
35+
import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient;
36+
import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient;
37+
import software.amazon.awssdk.utils.StringInputStream;
38+
39+
/**
40+
* Verifies that the logic in {@link MessageMD5ChecksumInterceptor} can be disabled, which is needed for use cases like
41+
* FIPS cryptography libraries that don't have MD5 support. SendMessage is used as the test API, but the flow is the
42+
* same for ReceiveMessage and SendMessageBatch.
43+
*/
44+
public class MessageMD5ChecksumValidationDisableTest {
45+
private static final AwsBasicCredentials CLIENT_CREDENTIALS = AwsBasicCredentials.create("ca", "cs");
46+
private static final String MESSAGE_ID = "0f433476-621e-4638-811a-112d2c2e41d7";
47+
48+
private MockAsyncHttpClient asyncHttpClient;
49+
private MockSyncHttpClient syncHttpClient;
50+
51+
@BeforeEach
52+
public void setupClient() {
53+
asyncHttpClient = new MockAsyncHttpClient();
54+
syncHttpClient = new MockSyncHttpClient();
55+
}
56+
57+
@AfterEach
58+
public void cleanup() {
59+
asyncHttpClient.reset();
60+
syncHttpClient.reset();
61+
}
62+
63+
@Test
64+
public void md5ValidationEnabled_default_md5InResponse_Works() {
65+
asyncHttpClient.stubResponses(responseWithMd5());
66+
SqsAsyncClient client = SqsAsyncClient.builder()
67+
.credentialsProvider(StaticCredentialsProvider.create(CLIENT_CREDENTIALS))
68+
.httpClient(asyncHttpClient)
69+
.build();
70+
71+
SendMessageResponse sendMessageResponse =
72+
client.sendMessage(r -> r.messageBody(messageBody()).messageAttributes(createAttributeValues())).join();
73+
74+
assertThat(sendMessageResponse.messageId()).isEqualTo(MESSAGE_ID);
75+
}
76+
77+
@Test
78+
public void md5ValidationEnabled_default_noMd5InResponse_throwsException() {
79+
asyncHttpClient.stubResponses(responseWithoutMd5());
80+
SqsAsyncClient client = SqsAsyncClient.builder()
81+
.credentialsProvider(StaticCredentialsProvider.create(CLIENT_CREDENTIALS))
82+
.httpClient(asyncHttpClient)
83+
.build();
84+
85+
assertThatThrownBy(() -> client.sendMessage(r -> r.messageBody(messageBody())
86+
.messageAttributes(createAttributeValues()))
87+
.join())
88+
.hasMessageContaining("MD5 returned by SQS does not match the calculation on the original request");
89+
}
90+
91+
@Test
92+
public void md5ValidationDisabled_md5InResponse_Works() {
93+
asyncHttpClient.stubResponses(responseWithMd5());
94+
SqsAsyncClient client = SqsAsyncClient.builder()
95+
.credentialsProvider(StaticCredentialsProvider.create(CLIENT_CREDENTIALS))
96+
.httpClient(asyncHttpClient)
97+
.checksumValidationEnabled(false)
98+
.build();
99+
100+
SendMessageResponse sendMessageResponse =
101+
client.sendMessage(r -> r.messageBody(messageBody()).messageAttributes(createAttributeValues())).join();
102+
103+
assertThat(sendMessageResponse.messageId()).isEqualTo(MESSAGE_ID);
104+
}
105+
106+
@Test
107+
public void md5ValidationDisabled_noMd5InResponse_Works() {
108+
asyncHttpClient.stubResponses(responseWithoutMd5());
109+
SqsAsyncClient client = SqsAsyncClient.builder()
110+
.credentialsProvider(StaticCredentialsProvider.create(CLIENT_CREDENTIALS))
111+
.httpClient(asyncHttpClient)
112+
.checksumValidationEnabled(false)
113+
.build();
114+
115+
SendMessageResponse sendMessageResponse =
116+
client.sendMessage(r -> r.messageBody(messageBody()).messageAttributes(createAttributeValues())).join();
117+
118+
assertThat(sendMessageResponse.messageId()).isEqualTo(MESSAGE_ID);
119+
}
120+
121+
@Test
122+
public void sync_md5ValidationDisabled_noMd5InResponse_Works() {
123+
syncHttpClient.stubResponses(responseWithoutMd5());
124+
SqsClient client = SqsClient.builder()
125+
.credentialsProvider(StaticCredentialsProvider.create(CLIENT_CREDENTIALS))
126+
.httpClient(syncHttpClient)
127+
.checksumValidationEnabled(false)
128+
.build();
129+
130+
SendMessageResponse sendMessageResponse =
131+
client.sendMessage(r -> r.messageBody(messageBody()).messageAttributes(createAttributeValues()));
132+
133+
assertThat(sendMessageResponse.messageId()).isEqualTo(MESSAGE_ID);
134+
}
135+
136+
private static String messageBody() {
137+
return "Body";
138+
}
139+
140+
private static HttpExecuteResponse responseWithMd5() {
141+
return HttpExecuteResponse.builder().response(SdkHttpResponse.builder().statusCode(200).build()).responseBody(
142+
AbortableInputStream.create(new StringInputStream(
143+
"{\"MD5OfMessageAttributes\":\"43eeb333d10515533e317490584ea243\","
144+
+ "\"MD5OfMessageBody\":\"ac101b32dda4448cf13a93fe283dddd8\","
145+
+ "\"MessageId\":\"" + MESSAGE_ID + "\"} ")))
146+
.build();
147+
}
148+
149+
private static HttpExecuteResponse responseWithoutMd5() {
150+
return HttpExecuteResponse.builder().response(SdkHttpResponse.builder().statusCode(200).build())
151+
.responseBody(AbortableInputStream
152+
.create(new StringInputStream("{\"MessageId\":\"" + MESSAGE_ID + "\"} ")))
153+
.build();
154+
}
155+
156+
protected static Map<String, MessageAttributeValue> createAttributeValues() {
157+
Map<String, MessageAttributeValue> attrs = new HashMap();
158+
attrs.put("attribute-1", MessageAttributeValue.builder().dataType("String").stringValue("tmp").build());
159+
return Collections.unmodifiableMap(attrs);
160+
}
161+
}

0 commit comments

Comments
 (0)