Skip to content

Commit 55bdcb8

Browse files
committed
Presigned URL checksums
1 parent 1aefe66 commit 55bdcb8

File tree

2 files changed

+110
-6
lines changed

2 files changed

+110
-6
lines changed

codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/PresignerGenerator.kt

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,32 @@
55
package aws.sdk.kotlin.codegen
66

77
import aws.sdk.kotlin.codegen.model.traits.Presignable
8+
import software.amazon.smithy.aws.traits.HttpChecksumTrait
89
import software.amazon.smithy.aws.traits.auth.SigV4Trait
910
import software.amazon.smithy.aws.traits.protocols.AwsQueryTrait
1011
import software.amazon.smithy.codegen.core.Symbol
1112
import software.amazon.smithy.kotlin.codegen.core.*
13+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Core.Hashing.isSupportedForFlexibleChecksums
14+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Core.Hashing.toHashFunctionOrThrow
15+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Core.IllegalStateException
16+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Core.Text.Encoding.encodeBase64String
17+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Core.Text.lowercase
18+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Core.Utils.runBlocking
19+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Http.HttpBody
20+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Http.readAll
21+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.KotlinCoroutines.coroutineContext
22+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Observability.TelemetryApi.warn
1223
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
1324
import software.amazon.smithy.kotlin.codegen.integration.SectionId
1425
import software.amazon.smithy.kotlin.codegen.integration.SectionKey
1526
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
16-
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
17-
import software.amazon.smithy.kotlin.codegen.model.expectShape
18-
import software.amazon.smithy.kotlin.codegen.model.getTrait
27+
import software.amazon.smithy.kotlin.codegen.model.*
1928
import software.amazon.smithy.kotlin.codegen.model.knowledge.AwsSignatureVersion4
2029
import software.amazon.smithy.kotlin.codegen.rendering.endpoints.EndpointResolverAdapterGenerator
2130
import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingProtocolGenerator
2231
import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingResolver
2332
import software.amazon.smithy.kotlin.codegen.rendering.serde.serializerName
33+
import software.amazon.smithy.kotlin.codegen.utils.getOrNull
2434
import software.amazon.smithy.model.knowledge.TopDownIndex
2535
import software.amazon.smithy.model.shapes.OperationShape
2636
import software.amazon.smithy.model.shapes.ServiceShape
@@ -149,6 +159,7 @@ class PresignerGenerator : KotlinIntegration {
149159
requestSymbol,
150160
serializerSymbol,
151161
contextMap,
162+
op,
152163
)
153164
}
154165
}
@@ -193,6 +204,7 @@ class PresignerGenerator : KotlinIntegration {
193204
requestSymbol: Symbol,
194205
serializerSymbol: Symbol,
195206
contextMap: Map<SectionKey<*>, Any>,
207+
op: OperationShape,
196208
) = writer.apply {
197209
dokka {
198210
write("Presign a [#T] using the configuration of this [#T].", requestSymbol, serviceSymbol)
@@ -265,6 +277,45 @@ class PresignerGenerator : KotlinIntegration {
265277
)
266278
}
267279

280+
checksumAlgorithmMember(op, ctx)?.let { checksumAlgorithmMember ->
281+
withBlock("input.#L?.value?.let { checksumAlgorithmString ->", "}", checksumAlgorithmMember) {
282+
withBlock("when (unsignedRequest.body) {", "}") {
283+
withBlock("is #1T.Bytes, is #1T.Empty -> {", "}", HttpBody) {
284+
write("val checksumAlgorithm = checksumAlgorithmString.#T()", toHashFunctionOrThrow)
285+
withInlineBlock(
286+
"if (checksumAlgorithm.#T) {",
287+
"}",
288+
isSupportedForFlexibleChecksums,
289+
) {
290+
withBlock("#T {", "}", runBlocking) {
291+
withBlock("checksumAlgorithm.update(", ")") {
292+
write("unsignedRequest.body.#T() ?: byteArrayOf()", readAll)
293+
}
294+
}
295+
write(
296+
"checksum = #S.#T() to checksumAlgorithm.digest().#T()",
297+
"x-amz-checksum-\${checksumAlgorithmString}",
298+
lowercase,
299+
encodeBase64String,
300+
)
301+
}
302+
withBlock(" else {", "}") {
303+
withBlock("#T {", "}", runBlocking) {
304+
write("class Presigner")
305+
write(
306+
"#T.#T<Presigner> { #S }",
307+
coroutineContext,
308+
warn,
309+
"The requested checksum algorithm is not supported for pre-signed URL checksums, sending request without checksum.",
310+
)
311+
}
312+
}
313+
}
314+
write("else -> throw #T(#S)", IllegalStateException, "HTTP body type unsupported for pre-signed URL checksums.")
315+
}
316+
}
317+
}
318+
268319
declareSection(SigningConfigCustomizationSection)
269320

270321
write("configBlock()")
@@ -287,4 +338,24 @@ class PresignerGenerator : KotlinIntegration {
287338
* > "my-object/example/photo.user". This is an incorrect path for that object.
288339
*/
289340
private fun normalizeUriPath(service: ServiceShape) = service.sdkId != "S3"
341+
342+
/**
343+
* Gets the checksum algorithm member if a user can configure request checksums otherwise null
344+
*/
345+
private fun checksumAlgorithmMember(
346+
operationShape: OperationShape,
347+
ctx: CodegenContext,
348+
): String? {
349+
operationShape.getTrait<HttpChecksumTrait>()?.let { httpChecksumTrait ->
350+
httpChecksumTrait.requestAlgorithmMember.getOrNull()?.let { requestAlgorithmMember ->
351+
val memberShape = ctx.model
352+
.expectShape<StructureShape>(operationShape.input.get())
353+
.members()
354+
.first { it.memberName == requestAlgorithmMember }
355+
356+
return ctx.symbolProvider.toMemberName(memberShape)
357+
}
358+
}
359+
return null
360+
}
290361
}

services/s3/e2eTest/src/S3ChecksumTest.kt

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import aws.sdk.kotlin.e2etest.S3TestUtils.deleteBucketContents
44
import aws.sdk.kotlin.e2etest.S3TestUtils.deleteMultiPartUploads
55
import aws.sdk.kotlin.e2etest.S3TestUtils.getAccountId
66
import aws.sdk.kotlin.e2etest.S3TestUtils.getBucketByName
7+
import aws.sdk.kotlin.e2etest.S3TestUtils.responseCodeFromPut
78
import aws.sdk.kotlin.services.s3.*
8-
import aws.sdk.kotlin.services.s3.model.CompletedMultipartUpload
9-
import aws.sdk.kotlin.services.s3.model.CompletedPart
10-
import aws.sdk.kotlin.services.s3.model.GetObjectRequest
9+
import aws.sdk.kotlin.services.s3.model.*
10+
import aws.sdk.kotlin.services.s3.presigners.presignPutObject
1111
import aws.smithy.kotlin.runtime.content.*
1212
import aws.smithy.kotlin.runtime.hashing.crc32
1313
import aws.smithy.kotlin.runtime.testing.RandomTempFile
@@ -16,7 +16,11 @@ import org.junit.jupiter.api.*
1616
import java.io.File
1717
import java.io.FileInputStream
1818
import java.util.*
19+
import kotlin.test.Ignore
1920
import kotlin.test.assertEquals
21+
import kotlin.test.assertFalse
22+
import kotlin.test.assertTrue
23+
import kotlin.time.Duration.Companion.seconds
2024

2125
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
2226
class S3ChecksumTest {
@@ -159,4 +163,33 @@ class S3ChecksumTest {
159163
assertEquals(actualChecksum, expectedChecksum)
160164
}
161165
}
166+
167+
@Test
168+
fun testPresignedUrlNoDefault() = runBlocking {
169+
val unsignedPutRequest = PutObjectRequest {
170+
bucket = testBucket
171+
key = testKey()
172+
}
173+
val presignedPutRequest = client.presignPutObject(unsignedPutRequest, 60.seconds)
174+
val contents = "presign-test"
175+
176+
assertFalse(presignedPutRequest.url.toString().contains("x-amz-checksum-crc32"))
177+
assertTrue(responseCodeFromPut(presignedPutRequest, contents) in 200..299)
178+
}
179+
180+
@Test
181+
@Ignore
182+
// FIXME: Sending checksum via query params should work
183+
fun testPresignedUrlContainsChecksum() = runBlocking {
184+
val unsignedPutRequest = PutObjectRequest {
185+
bucket = testBucket
186+
key = testKey()
187+
checksumAlgorithm = ChecksumAlgorithm.Crc32
188+
}
189+
val presignedPutRequest = client.presignPutObject(unsignedPutRequest, 60.seconds)
190+
val contents = "presign-test"
191+
192+
assertTrue(presignedPutRequest.url.toString().contains("x-amz-checksum-crc32"))
193+
assertTrue(responseCodeFromPut(presignedPutRequest, contents) in 200..299)
194+
}
162195
}

0 commit comments

Comments
 (0)