Skip to content

Commit f6068c8

Browse files
authored
chore(middleware-flexible-checksums): perform checksum calculation and validation by default (#6750)
1 parent 2293f5a commit f6068c8

9 files changed

+478
-135
lines changed

Diff for: packages/middleware-flexible-checksums/src/configuration.ts

+13
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import {
44
Encoder,
55
GetAwsChunkedEncodingStream,
66
HashConstructor,
7+
Provider,
78
StreamCollector,
89
StreamHasher,
910
} from "@smithy/types";
1011

12+
import { RequestChecksumCalculation, ResponseChecksumValidation } from "./constants";
13+
1114
export interface PreviouslyResolved {
1215
/**
1316
* The function that will be used to convert binary data to a base64-encoded string.
@@ -31,6 +34,16 @@ export interface PreviouslyResolved {
3134
*/
3235
md5: ChecksumConstructor | HashConstructor;
3336

37+
/**
38+
* Determines when a checksum will be calculated for request payloads
39+
*/
40+
requestChecksumCalculation: Provider<RequestChecksumCalculation>;
41+
42+
/**
43+
* Determines when a checksum will be calculated for response payloads
44+
*/
45+
responseChecksumValidation: Provider<ResponseChecksumValidation>;
46+
3447
/**
3548
* A constructor for a class implementing the {@link Hash} interface that computes SHA1 hashes.
3649
* @internal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { setFeature } from "@aws-sdk/core";
2+
import { afterEach, describe, expect, test as it, vi } from "vitest";
3+
4+
import { PreviouslyResolved } from "./configuration";
5+
import { DEFAULT_CHECKSUM_ALGORITHM, RequestChecksumCalculation, ResponseChecksumValidation } from "./constants";
6+
import { flexibleChecksumsInputMiddleware } from "./flexibleChecksumsInputMiddleware";
7+
8+
vi.mock("@aws-sdk/core");
9+
10+
describe(flexibleChecksumsInputMiddleware.name, () => {
11+
const mockNext = vi.fn();
12+
const mockRequestValidationModeMember = "mockRequestValidationModeMember";
13+
14+
const mockConfig = {
15+
requestChecksumCalculation: () => Promise.resolve(RequestChecksumCalculation.WHEN_SUPPORTED),
16+
responseChecksumValidation: () => Promise.resolve(ResponseChecksumValidation.WHEN_SUPPORTED),
17+
} as PreviouslyResolved;
18+
19+
afterEach(() => {
20+
expect(mockNext).toHaveBeenCalledTimes(1);
21+
vi.clearAllMocks();
22+
});
23+
24+
describe("sets input.requestValidationModeMember", () => {
25+
it("when requestValidationModeMember is defined and responseChecksumValidation is supported", async () => {
26+
const mockMiddlewareConfigWithMockRequestValidationModeMember = {
27+
requestValidationModeMember: mockRequestValidationModeMember,
28+
};
29+
const handler = flexibleChecksumsInputMiddleware(
30+
mockConfig,
31+
mockMiddlewareConfigWithMockRequestValidationModeMember
32+
)(mockNext, {});
33+
await handler({ input: {} });
34+
expect(mockNext).toHaveBeenCalledWith({ input: { [mockRequestValidationModeMember]: "ENABLED" } });
35+
});
36+
});
37+
38+
describe("leaves input.requestValidationModeMember", () => {
39+
const mockArgs = { input: {} };
40+
41+
it("when requestValidationModeMember is not defined", async () => {
42+
const handler = flexibleChecksumsInputMiddleware(mockConfig, {})(mockNext, {});
43+
await handler(mockArgs);
44+
expect(mockNext).toHaveBeenCalledWith(mockArgs);
45+
});
46+
47+
it("when responseChecksumValidation is required", async () => {
48+
const mockConfigResWhenRequired = {
49+
...mockConfig,
50+
responseChecksumValidation: () => Promise.resolve(ResponseChecksumValidation.WHEN_REQUIRED),
51+
} as PreviouslyResolved;
52+
53+
const handler = flexibleChecksumsInputMiddleware(mockConfigResWhenRequired, {})(mockNext, {});
54+
await handler(mockArgs);
55+
56+
expect(mockNext).toHaveBeenCalledWith(mockArgs);
57+
});
58+
});
59+
60+
describe("set feature", () => {
61+
it.each([
62+
[
63+
"FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED",
64+
"a",
65+
"requestChecksumCalculation",
66+
RequestChecksumCalculation.WHEN_REQUIRED,
67+
],
68+
[
69+
"FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED",
70+
"Z",
71+
"requestChecksumCalculation",
72+
RequestChecksumCalculation.WHEN_SUPPORTED,
73+
],
74+
[
75+
"FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED",
76+
"c",
77+
"responseChecksumValidation",
78+
ResponseChecksumValidation.WHEN_REQUIRED,
79+
],
80+
[
81+
"FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED",
82+
"b",
83+
"responseChecksumValidation",
84+
ResponseChecksumValidation.WHEN_SUPPORTED,
85+
],
86+
])("logs %s:%s when %s=%s", async (feature, value, configKey, configValue) => {
87+
const mockConfigOverride = {
88+
...mockConfig,
89+
[configKey]: () => Promise.resolve(configValue),
90+
} as PreviouslyResolved;
91+
92+
const handler = flexibleChecksumsInputMiddleware(mockConfigOverride, {})(mockNext, {});
93+
await handler({ input: {} });
94+
95+
expect(setFeature).toHaveBeenCalledTimes(2);
96+
if (configKey === "requestChecksumCalculation") {
97+
expect(setFeature).toHaveBeenNthCalledWith(1, expect.anything(), feature, value);
98+
} else {
99+
expect(setFeature).toHaveBeenNthCalledWith(2, expect.anything(), feature, value);
100+
}
101+
});
102+
});
103+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { setFeature } from "@aws-sdk/core";
2+
import {
3+
HandlerExecutionContext,
4+
MetadataBearer,
5+
RelativeMiddlewareOptions,
6+
SerializeHandler,
7+
SerializeHandlerArguments,
8+
SerializeHandlerOutput,
9+
SerializeMiddleware,
10+
} from "@smithy/types";
11+
12+
import { PreviouslyResolved } from "./configuration";
13+
import { RequestChecksumCalculation, ResponseChecksumValidation } from "./constants";
14+
15+
export interface FlexibleChecksumsInputMiddlewareConfig {
16+
/**
17+
* Defines a top-level operation input member used to opt-in to best-effort validation
18+
* of a checksum returned in the HTTP response of the operation.
19+
*/
20+
requestValidationModeMember?: string;
21+
}
22+
23+
/**
24+
* @internal
25+
*/
26+
export const flexibleChecksumsInputMiddlewareOptions: RelativeMiddlewareOptions = {
27+
name: "flexibleChecksumsInputMiddleware",
28+
toMiddleware: "serializerMiddleware",
29+
relation: "before",
30+
tags: ["BODY_CHECKSUM"],
31+
override: true,
32+
};
33+
34+
/**
35+
* @internal
36+
*
37+
* The input counterpart to the flexibleChecksumsMiddleware.
38+
*/
39+
export const flexibleChecksumsInputMiddleware =
40+
(
41+
config: PreviouslyResolved,
42+
middlewareConfig: FlexibleChecksumsInputMiddlewareConfig
43+
): SerializeMiddleware<any, any> =>
44+
<Output extends MetadataBearer>(
45+
next: SerializeHandler<any, Output>,
46+
context: HandlerExecutionContext
47+
): SerializeHandler<any, Output> =>
48+
async (args: SerializeHandlerArguments<any>): Promise<SerializeHandlerOutput<Output>> => {
49+
const input = args.input;
50+
const { requestValidationModeMember } = middlewareConfig;
51+
52+
const requestChecksumCalculation = await config.requestChecksumCalculation();
53+
const responseChecksumValidation = await config.responseChecksumValidation();
54+
55+
switch (requestChecksumCalculation) {
56+
case RequestChecksumCalculation.WHEN_REQUIRED:
57+
setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED", "a");
58+
break;
59+
case RequestChecksumCalculation.WHEN_SUPPORTED:
60+
setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED", "Z");
61+
break;
62+
}
63+
64+
switch (responseChecksumValidation) {
65+
case ResponseChecksumValidation.WHEN_REQUIRED:
66+
setFeature(context, "FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED", "c");
67+
break;
68+
case ResponseChecksumValidation.WHEN_SUPPORTED:
69+
setFeature(context, "FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED", "b");
70+
break;
71+
}
72+
73+
// The value for input member to opt-in to best-effort validation of a checksum returned in the HTTP response is not set.
74+
if (requestValidationModeMember && !input[requestValidationModeMember]) {
75+
// Set requestValidationModeMember as ENABLED only if response checksum validation is supported.
76+
if (responseChecksumValidation === ResponseChecksumValidation.WHEN_SUPPORTED) {
77+
input[requestValidationModeMember] = "ENABLED";
78+
}
79+
}
80+
81+
return next(args);
82+
};

Diff for: packages/middleware-flexible-checksums/src/flexibleChecksumsMiddleware.spec.ts

+54-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { BuildHandlerArguments } from "@smithy/types";
33
import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest";
44

55
import { PreviouslyResolved } from "./configuration";
6-
import { ChecksumAlgorithm } from "./constants";
6+
import { ChecksumAlgorithm, DEFAULT_CHECKSUM_ALGORITHM, RequestChecksumCalculation } from "./constants";
77
import { flexibleChecksumsMiddleware } from "./flexibleChecksumsMiddleware";
88
import { getChecksumAlgorithmForRequest } from "./getChecksumAlgorithmForRequest";
99
import { getChecksumLocationName } from "./getChecksumLocationName";
@@ -13,6 +13,7 @@ import { isStreaming } from "./isStreaming";
1313
import { selectChecksumAlgorithmFunction } from "./selectChecksumAlgorithmFunction";
1414
import { stringHasher } from "./stringHasher";
1515

16+
vi.mock("@aws-sdk/core");
1617
vi.mock("@smithy/protocol-http");
1718
vi.mock("./getChecksumAlgorithmForRequest");
1819
vi.mock("./getChecksumLocationName");
@@ -28,10 +29,14 @@ describe(flexibleChecksumsMiddleware.name, () => {
2829
const mockChecksum = "mockChecksum";
2930
const mockChecksumAlgorithmFunction = vi.fn();
3031
const mockChecksumLocationName = "mock-checksum-location-name";
32+
const mockRequestAlgorithmMember = "mockRequestAlgorithmMember";
33+
const mockRequestAlgorithmMemberHttpHeader = "mock-request-algorithm-member-http-header";
3134

3235
const mockInput = {};
33-
const mockConfig = {} as PreviouslyResolved;
34-
const mockMiddlewareConfig = { requestChecksumRequired: false };
36+
const mockConfig = {
37+
requestChecksumCalculation: () => Promise.resolve(RequestChecksumCalculation.WHEN_REQUIRED),
38+
} as PreviouslyResolved;
39+
const mockMiddlewareConfig = { input: mockInput, requestChecksumRequired: false };
3540

3641
const mockBody = { body: "mockRequestBody" };
3742
const mockHeaders = { "content-length": 100, "content-encoding": "gzip" };
@@ -41,9 +46,8 @@ describe(flexibleChecksumsMiddleware.name, () => {
4146

4247
beforeEach(() => {
4348
mockNext.mockResolvedValueOnce(mockResult);
44-
const { isInstance } = HttpRequest;
45-
(isInstance as unknown as any).mockReturnValue(true);
46-
vi.mocked(getChecksumAlgorithmForRequest).mockReturnValue(ChecksumAlgorithm.MD5);
49+
vi.mocked(HttpRequest.isInstance).mockReturnValue(true);
50+
vi.mocked(getChecksumAlgorithmForRequest).mockReturnValue(ChecksumAlgorithm.CRC32);
4751
vi.mocked(getChecksumLocationName).mockReturnValue(mockChecksumLocationName);
4852
vi.mocked(hasHeader).mockReturnValue(true);
4953
vi.mocked(hasHeaderWithPrefix).mockReturnValue(false);
@@ -58,8 +62,7 @@ describe(flexibleChecksumsMiddleware.name, () => {
5862

5963
describe("skips", () => {
6064
it("if not an instance of HttpRequest", async () => {
61-
const { isInstance } = HttpRequest;
62-
(isInstance as unknown as any).mockReturnValue(false);
65+
vi.mocked(HttpRequest.isInstance).mockReturnValue(false);
6366
const handler = flexibleChecksumsMiddleware(mockConfig, mockMiddlewareConfig)(mockNext, {});
6467
await handler(mockArgs);
6568
expect(getChecksumAlgorithmForRequest).not.toHaveBeenCalled();
@@ -77,7 +80,7 @@ describe(flexibleChecksumsMiddleware.name, () => {
7780
expect(getChecksumAlgorithmForRequest).toHaveBeenCalledTimes(1);
7881
});
7982

80-
it("if header is already present", async () => {
83+
it("skip if header is already present", async () => {
8184
const handler = flexibleChecksumsMiddleware(mockConfig, mockMiddlewareConfig)(mockNext, {});
8285
vi.mocked(hasHeaderWithPrefix).mockReturnValue(true);
8386

@@ -94,11 +97,53 @@ describe(flexibleChecksumsMiddleware.name, () => {
9497

9598
describe("adds checksum in the request header", () => {
9699
afterEach(() => {
100+
expect(HttpRequest.isInstance).toHaveBeenCalledTimes(1);
101+
expect(hasHeaderWithPrefix).toHaveBeenCalledTimes(1);
97102
expect(getChecksumAlgorithmForRequest).toHaveBeenCalledTimes(1);
98103
expect(getChecksumLocationName).toHaveBeenCalledTimes(1);
99104
expect(selectChecksumAlgorithmFunction).toHaveBeenCalledTimes(1);
100105
});
101106

107+
describe("if input.requestAlgorithmMember can be set", () => {
108+
describe("input[requestAlgorithmMember] is not defined and", () => {
109+
const mockMwConfigWithReqAlgoMember = {
110+
...mockMiddlewareConfig,
111+
requestAlgorithmMember: {
112+
name: mockRequestAlgorithmMember,
113+
httpHeader: mockRequestAlgorithmMemberHttpHeader,
114+
},
115+
};
116+
117+
it("requestChecksumCalculation is supported", async () => {
118+
const handler = flexibleChecksumsMiddleware(
119+
{
120+
...mockConfig,
121+
requestChecksumCalculation: () => Promise.resolve(RequestChecksumCalculation.WHEN_SUPPORTED),
122+
},
123+
mockMwConfigWithReqAlgoMember
124+
)(mockNext, {});
125+
await handler(mockArgs);
126+
expect(mockNext.mock.calls[0][0].input[mockRequestAlgorithmMember]).toEqual(DEFAULT_CHECKSUM_ALGORITHM);
127+
expect(mockNext.mock.calls[0][0].request.headers[mockRequestAlgorithmMemberHttpHeader]).toEqual(
128+
DEFAULT_CHECKSUM_ALGORITHM
129+
);
130+
});
131+
132+
it("requestChecksumRequired is set to true", async () => {
133+
const handler = flexibleChecksumsMiddleware(mockConfig, {
134+
...mockMwConfigWithReqAlgoMember,
135+
requestChecksumRequired: true,
136+
})(mockNext, {});
137+
138+
await handler(mockArgs);
139+
expect(mockNext.mock.calls[0][0].input[mockRequestAlgorithmMember]).toEqual(DEFAULT_CHECKSUM_ALGORITHM);
140+
expect(mockNext.mock.calls[0][0].request.headers[mockRequestAlgorithmMemberHttpHeader]).toEqual(
141+
DEFAULT_CHECKSUM_ALGORITHM
142+
);
143+
});
144+
});
145+
});
146+
102147
it("for streaming body", async () => {
103148
vi.mocked(isStreaming).mockReturnValue(true);
104149
const mockUpdatedBody = { body: "mockUpdatedBody" };

Diff for: packages/middleware-flexible-checksums/src/flexibleChecksumsMiddleware.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from "@smithy/types";
1212

1313
import { PreviouslyResolved } from "./configuration";
14-
import { ChecksumAlgorithm } from "./constants";
14+
import { ChecksumAlgorithm, DEFAULT_CHECKSUM_ALGORITHM, RequestChecksumCalculation } from "./constants";
1515
import { getChecksumAlgorithmForRequest } from "./getChecksumAlgorithmForRequest";
1616
import { getChecksumLocationName } from "./getChecksumLocationName";
1717
import { hasHeader } from "./hasHeader";
@@ -73,10 +73,26 @@ export const flexibleChecksumsMiddleware =
7373
const { body: requestBody, headers } = request;
7474
const { base64Encoder, streamHasher } = config;
7575
const { requestChecksumRequired, requestAlgorithmMember } = middlewareConfig;
76+
const requestChecksumCalculation = await config.requestChecksumCalculation();
77+
78+
const requestAlgorithmMemberName = requestAlgorithmMember?.name;
79+
const requestAlgorithmMemberHttpHeader = requestAlgorithmMember?.httpHeader;
80+
// The value for input member to configure flexible checksum is not set.
81+
if (requestAlgorithmMemberName && !input[requestAlgorithmMemberName]) {
82+
// Set requestAlgorithmMember as default checksum algorithm only if request checksum calculation is supported
83+
// or request checksum is required.
84+
if (requestChecksumCalculation === RequestChecksumCalculation.WHEN_SUPPORTED || requestChecksumRequired) {
85+
input[requestAlgorithmMemberName] = DEFAULT_CHECKSUM_ALGORITHM;
86+
if (requestAlgorithmMemberHttpHeader) {
87+
headers[requestAlgorithmMemberHttpHeader] = DEFAULT_CHECKSUM_ALGORITHM;
88+
}
89+
}
90+
}
7691

7792
const checksumAlgorithm = getChecksumAlgorithmForRequest(input, {
7893
requestChecksumRequired,
7994
requestAlgorithmMember: requestAlgorithmMember?.name,
95+
requestChecksumCalculation,
8096
});
8197
let updatedBody = requestBody;
8298
let updatedHeaders = headers;

0 commit comments

Comments
 (0)