Skip to content

Commit f4f0987

Browse files
authored
Implement deleteObject & deleteObjects (#34)
* Implement `deleteObject` & `deleteObjects` methods
1 parent 90cab2d commit f4f0987

File tree

5 files changed

+166
-12
lines changed

5 files changed

+166
-12
lines changed

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

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
44

55
import java.security.KeyPair;
6+
import java.util.ArrayList;
7+
import java.util.List;
68
import java.security.SecureRandom;
79
import java.util.Map;
810
import java.util.function.Consumer;
@@ -21,6 +23,7 @@
2123
import software.amazon.awssdk.services.s3.model.DeleteObjectsResponse;
2224
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
2325
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
26+
import software.amazon.awssdk.services.s3.model.ObjectIdentifier;
2427
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
2528
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
2629
import software.amazon.encryption.s3.internal.GetEncryptedObjectPipeline;
@@ -40,7 +43,7 @@
4043
public class S3EncryptionClient implements S3Client {
4144

4245
// Used for request-scoped encryption contexts for supporting keys
43-
public static final ExecutionAttribute<Map<String,String>> ENCRYPTION_CONTEXT = new ExecutionAttribute<>("EncryptionContext");
46+
public static final ExecutionAttribute<Map<String, String>> ENCRYPTION_CONTEXT = new ExecutionAttribute<>("EncryptionContext");
4447

4548
private final S3Client _wrappedClient;
4649
private final CryptographicMaterialsManager _cryptoMaterialsManager;
@@ -81,7 +84,7 @@ public PutObjectResponse putObject(PutObjectRequest putObjectRequest, RequestBod
8184

8285
@Override
8386
public <T> T getObject(GetObjectRequest getObjectRequest,
84-
ResponseTransformer<GetObjectResponse, T> responseTransformer)
87+
ResponseTransformer<GetObjectResponse, T> responseTransformer)
8588
throws AwsServiceException, SdkClientException {
8689

8790
GetEncryptedObjectPipeline pipeline = GetEncryptedObjectPipeline.builder()
@@ -97,13 +100,33 @@ public <T> T getObject(GetObjectRequest getObjectRequest,
97100
@Override
98101
public DeleteObjectResponse deleteObject(DeleteObjectRequest deleteObjectRequest) throws AwsServiceException,
99102
SdkClientException {
100-
return _wrappedClient.deleteObject(deleteObjectRequest);
103+
// Delete the object
104+
DeleteObjectResponse deleteObjectResponse = _wrappedClient.deleteObject(deleteObjectRequest);
105+
// If Instruction file exists, delete the instruction file as well.
106+
String instructionObjectKey = deleteObjectRequest.key() + ".instruction";
107+
_wrappedClient.deleteObject(builder -> builder
108+
.bucket(deleteObjectRequest.bucket())
109+
.key(instructionObjectKey));
110+
return deleteObjectResponse;
101111
}
102112

103113
@Override
104114
public DeleteObjectsResponse deleteObjects(DeleteObjectsRequest deleteObjectsRequest) throws AwsServiceException,
105115
SdkClientException {
106-
return _wrappedClient.deleteObjects(deleteObjectsRequest);
116+
// Delete the objects
117+
DeleteObjectsResponse deleteObjectsResponse = _wrappedClient.deleteObjects(deleteObjectsRequest);
118+
// If Instruction files exists, delete the instruction files as well.
119+
List<ObjectIdentifier> deleteObjects = new ArrayList<>();
120+
for (ObjectIdentifier o : deleteObjectsRequest.delete().objects()) {
121+
deleteObjects.add(o.toBuilder()
122+
.key(o.key() + ".instruction")
123+
.build());
124+
}
125+
_wrappedClient.deleteObjects(DeleteObjectsRequest.builder()
126+
.bucket(deleteObjectsRequest.bucket())
127+
.delete(builder -> builder.objects(deleteObjects))
128+
.build());
129+
return deleteObjectsResponse;
107130
}
108131

109132
@Override
@@ -127,7 +150,8 @@ public static class Builder {
127150
private boolean _enableDelayedAuthenticationMode = false;
128151
private SecureRandom _secureRandom = new SecureRandom();
129152

130-
private Builder() {}
153+
private Builder() {
154+
}
131155

132156
/**
133157
* Note that this does NOT create a defensive clone of S3Client. Any modifications made to the wrapped

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.amazonaws.services.s3.model.KMSEncryptionMaterials;
2222
import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider;
2323
import com.amazonaws.services.s3.model.StaticEncryptionMaterialsProvider;
24+
2425
import java.io.ByteArrayInputStream;
2526
import java.io.IOException;
2627
import java.nio.charset.StandardCharsets;
@@ -31,6 +32,7 @@
3132
import java.util.Map;
3233
import javax.crypto.KeyGenerator;
3334
import javax.crypto.SecretKey;
35+
3436
import org.junit.jupiter.api.BeforeAll;
3537
import org.junit.jupiter.api.Test;
3638
import software.amazon.awssdk.core.ResponseBytes;
@@ -196,11 +198,8 @@ public void AesGcmV2toV3WithInstructionFile() {
196198
String output = objectResponse.asUtf8String();
197199
assertEquals(input, output);
198200

199-
// TODO: Implement deleteObject in the v3 client
200-
// so that instruction file is deleted too
201201
// Cleanup
202202
deleteObject(BUCKET, objectKey, v3Client);
203-
deleteObject(BUCKET, (objectKey + ".instruction"), v3Client);
204203
v3Client.close();
205204
}
206205

@@ -703,5 +702,4 @@ public void AesWrapV1toV3FailsWhenLegacyModeDisabled() {
703702
deleteObject(BUCKET, objectKey, v3Client);
704703
v3Client.close();
705704
}
706-
707705
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.junit.jupiter.api.Assertions.assertEquals;
2323
import static org.junit.jupiter.api.Assertions.assertThrows;
2424
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET;
25+
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject;
2526

2627
/**
2728
* This class is an integration test for Unauthenticated Ranged Get for AES/CBC and AES/GCM modes
@@ -58,6 +59,10 @@ public void failsOnRangeWhenLegacyModeDisabled() {
5859
assertThrows(S3EncryptionClientException.class, () -> v3Client.getObjectAsBytes(builder -> builder.bucket(BUCKET)
5960
.key(objectKey)
6061
.range("bytes=10-20")));
62+
63+
// Cleanup
64+
deleteObject(BUCKET, objectKey, v3Client);
65+
v3Client.close();
6166
}
6267

6368
@Test
@@ -119,6 +124,10 @@ public void AesGcmV3toV3RangedGet() {
119124
.key(objectKey));
120125
output = objectResponse.asUtf8String();
121126
assertEquals("", output);
127+
128+
// Cleanup
129+
deleteObject(BUCKET, objectKey, v3Client);
130+
v3Client.close();
122131
}
123132

124133
@Test
@@ -147,6 +156,10 @@ public void AesGcmV3toV3FailsRangeExceededObjectLength() {
147156
.bucket(BUCKET)
148157
.range("bytes=300-400")
149158
.key(objectKey)));
159+
160+
// Cleanup
161+
deleteObject(BUCKET, objectKey, v3Client);
162+
v3Client.close();
150163
}
151164

152165
@Test
@@ -216,6 +229,10 @@ public void AesCbcV1toV3RangedGet() {
216229
.key(objectKey));
217230
output = objectResponse.asUtf8String();
218231
assertEquals("", output);
232+
233+
// Cleanup
234+
deleteObject(BUCKET, objectKey, v3Client);
235+
v3Client.close();
219236
}
220237

221238
@Test
@@ -251,5 +268,9 @@ public void AesCbcV1toV3FailsRangeExceededObjectLength() {
251268
.bucket(BUCKET)
252269
.range("bytes=300-400")
253270
.key(objectKey)));
271+
272+
// Cleanup
273+
deleteObject(BUCKET, objectKey, v3Client);
274+
v3Client.close();
254275
}
255276
}

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

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package software.amazon.encryption.s3;
22

3+
import com.amazonaws.services.s3.AmazonS3EncryptionClientV2;
4+
import com.amazonaws.services.s3.AmazonS3EncryptionV2;
5+
import com.amazonaws.services.s3.model.CryptoConfigurationV2;
6+
import com.amazonaws.services.s3.model.CryptoMode;
7+
import com.amazonaws.services.s3.model.CryptoStorageMode;
8+
import com.amazonaws.services.s3.model.EncryptionMaterials;
9+
import com.amazonaws.services.s3.model.EncryptionMaterialsProvider;
10+
import com.amazonaws.services.s3.model.StaticEncryptionMaterialsProvider;
311
import java.security.SecureRandom;
412

513
import org.junit.jupiter.api.BeforeAll;
@@ -9,7 +17,9 @@
917
import software.amazon.awssdk.core.sync.RequestBody;
1018
import software.amazon.awssdk.services.s3.S3Client;
1119
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
20+
import software.amazon.awssdk.services.s3.model.ObjectIdentifier;
1221
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
22+
import software.amazon.awssdk.services.s3.model.S3Exception;
1323
import software.amazon.encryption.s3.materials.AesKeyring;
1424
import software.amazon.encryption.s3.materials.CryptographicMaterialsManager;
1525
import software.amazon.encryption.s3.materials.DefaultCryptoMaterialsManager;
@@ -23,10 +33,14 @@
2333
import java.security.KeyPair;
2434
import java.security.KeyPairGenerator;
2535
import java.security.NoSuchAlgorithmException;
36+
import java.util.ArrayList;
2637
import java.util.HashMap;
38+
import java.util.List;
2739
import java.util.Map;
2840

29-
import static org.junit.jupiter.api.Assertions.*;
41+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
42+
import static org.junit.jupiter.api.Assertions.assertEquals;
43+
import static org.junit.jupiter.api.Assertions.assertThrows;
3044
import static org.mockito.ArgumentMatchers.any;
3145
import static org.mockito.Mockito.mock;
3246
import static org.mockito.Mockito.never;
@@ -60,6 +74,104 @@ public static void setUp() throws NoSuchAlgorithmException {
6074
RSA_KEY_PAIR = keyPairGen.generateKeyPair();
6175
}
6276

77+
@Test
78+
public void deleteObjectWithInstructionFileSuccess() {
79+
final String objectKey = "delete-object-with-instruction-file";
80+
81+
// V2 Client
82+
EncryptionMaterialsProvider materialsProvider =
83+
new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY));
84+
CryptoConfigurationV2 cryptoConfig =
85+
new CryptoConfigurationV2(CryptoMode.StrictAuthenticatedEncryption)
86+
.withStorageMode(CryptoStorageMode.InstructionFile);
87+
AmazonS3EncryptionV2 v2Client = AmazonS3EncryptionClientV2.encryptionBuilder()
88+
.withCryptoConfiguration(cryptoConfig)
89+
.withEncryptionMaterialsProvider(materialsProvider)
90+
.build();
91+
92+
// V3 Client
93+
S3Client v3Client = S3EncryptionClient.builder()
94+
.aesKey(AES_KEY)
95+
.build();
96+
final String input = "DeleteObjectWithInstructionFileSuccess";
97+
v2Client.putObject(BUCKET, objectKey, input);
98+
99+
// Delete Object
100+
v3Client.deleteObject(builder -> builder.bucket(BUCKET).key(objectKey));
101+
102+
S3Client s3Client = S3Client.builder().build();
103+
// Assert throw NoSuchKeyException when getObject for objectKey
104+
assertThrows(S3Exception.class, () -> s3Client.getObject(builder -> builder
105+
.bucket(BUCKET)
106+
.key(objectKey)));
107+
assertThrows(S3Exception.class, () -> s3Client.getObject(builder -> builder
108+
.bucket(BUCKET)
109+
.key(objectKey + ".instruction")));
110+
111+
// Cleanup
112+
v3Client.close();
113+
s3Client.close();
114+
}
115+
116+
@Test
117+
public void deleteObjectsWithInstructionFilesSuccess() {
118+
final String[] objectKeys = {"delete-object-with-instruction-file-1",
119+
"delete-object-with-instruction-file-2",
120+
"delete-object-with-instruction-file-3"};
121+
122+
// V2 Client
123+
EncryptionMaterialsProvider materialsProvider =
124+
new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY));
125+
CryptoConfigurationV2 cryptoConfig =
126+
new CryptoConfigurationV2(CryptoMode.StrictAuthenticatedEncryption)
127+
.withStorageMode(CryptoStorageMode.InstructionFile);
128+
AmazonS3EncryptionV2 v2Client = AmazonS3EncryptionClientV2.encryptionBuilder()
129+
.withCryptoConfiguration(cryptoConfig)
130+
.withEncryptionMaterialsProvider(materialsProvider)
131+
.build();
132+
133+
// V3 Client
134+
S3Client v3Client = S3EncryptionClient.builder()
135+
.aesKey(AES_KEY)
136+
.build();
137+
final String input = "DeleteObjectsWithInstructionFileSuccess";
138+
List<ObjectIdentifier> objects = new ArrayList<>();
139+
for (String objectKey : objectKeys) {
140+
v2Client.putObject(BUCKET, objectKey, input);
141+
objects.add(ObjectIdentifier.builder().key(objectKey).build());
142+
}
143+
144+
// Delete Objects from S3 Buckets
145+
v3Client.deleteObjects(builder -> builder
146+
.bucket(BUCKET)
147+
.delete(builder1 -> builder1.objects(objects)));
148+
149+
S3Client s3Client = S3Client.builder().build();
150+
// Assert throw NoSuchKeyException when getObject for any of objectKeys
151+
assertThrows(S3Exception.class, () -> s3Client.getObject(builder -> builder
152+
.bucket(BUCKET)
153+
.key(objectKeys[0])));
154+
assertThrows(S3Exception.class, () -> s3Client.getObject(builder -> builder
155+
.bucket(BUCKET)
156+
.key(objectKeys[0] + ".instruction")));
157+
158+
// Cleanup
159+
v3Client.close();
160+
s3Client.close();
161+
}
162+
163+
@Test
164+
public void deleteObjectWithWrongObjectKeySuccess() {
165+
// V3 Client
166+
S3Client v3Client = S3EncryptionClient.builder()
167+
.aesKey(AES_KEY)
168+
.build();
169+
assertDoesNotThrow(() -> v3Client.deleteObject(builder -> builder.bucket(BUCKET).key("InvalidKey")));
170+
171+
// Cleanup
172+
v3Client.close();
173+
}
174+
63175
@Test
64176
public void s3EncryptionClientWithMultipleKeyringsFails() {
65177
assertThrows(S3EncryptionClientException.class, () -> S3EncryptionClient.builder()
@@ -361,6 +473,7 @@ public void s3EncryptionClientFromAESKeyringUsesDifferentSecureRandomThanKeyring
361473
/**
362474
* A simple, reusable round-trip (encryption + decryption) using a given
363475
* S3Client. Useful for testing client configuration.
476+
*
364477
* @param v3Client the client under test
365478
*/
366479
private void simpleV3RoundTrip(final S3Client v3Client, final String objectKey) {

src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,4 @@ public static void deleteObject(final String bucket, final String objectKey, fin
2828
.delete(delete)
2929
.build());
3030
}
31-
32-
3331
}

0 commit comments

Comments
 (0)