Skip to content

Commit ba78c2c

Browse files
authored
feat: extend delegating client to enable non-encrypted API operations in default and async encrypion clients (#94)
1 parent ba3be52 commit ba78c2c

File tree

5 files changed

+119
-15
lines changed

5 files changed

+119
-15
lines changed

src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import software.amazon.awssdk.core.async.AsyncRequestBody;
77
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
88
import software.amazon.awssdk.core.exception.SdkClientException;
9+
import software.amazon.awssdk.services.s3.DelegatingS3AsyncClient;
910
import software.amazon.awssdk.services.s3.S3AsyncClient;
1011
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
1112
import software.amazon.awssdk.services.s3.model.DeleteObjectResponse;
@@ -36,7 +37,7 @@
3637
import java.util.function.Consumer;
3738
import java.util.function.Function;
3839

39-
public class S3AsyncEncryptionClient implements S3AsyncClient {
40+
public class S3AsyncEncryptionClient extends DelegatingS3AsyncClient {
4041

4142
private final S3AsyncClient _wrappedClient;
4243
private final S3AsyncClient _wrappedCrtClient;
@@ -48,6 +49,7 @@ public class S3AsyncEncryptionClient implements S3AsyncClient {
4849
private final boolean _enableMultipartPutObject;
4950

5051
private S3AsyncEncryptionClient(Builder builder) {
52+
super(builder._wrappedClient);
5153
_wrappedClient = builder._wrappedClient;
5254
_wrappedCrtClient = builder._wrappedCrtClient;
5355
_cryptoMaterialsManager = builder._cryptoMaterialsManager;
@@ -123,11 +125,6 @@ public CompletableFuture<DeleteObjectsResponse> deleteObjects(DeleteObjectsReque
123125
.build());
124126
}
125127

126-
@Override
127-
public String serviceName() {
128-
return _wrappedClient.serviceName();
129-
}
130-
131128
@Override
132129
public void close() {
133130
_wrappedClient.close();

src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import software.amazon.awssdk.core.sync.RequestBody;
1212
import software.amazon.awssdk.core.sync.ResponseTransformer;
1313
import software.amazon.awssdk.http.AbortableInputStream;
14+
import software.amazon.awssdk.services.s3.DelegatingS3Client;
1415
import software.amazon.awssdk.services.s3.S3AsyncClient;
1516
import software.amazon.awssdk.services.s3.S3Client;
1617
import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest;
@@ -59,13 +60,14 @@
5960
* This client is a drop-in replacement for the S3 client. It will automatically encrypt objects
6061
* on putObject and decrypt objects on getObject using the provided encryption key(s).
6162
*/
62-
public class S3EncryptionClient implements S3Client {
63+
public class S3EncryptionClient extends DelegatingS3Client {
6364

6465
// Used for request-scoped encryption contexts for supporting keys
6566
public static final ExecutionAttribute<Map<String, String>> ENCRYPTION_CONTEXT = new ExecutionAttribute<>("EncryptionContext");
6667
// TODO: Replace with UploadPartRequest.isLastPart() when launched.
6768
// Used for multipart uploads
6869
public static final ExecutionAttribute<Boolean> IS_LAST_PART = new ExecutionAttribute<>("isLastPart");
70+
6971
private final S3AsyncClient _wrappedClient;
7072
private final S3AsyncClient _wrappedCrtClient;
7173
private final CryptographicMaterialsManager _cryptoMaterialsManager;
@@ -77,6 +79,9 @@ public class S3EncryptionClient implements S3Client {
7779
private final MultipartUploadObjectPipeline _multipartPipeline;
7880

7981
private S3EncryptionClient(Builder builder) {
82+
// The non-encrypted APIs will use a default client.
83+
// In the future, we may want to make this configurable.
84+
super(S3Client.create());
8085
_wrappedClient = builder._wrappedClient;
8186
_wrappedCrtClient = builder._wrappedCrtClient;
8287
_cryptoMaterialsManager = builder._cryptoMaterialsManager;
@@ -206,11 +211,6 @@ public AbortMultipartUploadResponse abortMultipartUpload(AbortMultipartUploadReq
206211
return _multipartPipeline.abortMultipartUpload(request);
207212
}
208213

209-
@Override
210-
public String serviceName() {
211-
return _wrappedClient.serviceName();
212-
}
213-
214214
@Override
215215
public void close() {
216216
_wrappedClient.close();

src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import software.amazon.awssdk.core.sync.ResponseTransformer;
2121
import software.amazon.awssdk.services.s3.S3AsyncClient;
2222
import software.amazon.awssdk.services.s3.S3Client;
23+
import software.amazon.awssdk.services.s3.model.CopyObjectResponse;
2324
import software.amazon.awssdk.services.s3.model.DeleteObjectResponse;
2425
import software.amazon.awssdk.services.s3.model.DeleteObjectsResponse;
2526
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
@@ -38,7 +39,6 @@
3839
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
3940
import static org.junit.jupiter.api.Assertions.assertEquals;
4041
import static org.junit.jupiter.api.Assertions.assertThrows;
41-
import static org.junit.jupiter.api.Assertions.assertTrue;
4242
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET;
4343
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix;
4444
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject;
@@ -120,6 +120,36 @@ public void putDefaultGetAsync() {
120120
v3AsyncClient.close();
121121
}
122122

123+
@Test
124+
public void putAsyncGetAsync() {
125+
final String objectKey = appendTestSuffix("put-async-get-async");
126+
127+
S3AsyncClient v3AsyncClient = S3AsyncEncryptionClient.builder()
128+
.aesKey(AES_KEY)
129+
.build();
130+
131+
final String input = "PutAsyncGetAsync";
132+
133+
CompletableFuture<PutObjectResponse> futurePut = v3AsyncClient.putObject(builder -> builder
134+
.bucket(BUCKET)
135+
.key(objectKey)
136+
.build(), AsyncRequestBody.fromString(input));
137+
// Block on completion of the futurePut
138+
futurePut.join();
139+
140+
CompletableFuture<ResponseBytes<GetObjectResponse>> futureGet = v3AsyncClient.getObject(builder -> builder
141+
.bucket(BUCKET)
142+
.key(objectKey)
143+
.build(), AsyncResponseTransformer.toBytes());
144+
// Just wait for the future to complete
145+
ResponseBytes<GetObjectResponse> getResponse = futureGet.join();
146+
assertEquals(input, getResponse.asUtf8String());
147+
148+
// Cleanup
149+
deleteObject(BUCKET, objectKey, v3AsyncClient);
150+
v3AsyncClient.close();
151+
}
152+
123153
@Test
124154
public void aesCbcV1toV3Async() {
125155
final String objectKey = appendTestSuffix("aes-cbc-v1-to-v3-async");
@@ -333,4 +363,46 @@ public void deleteObjectWithWrongObjectKeySuccessAsync() {
333363
// Cleanup
334364
v3Client.close();
335365
}
366+
367+
@Test
368+
public void copyObjectTransparentlyAsync() {
369+
final String objectKey = appendTestSuffix("copy-object-from-here-async");
370+
final String newObjectKey = appendTestSuffix("copy-object-to-here-async");
371+
372+
S3AsyncClient v3AsyncClient = S3AsyncEncryptionClient.builder()
373+
.aesKey(AES_KEY)
374+
.build();
375+
376+
final String input = "CopyObjectAsync";
377+
378+
CompletableFuture<PutObjectResponse> futurePut = v3AsyncClient.putObject(builder -> builder
379+
.bucket(BUCKET)
380+
.key(objectKey)
381+
.build(), AsyncRequestBody.fromString(input));
382+
// Block on completion of the futurePut
383+
futurePut.join();
384+
385+
CompletableFuture<CopyObjectResponse> futureCopy = v3AsyncClient.copyObject(builder -> builder
386+
.sourceBucket(BUCKET)
387+
.destinationBucket(BUCKET)
388+
.sourceKey(objectKey)
389+
.destinationKey(newObjectKey)
390+
.build());
391+
// Block on copy future
392+
futureCopy.join();
393+
394+
// Decrypt new object
395+
CompletableFuture<ResponseBytes<GetObjectResponse>> futureGet = v3AsyncClient.getObject(builder -> builder
396+
.bucket(BUCKET)
397+
.key(newObjectKey)
398+
.build(), AsyncResponseTransformer.toBytes());
399+
ResponseBytes<GetObjectResponse> getResponse = futureGet.join();
400+
assertEquals(input, getResponse.asUtf8String());
401+
402+
// Cleanup
403+
deleteObject(BUCKET, objectKey, v3AsyncClient);
404+
deleteObject(BUCKET, newObjectKey, v3AsyncClient);
405+
v3AsyncClient.close();
406+
}
407+
336408
}

src/test/java/software/amazon/encryption/s3/S3EncryptionClientCompatibilityTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
2525
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
2626
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
27-
import software.amazon.awssdk.services.s3.model.S3Exception;
2827

2928
import javax.crypto.KeyGenerator;
3029
import javax.crypto.SecretKey;
@@ -36,7 +35,6 @@
3635
import java.security.NoSuchAlgorithmException;
3736
import java.util.HashMap;
3837
import java.util.Map;
39-
import java.util.concurrent.CompletionException;
4038

4139
import static org.junit.jupiter.api.Assertions.assertEquals;
4240
import static org.junit.jupiter.api.Assertions.assertThrows;

src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,43 @@ public static void setUp() throws NoSuchAlgorithmException {
7575
RSA_KEY_PAIR = keyPairGen.generateKeyPair();
7676
}
7777

78+
@Test
79+
public void copyObjectTransparently() {
80+
final String objectKey = appendTestSuffix("copy-object-from-here");
81+
final String newObjectKey = appendTestSuffix("copy-object-to-here");
82+
83+
S3Client s3EncryptionClient = S3EncryptionClient.builder()
84+
.kmsKeyId(KMS_KEY_ID)
85+
.build();
86+
87+
final String input = "SimpleTestOfV3EncryptionClientCopyObject";
88+
89+
s3EncryptionClient.putObject(builder -> builder
90+
.bucket(BUCKET)
91+
.key(objectKey)
92+
.build(),
93+
RequestBody.fromString(input));
94+
95+
s3EncryptionClient.copyObject(builder -> builder
96+
.sourceBucket(BUCKET)
97+
.destinationBucket(BUCKET)
98+
.sourceKey(objectKey)
99+
.destinationKey(newObjectKey)
100+
.build());
101+
102+
ResponseBytes<GetObjectResponse> objectResponse = s3EncryptionClient.getObjectAsBytes(builder -> builder
103+
.bucket(BUCKET)
104+
.key(newObjectKey)
105+
.build());
106+
String output = objectResponse.asUtf8String();
107+
assertEquals(input, output);
108+
109+
// Cleanup
110+
deleteObject(BUCKET, objectKey, s3EncryptionClient);
111+
deleteObject(BUCKET, newObjectKey, s3EncryptionClient);
112+
s3EncryptionClient.close();
113+
}
114+
78115
@Test
79116
public void deleteObjectWithInstructionFileSuccess() {
80117
final String objectKey = appendTestSuffix("delete-object-with-instruction-file");

0 commit comments

Comments
 (0)