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" +} 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..af815f6b5478 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 @@ -20,10 +20,12 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; 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 +55,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, builder.build(), 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 +161,7 @@ public static CompleteMultipartUploadRequest toCompleteMultipartUploadRequest(Pu String uploadId, CompletedPart[] parts, long contentLength) { CompleteMultipartUploadRequest.Builder builder = CompleteMultipartUploadRequest.builder(); + validateRequestFields(putObjectRequest, builder.build(), PUT_OBJECT_TO_UPLOAD_PART_ALLOWED_FIELDS); setSdkFields(builder, putObjectRequest); builder.mpuObjectSize(contentLength); @@ -81,6 +176,7 @@ public static CompleteMultipartUploadRequest toCompleteMultipartUploadRequest(Pu public static CreateMultipartUploadRequest toCreateMultipartUploadRequest(PutObjectRequest putObjectRequest) { CreateMultipartUploadRequest.Builder builder = CreateMultipartUploadRequest.builder(); + validateRequestFields(putObjectRequest, builder.build(), PUT_OBJECT_TO_UPLOAD_PART_ALLOWED_FIELDS); setSdkFields(builder, putObjectRequest); if (S3ChecksumUtils.checksumValueSpecified(putObjectRequest)) { @@ -130,29 +226,14 @@ public static CompletedPart toCompletedPart(Part part) { public static ListPartsRequest toListPartsRequest(String uploadId, PutObjectRequest putObjectRequest) { ListPartsRequest.Builder builder = ListPartsRequest.builder(); + validateRequestFields(putObjectRequest, builder.build(), 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, builder.build(), COPY_OBJECT_TO_COPY_OBJECT_ALLOWED_FIELDS); setSdkFields(builder, copyObjectRequest); builder.bucket(copyObjectRequest.destinationBucket()); builder.key(copyObjectRequest.destinationKey()); @@ -180,6 +261,7 @@ private static CopyObjectResult toCopyObjectResult(CompleteMultipartUploadRespon public static AbortMultipartUploadRequest.Builder toAbortMultipartUploadRequest(CopyObjectRequest copyObjectRequest) { AbortMultipartUploadRequest.Builder builder = AbortMultipartUploadRequest.builder(); + validateRequestFields(copyObjectRequest, builder.build(), COPY_OBJECT_TO_COPY_OBJECT_ALLOWED_FIELDS); setSdkFields(builder, copyObjectRequest); builder.bucket(copyObjectRequest.destinationBucket()); builder.key(copyObjectRequest.destinationKey()); @@ -188,6 +270,7 @@ public static AbortMultipartUploadRequest.Builder toAbortMultipartUploadRequest( public static AbortMultipartUploadRequest.Builder toAbortMultipartUploadRequest(PutObjectRequest putObjectRequest) { AbortMultipartUploadRequest.Builder builder = AbortMultipartUploadRequest.builder(); + validateRequestFields(putObjectRequest, builder.build(), PUT_OBJECT_TO_UPLOAD_PART_ALLOWED_FIELDS); setSdkFields(builder, putObjectRequest); return builder; } @@ -225,4 +308,47 @@ 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, SdkPojo targetObject, Set allowedFields) { + Set invalidFields = new HashSet<>(); + + for (SdkField sourceField : sourceObject.sdkFields()) { + String fieldName = sourceField.memberName(); + Object sourceValue = sourceField.getValueOrDefault(sourceObject); + + if (!allowedFields.contains(fieldName)) { + SdkField targetField = targetObject.sdkFields() + .stream() + .filter(field -> field.memberName().equals(fieldName)) + .findFirst() + .orElse(null); + if (targetField != null && !Objects.equals(sourceValue, targetField.getValueOrDefault(targetObject))) { + invalidFields.add(fieldName); + } + } + } + + if (!invalidFields.isEmpty()) { + throw SdkClientException.create( + String.format("The following fields are not allowed: %s", + String.join(", ", invalidFields)) + ); + } + } } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/multipart/SdkPojoConversionUtilsTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/multipart/SdkPojoConversionUtilsTest.java index 3e93682113c5..c700615e53db 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/multipart/SdkPojoConversionUtilsTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/multipart/SdkPojoConversionUtilsTest.java @@ -36,7 +36,6 @@ import software.amazon.awssdk.core.SdkField; import software.amazon.awssdk.core.SdkPojo; import software.amazon.awssdk.http.SdkHttpFullResponse; -import software.amazon.awssdk.services.s3.internal.multipart.SdkPojoConversionUtils; import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest; import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest;