Skip to content

Commit 037fb32

Browse files
committed
fix: Support all PutObjectRequest fields
All possible values of a PutObjectRequest need to be supported when enabling multiparty put object. This PR adds support and the S3EC will now fail closed in the case of new options. This is preferred to silently dropping the value.
1 parent 92aee3c commit 037fb32

File tree

3 files changed

+570
-12
lines changed

3 files changed

+570
-12
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package software.amazon.encryption.s3.internal;
2+
3+
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
4+
import software.amazon.awssdk.services.s3.model.*;
5+
import java.time.Instant;
6+
import java.util.Map;
7+
8+
public class ConvertSDKRequests {
9+
10+
public static CreateMultipartUploadRequest convert(PutObjectRequest request) {
11+
12+
final CreateMultipartUploadRequest.Builder output = CreateMultipartUploadRequest.builder();
13+
request
14+
.toBuilder()
15+
.sdkFields()
16+
.forEach(f -> {
17+
final Object value = f.getValueOrDefault(request);
18+
if (value != null) {
19+
switch (f.memberName()) {
20+
case "ACL":
21+
output.acl((String) value);
22+
break;
23+
case "Bucket":
24+
output.bucket((String) value);
25+
break;
26+
case "BucketKeyEnabled":
27+
output.bucketKeyEnabled((Boolean) value);
28+
break;
29+
case "CacheControl":
30+
output.cacheControl((String) value);
31+
break;
32+
case "ChecksumAlgorithm":
33+
output.checksumAlgorithm((String) value);
34+
break;
35+
case "ChecksumType":
36+
output.checksumType((ChecksumType) value);
37+
case "ContentDisposition":
38+
assert value instanceof String;
39+
output.contentDisposition((String) value);
40+
break;
41+
case "ContentEncoding":
42+
output.contentEncoding((String) value);
43+
break;
44+
case "ContentLanguage":
45+
output.contentLanguage((String) value);
46+
break;
47+
case "ContentType":
48+
output.contentType((String) value);
49+
break;
50+
case "ExpectedBucketOwner":
51+
output.expectedBucketOwner((String) value);
52+
break;
53+
case "Expires":
54+
output.expires((Instant) value);
55+
break;
56+
case "GrantFullControl":
57+
output.grantFullControl((String) value);
58+
break;
59+
case "GrantRead":
60+
output.grantRead((String) value);
61+
break;
62+
case "GrantReadACP":
63+
output.grantReadACP((String) value);
64+
break;
65+
case "GrantWriteACP":
66+
output.grantWriteACP((String) value);
67+
break;
68+
case "Key":
69+
output.key((String) value);
70+
break;
71+
case "Metadata":
72+
// The PutObjectRequest.builder().metadata(value)
73+
// only takes Map<String, String> therefore it should not be possible
74+
// to get here with anything other than a Map<String, String>
75+
// This may be overkill, but this map should be small
76+
// so the performance hit to verify this is worth the correctness.
77+
if (!isStringStringMap(value)) {
78+
throw new IllegalArgumentException("Metadata must be a Map<String, String>");
79+
}
80+
@SuppressWarnings("unchecked")
81+
Map<String, String> metadata = (Map<String, String>) value;
82+
output.metadata(metadata);
83+
break;
84+
case "ObjectLockLegalHoldStatus":
85+
output.objectLockLegalHoldStatus((String) value);
86+
break;
87+
case "ObjectLockMode":
88+
output.objectLockMode((String) value);
89+
break;
90+
case "ObjectLockRetainUntilDate":
91+
output.objectLockRetainUntilDate((Instant) value);
92+
break;
93+
case "RequestPayer":
94+
output.requestPayer((String) value);
95+
break;
96+
case "ServerSideEncryption":
97+
output.serverSideEncryption((String) value);
98+
break;
99+
case "SSECustomerAlgorithm":
100+
output.sseCustomerAlgorithm((String) value);
101+
break;
102+
case "SSECustomerKey":
103+
output.sseCustomerKey((String) value);
104+
break;
105+
case "SSEKMSEncryptionContext":
106+
output.ssekmsEncryptionContext((String) value);
107+
break;
108+
case "SSEKMSKeyId":
109+
output.ssekmsKeyId((String) value);
110+
break;
111+
case "StorageClass":
112+
output.storageClass((String) value);
113+
break;
114+
case "Tagging":
115+
output.tagging((String) value);
116+
break;
117+
case "WebsiteRedirectLocation":
118+
output.websiteRedirectLocation((String) value);
119+
break;
120+
default:
121+
// Rather than silently dropping the value,
122+
// we loudly signal that we don't know how to handle this field.
123+
throw new IllegalArgumentException("Unknown PutObjectRequest field " + f.locationName() + ".");
124+
}
125+
}
126+
});
127+
return output
128+
// OverrideConfiguration is not as SDKField but still needs to be supported
129+
.overrideConfiguration(request.overrideConfiguration().orElse(null))
130+
.build();
131+
}
132+
133+
private static boolean isStringStringMap(Object value) {
134+
if (!(value instanceof Map)) {
135+
return false;
136+
}
137+
Map<?, ?> map = (Map<?, ?>) value;
138+
return map.entrySet().stream()
139+
.allMatch(entry -> entry != null
140+
&& ((Map.Entry<?, ?>) entry).getKey() instanceof String
141+
&& ((Map.Entry<?, ?>) entry).getValue() instanceof String);
142+
}
143+
}

src/main/java/software/amazon/encryption/s3/internal/UploadObjectObserver.java

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,10 @@ public UploadObjectObserver init(PutObjectRequest req,
4242
this.es = es;
4343
return this;
4444
}
45-
46-
protected CreateMultipartUploadRequest newCreateMultipartUploadRequest(
47-
PutObjectRequest request) {
48-
return CreateMultipartUploadRequest.builder()
49-
.bucket(request.bucket())
50-
.key(request.key())
51-
.metadata(request.metadata())
52-
.overrideConfiguration(request.overrideConfiguration().orElse(null))
53-
.build();
54-
}
55-
45+
5646
public String onUploadCreation(PutObjectRequest req) {
5747
CreateMultipartUploadResponse res =
58-
s3EncryptionClient.createMultipartUpload(newCreateMultipartUploadRequest(req));
48+
s3EncryptionClient.createMultipartUpload(ConvertSDKRequests.convert(req));
5949
return this.uploadId = res.uploadId();
6050
}
6151

0 commit comments

Comments
 (0)