-
Notifications
You must be signed in to change notification settings - Fork 937
allowlist for copy request added #6352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cce84bf
c103284
04136c4
65178b2
e19d68d
a4ffc6f
a46c86f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<String> 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<String> 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<String> fieldsToIgnore) { | ||
Map<String, Object> sourceFields = retrieveSdkFields(sourceObject, sourceObject.sdkFields()); | ||
List<SdkField<?>> 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<String, Object> 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<String> fieldsToIgnore) { | ||
Map<String, Object> sourceFields = retrieveSdkFields(sourceObject, sourceObject.sdkFields()); | ||
List<SdkField<?>> 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<String> allowedFields) { | ||
Set<String> 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", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there currently any fields not on the allow list and do we know if any of them have different default values? IE - is there a risk of now raising an exception for cases that was previously working for customers? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All current fields are on the allow list so there shouldn't be cases that raise exception. The existing unit test for SdkPojoConversionUtils should already covered that. |
||
String.join(", ", invalidFields)) | ||
); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we keep these allowlists up to date over time (ie, if/when there are service model updates that add new fields)? The PutObject to UploadPart translation uses an ignore list instead of allow list - is there a reason we choose allow rather than ignore for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on offline discussion, I think the allow list here makes sense so I'm good moving forward with it.