From cce84bfad6fe15c1fc177e3f72f6d2b182e47208 Mon Sep 17 00:00:00 2001 From: yibole Date: Mon, 18 Aug 2025 09:22:17 -0700 Subject: [PATCH 1/2] allowlist for copy request added --- .../multipart/SdkPojoConversionUtils.java | 152 ++++++++++++++++-- 1 file changed, 135 insertions(+), 17 deletions(-) diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/multipart/SdkPojoConversionUtils.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/multipart/SdkPojoConversionUtils.java index 1e35cfde2a74..8ebd24126d2f 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/multipart/SdkPojoConversionUtils.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/multipart/SdkPojoConversionUtils.java @@ -24,6 +24,7 @@ import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.core.SdkField; import software.amazon.awssdk.core.SdkPojo; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest; import software.amazon.awssdk.services.s3.model.ChecksumType; import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest; @@ -53,12 +54,104 @@ public final class SdkPojoConversionUtils { new HashSet<>(Arrays.asList("ChecksumSHA1", "ChecksumSHA256", "ContentMD5", "ChecksumCRC32C", "ChecksumCRC32", "ChecksumCRC64NVME", "ContentLength")); + private static final Set PUT_OBJECT_TO_UPLOAD_PART_ALLOWED_FIELDS = new HashSet<>(Arrays.asList( + "ACL", + "Bucket", + "CacheControl", + "ContentDisposition", + "ContentEncoding", + "ContentLanguage", + "ContentLength", + "ContentMD5", + "ContentType", + "ChecksumAlgorithm", + "ChecksumCRC32", + "ChecksumCRC32C", + "ChecksumCRC64NVME", + "ChecksumSHA1", + "ChecksumSHA256", + "Expires", + "IfMatch", + "IfNoneMatch", + "GrantFullControl", + "GrantRead", + "GrantReadACP", + "GrantWriteACP", + "Key", + "WriteOffsetBytes", + "Metadata", + "ServerSideEncryption", + "StorageClass", + "WebsiteRedirectLocation", + "SSECustomerAlgorithm", + "SSECustomerKey", + "SSECustomerKeyMD5", + "SSEKMSKeyId", + "SSEKMSEncryptionContext", + "BucketKeyEnabled", + "RequestPayer", + "Tagging", + "ObjectLockMode", + "ObjectLockRetainUntilDate", + "ObjectLockLegalHoldStatus", + "ExpectedBucketOwner" + )); + + private static final Set COPY_OBJECT_TO_COPY_OBJECT_ALLOWED_FIELDS = new HashSet<>(Arrays.asList( + "ACL", + "CacheControl", + "ChecksumAlgorithm", + "ContentDisposition", + "ContentEncoding", + "ContentLanguage", + "ContentType", + "CopySource", + "CopySourceIfMatch", + "CopySourceIfModifiedSince", + "CopySourceIfNoneMatch", + "CopySourceIfUnmodifiedSince", + "CopySourceSSECustomerAlgorithm", + "CopySourceSSECustomerKey", + "CopySourceSSECustomerKeyMD5", + "DestinationBucket", + "DestinationKey", + "Expires", + "GrantFullControl", + "GrantRead", + "GrantReadACP", + "GrantWriteACP", + "Metadata", + "MetadataDirective", + "TaggingDirective", + "ServerSideEncryption", + "SourceBucket", + "SourceKey", + "SourceVersionId", + "StorageClass", + "WebsiteRedirectLocation", + "SSECustomerAlgorithm", + "SSECustomerKey", + "SSECustomerKeyMD5", + "SSEKMSKeyId", + "SSEKMSEncryptionContext", + "BucketKeyEnabled", + "RequestPayer", + "Tagging", + "ObjectLockMode", + "ObjectLockRetainUntilDate", + "ObjectLockLegalHoldStatus", + "ExpectedBucketOwner", + "ExpectedSourceBucketOwner" + )); + + private SdkPojoConversionUtils() { } public static UploadPartRequest toUploadPartRequest(PutObjectRequest putObjectRequest, int partNumber, String uploadId) { UploadPartRequest.Builder builder = UploadPartRequest.builder(); + validateRequestFields(putObjectRequest, PUT_OBJECT_TO_UPLOAD_PART_ALLOWED_FIELDS); setSdkFields(builder, putObjectRequest, PUT_OBJECT_REQUEST_TO_UPLOAD_PART_FIELDS_TO_IGNORE); return builder.uploadId(uploadId).partNumber(partNumber).build(); } @@ -67,6 +160,7 @@ public static CompleteMultipartUploadRequest toCompleteMultipartUploadRequest(Pu String uploadId, CompletedPart[] parts, long contentLength) { CompleteMultipartUploadRequest.Builder builder = CompleteMultipartUploadRequest.builder(); + validateRequestFields(putObjectRequest, PUT_OBJECT_TO_UPLOAD_PART_ALLOWED_FIELDS); setSdkFields(builder, putObjectRequest); builder.mpuObjectSize(contentLength); @@ -81,6 +175,7 @@ public static CompleteMultipartUploadRequest toCompleteMultipartUploadRequest(Pu public static CreateMultipartUploadRequest toCreateMultipartUploadRequest(PutObjectRequest putObjectRequest) { CreateMultipartUploadRequest.Builder builder = CreateMultipartUploadRequest.builder(); + validateRequestFields(putObjectRequest, PUT_OBJECT_TO_UPLOAD_PART_ALLOWED_FIELDS); setSdkFields(builder, putObjectRequest); if (S3ChecksumUtils.checksumValueSpecified(putObjectRequest)) { @@ -130,29 +225,14 @@ public static CompletedPart toCompletedPart(Part part) { public static ListPartsRequest toListPartsRequest(String uploadId, PutObjectRequest putObjectRequest) { ListPartsRequest.Builder builder = ListPartsRequest.builder(); + validateRequestFields(putObjectRequest, PUT_OBJECT_TO_UPLOAD_PART_ALLOWED_FIELDS); setSdkFields(builder, putObjectRequest); return builder.uploadId(uploadId).build(); } - private static void setSdkFields(SdkPojo targetBuilder, SdkPojo sourceObject) { - setSdkFields(targetBuilder, sourceObject, new HashSet<>()); - } - - private static void setSdkFields(SdkPojo targetBuilder, SdkPojo sourceObject, Set fieldsToIgnore) { - Map sourceFields = retrieveSdkFields(sourceObject, sourceObject.sdkFields()); - List> targetSdkFields = targetBuilder.sdkFields(); - - for (SdkField field : targetSdkFields) { - if (fieldsToIgnore.contains(field.memberName())) { - continue; - } - field.set(targetBuilder, sourceFields.getOrDefault(field.memberName(), null)); - } - } - public static CreateMultipartUploadRequest toCreateMultipartUploadRequest(CopyObjectRequest copyObjectRequest) { CreateMultipartUploadRequest.Builder builder = CreateMultipartUploadRequest.builder(); - + validateRequestFields(copyObjectRequest, COPY_OBJECT_TO_COPY_OBJECT_ALLOWED_FIELDS); setSdkFields(builder, copyObjectRequest); builder.bucket(copyObjectRequest.destinationBucket()); builder.key(copyObjectRequest.destinationKey()); @@ -180,6 +260,7 @@ private static CopyObjectResult toCopyObjectResult(CompleteMultipartUploadRespon public static AbortMultipartUploadRequest.Builder toAbortMultipartUploadRequest(CopyObjectRequest copyObjectRequest) { AbortMultipartUploadRequest.Builder builder = AbortMultipartUploadRequest.builder(); + validateRequestFields(copyObjectRequest, COPY_OBJECT_TO_COPY_OBJECT_ALLOWED_FIELDS); setSdkFields(builder, copyObjectRequest); builder.bucket(copyObjectRequest.destinationBucket()); builder.key(copyObjectRequest.destinationKey()); @@ -188,6 +269,7 @@ public static AbortMultipartUploadRequest.Builder toAbortMultipartUploadRequest( public static AbortMultipartUploadRequest.Builder toAbortMultipartUploadRequest(PutObjectRequest putObjectRequest) { AbortMultipartUploadRequest.Builder builder = AbortMultipartUploadRequest.builder(); + validateRequestFields(putObjectRequest, PUT_OBJECT_TO_UPLOAD_PART_ALLOWED_FIELDS); setSdkFields(builder, putObjectRequest); return builder; } @@ -225,4 +307,40 @@ private static Map retrieveSdkFields(SdkPojo sourceObject, List< field.getValueOrDefault(sourceObject)), Map::putAll); } + + private static void setSdkFields(SdkPojo targetBuilder, SdkPojo sourceObject) { + setSdkFields(targetBuilder, sourceObject, new HashSet<>()); + } + + private static void setSdkFields(SdkPojo targetBuilder, SdkPojo sourceObject, Set fieldsToIgnore) { + Map sourceFields = retrieveSdkFields(sourceObject, sourceObject.sdkFields()); + List> targetSdkFields = targetBuilder.sdkFields(); + + for (SdkField field : targetSdkFields) { + if (fieldsToIgnore.contains(field.memberName())) { + continue; + } + field.set(targetBuilder, sourceFields.getOrDefault(field.memberName(), null)); + } + } + + private static void validateRequestFields(SdkPojo sourceObject, Set allowedFields) { + Set invalidFields = new HashSet<>(); + + for (SdkField sourceField : sourceObject.sdkFields()) { + String fieldName = sourceField.memberName(); + Object rawValue = sourceField.getValueOrDefault(sourceObject); + + if (rawValue != null && !allowedFields.contains(fieldName)) { + invalidFields.add(fieldName); + } + } + + if (!invalidFields.isEmpty()) { + throw SdkClientException.create( + String.format("The following fields are not allowed: %s", + String.join(", ", invalidFields)) + ); + } + } } From c1032841b5da1acafd42616153fca0cac77dfff0 Mon Sep 17 00:00:00 2001 From: yibole Date: Mon, 18 Aug 2025 09:31:57 -0700 Subject: [PATCH 2/2] changelog added --- .changes/next-release/bugfix-AWSSDKforJavav2-17f90b1.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/next-release/bugfix-AWSSDKforJavav2-17f90b1.json diff --git a/.changes/next-release/bugfix-AWSSDKforJavav2-17f90b1.json b/.changes/next-release/bugfix-AWSSDKforJavav2-17f90b1.json new file mode 100644 index 000000000000..ff42b32280e8 --- /dev/null +++ b/.changes/next-release/bugfix-AWSSDKforJavav2-17f90b1.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Added allowlist when copying PutObjectRequest and CopyObjectRequest to transfer manager request" +}