From dfda12916381fbebd967cd4f58915c852020f9d6 Mon Sep 17 00:00:00 2001 From: George Fu Date: Fri, 4 Oct 2024 15:45:11 -0400 Subject: [PATCH] chore(middleware-user-agent): add feature detection for account-id, s3-express, lib-dynamodb (#6541) --- .../DynamoDBDocumentClientCommand.ts | 2 + .../package.json | 1 + .../src/flexibleChecksumsMiddleware.ts | 16 ++++++++ ...iddleware-flexible-checksums.integ.spec.ts | 30 +++++++++++++- .../functions/s3ExpressMiddleware.ts | 2 + .../middleware-s3-express.integ.spec.ts | 17 ++++++++ packages/middleware-user-agent/package.json | 1 + .../src/check-features.ts | 39 +++++++++++++++++++ .../src/configurations.ts | 2 +- .../src/middleware-user-agent.integ.spec.ts | 31 +++++++++++++++ .../src/user-agent-middleware.ts | 4 ++ .../src/requests/test-http-handler.ts | 2 +- 12 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 packages/middleware-user-agent/src/check-features.ts diff --git a/lib/lib-dynamodb/src/baseCommand/DynamoDBDocumentClientCommand.ts b/lib/lib-dynamodb/src/baseCommand/DynamoDBDocumentClientCommand.ts index 94cf791d1488..2ccf963df43d 100644 --- a/lib/lib-dynamodb/src/baseCommand/DynamoDBDocumentClientCommand.ts +++ b/lib/lib-dynamodb/src/baseCommand/DynamoDBDocumentClientCommand.ts @@ -1,3 +1,4 @@ +import { setFeature } from "@aws-sdk/core"; import { Command as $Command } from "@smithy/smithy-client"; import { DeserializeHandler, @@ -51,6 +52,7 @@ export abstract class DynamoDBDocumentClientCommand< async ( args: InitializeHandlerArguments ): Promise> => { + setFeature(context, "DDB_MAPPER", "d"); args.input = marshallInput(this.input, this.inputKeyNodes, marshallOptions); context.dynamoDbDocumentClientOptions = context.dynamoDbDocumentClientOptions || DynamoDBDocumentClientCommand.defaultLogFilterOverrides; diff --git a/packages/middleware-flexible-checksums/package.json b/packages/middleware-flexible-checksums/package.json index c01828292d46..69dfa2c37a69 100644 --- a/packages/middleware-flexible-checksums/package.json +++ b/packages/middleware-flexible-checksums/package.json @@ -30,6 +30,7 @@ "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", + "@aws-sdk/core": "*", "@aws-sdk/types": "*", "@smithy/is-array-buffer": "^3.0.0", "@smithy/node-config-provider": "^3.1.8", diff --git a/packages/middleware-flexible-checksums/src/flexibleChecksumsMiddleware.ts b/packages/middleware-flexible-checksums/src/flexibleChecksumsMiddleware.ts index 5f426f35050c..cc5efb32212a 100644 --- a/packages/middleware-flexible-checksums/src/flexibleChecksumsMiddleware.ts +++ b/packages/middleware-flexible-checksums/src/flexibleChecksumsMiddleware.ts @@ -1,3 +1,4 @@ +import { setFeature } from "@aws-sdk/core"; import { HttpRequest } from "@smithy/protocol-http"; import { BuildHandler, @@ -10,6 +11,7 @@ import { } from "@smithy/types"; import { PreviouslyResolved } from "./configuration"; +import { ChecksumAlgorithm } from "./constants"; import { getChecksumAlgorithmForRequest } from "./getChecksumAlgorithmForRequest"; import { getChecksumLocationName } from "./getChecksumLocationName"; import { hasHeader } from "./hasHeader"; @@ -72,6 +74,20 @@ export const flexibleChecksumsMiddleware = let updatedHeaders = headers; if (checksumAlgorithm) { + switch (checksumAlgorithm) { + case ChecksumAlgorithm.CRC32: + setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_CRC32", "U"); + break; + case ChecksumAlgorithm.CRC32C: + setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_CRC32C", "V"); + break; + case ChecksumAlgorithm.SHA1: + setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_SHA1", "X"); + break; + case ChecksumAlgorithm.SHA256: + setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_SHA256", "Y"); + break; + } const checksumLocationName = getChecksumLocationName(checksumAlgorithm); const checksumAlgorithmFn = selectChecksumAlgorithmFunction(checksumAlgorithm, config); if (isStreaming(requestBody)) { diff --git a/packages/middleware-flexible-checksums/src/middleware-flexible-checksums.integ.spec.ts b/packages/middleware-flexible-checksums/src/middleware-flexible-checksums.integ.spec.ts index f65c31caf8cb..027e8ad74b1f 100644 --- a/packages/middleware-flexible-checksums/src/middleware-flexible-checksums.integ.spec.ts +++ b/packages/middleware-flexible-checksums/src/middleware-flexible-checksums.integ.spec.ts @@ -1,4 +1,4 @@ -import { S3 } from "@aws-sdk/client-s3"; +import { ChecksumAlgorithm, S3 } from "@aws-sdk/client-s3"; import { Transform } from "stream"; import { requireRequestsFrom } from "../../../private/aws-util-test/src"; @@ -124,5 +124,33 @@ describe("middleware-flexible-checksums", () => { expect.hasAssertions(); }); + + describe("features", () => { + [ + ["SHA256", "Y"], + ["SHA1", "X"], + ["CRC32", "U"], + ["CRC32C", "V"], + ].forEach(([algo, id]) => { + it(`should feature-detect checksum ${algo}=${id}`, async () => { + const client = new S3({ region: "us-west-2", logger }); + + requireRequestsFrom(client).toMatch({ + headers: { + "user-agent": new RegExp(`(.*?) m\/${id}$`), + }, + }); + + await client.putObject({ + Bucket: "b", + Key: "k", + Body: "abcd", + ChecksumAlgorithm: algo as ChecksumAlgorithm, + }); + + expect.hasAssertions(); + }); + }); + }); }); }); diff --git a/packages/middleware-sdk-s3/src/s3-express/functions/s3ExpressMiddleware.ts b/packages/middleware-sdk-s3/src/s3-express/functions/s3ExpressMiddleware.ts index 57fa6e1b9d25..32ab90d3d738 100644 --- a/packages/middleware-sdk-s3/src/s3-express/functions/s3ExpressMiddleware.ts +++ b/packages/middleware-sdk-s3/src/s3-express/functions/s3ExpressMiddleware.ts @@ -1,3 +1,4 @@ +import { setFeature } from "@aws-sdk/core"; import { AwsCredentialIdentity } from "@aws-sdk/types"; import { HttpRequest } from "@smithy/protocol-http"; import { @@ -51,6 +52,7 @@ export const s3ExpressMiddleware: (options: S3ExpressResolvedConfig) => BuildMid endpoint.properties?.bucketType === S3_EXPRESS_BUCKET_TYPE; if (isS3ExpressBucket) { + setFeature(context, "S3_EXPRESS_BUCKET", "J"); context.isS3ExpressBucket = true; } diff --git a/packages/middleware-sdk-s3/src/s3-express/middleware-s3-express.integ.spec.ts b/packages/middleware-sdk-s3/src/s3-express/middleware-s3-express.integ.spec.ts index d99fb1b37f24..a73facfe5d8c 100644 --- a/packages/middleware-sdk-s3/src/s3-express/middleware-s3-express.integ.spec.ts +++ b/packages/middleware-sdk-s3/src/s3-express/middleware-s3-express.integ.spec.ts @@ -84,5 +84,22 @@ describe("middleware-s3-express", () => { expect.hasAssertions(); }); + + it("should feature-detect S3 express bucket", async () => { + const client = new S3({ + region: "us-west-2", + s3ExpressIdentityProvider, + }); + + requireRequestsFrom(client).toMatch({ + headers: { + "user-agent": /(.*?) m\/J$/, + }, + }); + + await client.headBucket({ + Bucket: "aws-sdk-js-v3-test--usw2-az1--x-s3", + }); + }); }); }); diff --git a/packages/middleware-user-agent/package.json b/packages/middleware-user-agent/package.json index 496ebecd6241..f44013005ed4 100644 --- a/packages/middleware-user-agent/package.json +++ b/packages/middleware-user-agent/package.json @@ -22,6 +22,7 @@ }, "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "*", "@aws-sdk/types": "*", "@aws-sdk/util-endpoints": "*", "@smithy/core": "^2.4.8", diff --git a/packages/middleware-user-agent/src/check-features.ts b/packages/middleware-user-agent/src/check-features.ts new file mode 100644 index 000000000000..c47446aba3cd --- /dev/null +++ b/packages/middleware-user-agent/src/check-features.ts @@ -0,0 +1,39 @@ +import { setFeature } from "@aws-sdk/core"; +import type { AccountIdEndpointMode } from "@aws-sdk/core/account-id-endpoint"; +import type { AwsHandlerExecutionContext } from "@aws-sdk/types"; +import type { IHttpRequest } from "@smithy/protocol-http"; +import type { BuildHandlerArguments, Provider } from "@smithy/types"; + +/** + * @internal + */ +type PreviouslyResolved = Partial<{ + accountIdEndpointMode?: Provider; +}>; + +/** + * @internal + * Check for features that don't have a middleware activation site but + * may be detected on the context, client config, or request. + */ +export async function checkFeatures( + context: AwsHandlerExecutionContext, + config: PreviouslyResolved, + args: BuildHandlerArguments +): Promise { + // eslint-disable-next-line + const request = args.request as IHttpRequest; + if (typeof config.accountIdEndpointMode === "function") { + switch (await config.accountIdEndpointMode?.()) { + case "disabled": + setFeature(context, "ACCOUNT_ID_MODE_DISABLED", "Q"); + break; + case "preferred": + setFeature(context, "ACCOUNT_ID_MODE_PREFERRED", "P"); + break; + case "required": + setFeature(context, "ACCOUNT_ID_MODE_REQUIRED", "R"); + break; + } + } +} diff --git a/packages/middleware-user-agent/src/configurations.ts b/packages/middleware-user-agent/src/configurations.ts index e778e81779d3..af973ba931d5 100644 --- a/packages/middleware-user-agent/src/configurations.ts +++ b/packages/middleware-user-agent/src/configurations.ts @@ -1,5 +1,5 @@ -import { Logger, Provider, UserAgent } from "@smithy/types"; import { normalizeProvider } from "@smithy/core"; +import { Logger, Provider, UserAgent } from "@smithy/types"; /** * @internal diff --git a/packages/middleware-user-agent/src/middleware-user-agent.integ.spec.ts b/packages/middleware-user-agent/src/middleware-user-agent.integ.spec.ts index aa2c67fef18c..4b8370263826 100644 --- a/packages/middleware-user-agent/src/middleware-user-agent.integ.spec.ts +++ b/packages/middleware-user-agent/src/middleware-user-agent.integ.spec.ts @@ -1,4 +1,7 @@ import { CodeCatalyst } from "@aws-sdk/client-codecatalyst"; +import { DynamoDB } from "@aws-sdk/client-dynamodb"; +import { S3 } from "@aws-sdk/client-s3"; +import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"; import { requireRequestsFrom } from "../../../private/aws-util-test/src"; @@ -23,4 +26,32 @@ describe("middleware-user-agent", () => { }); }); }); + + describe("features", () => { + it("should detect DDB mapper, and account id mode", async () => { + const client = new DynamoDB({ + credentials: { + accessKeyId: "", + secretAccessKey: "", + accountId: "123", + }, + accountIdEndpointMode: async () => "preferred" as const, + }); + + const doc = DynamoDBDocument.from(client); + + requireRequestsFrom(doc).toMatch({ + headers: { + "user-agent": /(.*?) m\/d,P$/, + }, + }); + + await doc.get({ + TableName: "table", + Key: { + id: "1", + }, + }); + }); + }); }); diff --git a/packages/middleware-user-agent/src/user-agent-middleware.ts b/packages/middleware-user-agent/src/user-agent-middleware.ts index 260151a13cb5..4438ede668d0 100644 --- a/packages/middleware-user-agent/src/user-agent-middleware.ts +++ b/packages/middleware-user-agent/src/user-agent-middleware.ts @@ -13,6 +13,7 @@ import { UserAgentPair, } from "@smithy/types"; +import { checkFeatures } from "./check-features"; import { UserAgentResolvedConfig } from "./configurations"; import { SPACE, @@ -51,12 +52,15 @@ export const userAgentMiddleware = const { headers } = request; const userAgent = context?.userAgent?.map(escapeUserAgent) || []; const defaultUserAgent = (await options.defaultUserAgentProvider()).map(escapeUserAgent); + + await checkFeatures(context, options as any, args); const awsContext = context as AwsHandlerExecutionContext; defaultUserAgent.push( `m/${encodeFeatures( Object.assign({}, context.__smithy_context?.features, awsContext.__aws_sdk_context?.features) )}` ); + const customUserAgent = options?.customUserAgent?.map(escapeUserAgent) || []; const appId = await options.userAgentAppId(); if (appId) { diff --git a/private/aws-util-test/src/requests/test-http-handler.ts b/private/aws-util-test/src/requests/test-http-handler.ts index 8187924f1fd3..0be329ed7ddb 100644 --- a/private/aws-util-test/src/requests/test-http-handler.ts +++ b/private/aws-util-test/src/requests/test-http-handler.ts @@ -56,7 +56,7 @@ export class TestHttpHandler implements HttpHandler { */ public watch(client: Client, matcher: HttpRequestMatcher = this.matcher) { this.client = client; - this.originalRequestHandler = client.config.originalRequestHandler; + this.originalRequestHandler = client.config.requestHandler; // mock credentials to avoid default chain lookup. client.config.credentials = async () => MOCK_CREDENTIALS; client.config.credentialDefaultProvider = () => {