Skip to content

Commit 9abc102

Browse files
authored
feat: Added support for object canned access control lists. (#9)
feat: Added support for object canned access control lists.
1 parent aaf01f6 commit 9abc102

File tree

8 files changed

+164
-79
lines changed

8 files changed

+164
-79
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ You can download release builds through the [releases section of this](https://g
2424
<dependency>
2525
<groupId>software.amazon.payloadoffloading</groupId>
2626
<artifactId>payloadoffloading-common</artifactId>
27-
<version>2.0.0</version>
27+
<version>2.1.0</version>
2828
</dependency>
2929
```
3030

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>software.amazon.payloadoffloading</groupId>
88
<artifactId>payloadoffloading-common</artifactId>
9-
<version>2.0.0</version>
9+
<version>2.1.0</version>
1010
<packaging>jar</packaging>
1111
<name>Payload offloading common library for AWS</name>
1212
<description>Common library between extended Amazon AWS clients to save payloads up to 2GB on Amazon S3.</description>

src/main/java/software/amazon/payloadoffloading/PayloadStorageConfiguration.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import software.amazon.awssdk.annotations.NotThreadSafe;
66
import software.amazon.awssdk.core.exception.SdkClientException;
77
import software.amazon.awssdk.services.s3.S3Client;
8+
import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
89

910
/**
1011
* <p>Amazon payload storage configuration options such as Amazon S3 client,
@@ -42,11 +43,16 @@ public class PayloadStorageConfiguration {
4243
* This field is optional, it is set only when we want to configure S3 Server Side Encryption with KMS.
4344
*/
4445
private ServerSideEncryptionStrategy serverSideEncryptionStrategy;
46+
/**
47+
* This field is optional, it is set only when we want to add access control list to Amazon S3 buckets and objects
48+
*/
49+
private ObjectCannedACL objectCannedACL;
4550

4651
public PayloadStorageConfiguration() {
4752
s3 = null;
4853
s3BucketName = null;
4954
serverSideEncryptionStrategy = null;
55+
objectCannedACL = null;
5056
}
5157

5258
public PayloadStorageConfiguration(PayloadStorageConfiguration other) {
@@ -56,6 +62,7 @@ public PayloadStorageConfiguration(PayloadStorageConfiguration other) {
5662
this.alwaysThroughS3 = other.isAlwaysThroughS3();
5763
this.payloadSizeThreshold = other.getPayloadSizeThreshold();
5864
this.serverSideEncryptionStrategy = other.getServerSideEncryptionStrategy();
65+
this.objectCannedACL = other.getObjectCannedACL();
5966
}
6067

6168
/**
@@ -235,4 +242,38 @@ public ServerSideEncryptionStrategy getServerSideEncryptionStrategy() {
235242
return this.serverSideEncryptionStrategy;
236243
}
237244

245+
/**
246+
* Configures the ACL to apply to the Amazon S3 putObject request.
247+
* @param objectCannedACL
248+
* The ACL to be used when storing objects in Amazon S3
249+
*/
250+
public void setObjectCannedACL(ObjectCannedACL objectCannedACL) {
251+
this.objectCannedACL = objectCannedACL;
252+
}
253+
254+
/**
255+
* Configures the ACL to apply to the Amazon S3 putObject request.
256+
* @param objectCannedACL
257+
* The ACL to be used when storing objects in Amazon S3
258+
*/
259+
public PayloadStorageConfiguration withObjectCannedACL(ObjectCannedACL objectCannedACL) {
260+
setObjectCannedACL(objectCannedACL);
261+
return this;
262+
}
263+
264+
/**
265+
* Checks whether an ACL have been configured for storing objects in Amazon S3.
266+
* @return True if ACL is defined
267+
*/
268+
public boolean isObjectCannedACLDefined() {
269+
return null != objectCannedACL;
270+
}
271+
272+
/**
273+
* Gets the AWS ACL to apply to the Amazon S3 putObject request.
274+
* @return Amazon S3 object ACL
275+
*/
276+
public ObjectCannedACL getObjectCannedACL() {
277+
return objectCannedACL;
278+
}
238279
}

src/main/java/software/amazon/payloadoffloading/S3BackedPayloadStore.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,17 @@ public class S3BackedPayloadStore implements PayloadStore {
1313

1414
private final String s3BucketName;
1515
private final S3Dao s3Dao;
16-
private final ServerSideEncryptionStrategy serverSideEncryptionStrategy;
1716

1817
public S3BackedPayloadStore(S3Dao s3Dao, String s3BucketName) {
19-
this(s3Dao, s3BucketName, null);
20-
}
21-
22-
public S3BackedPayloadStore(S3Dao s3Dao, String s3BucketName, ServerSideEncryptionStrategy serverSideEncryptionStrategy) {
2318
this.s3BucketName = s3BucketName;
2419
this.s3Dao = s3Dao;
25-
this.serverSideEncryptionStrategy = serverSideEncryptionStrategy;
2620
}
2721

2822
@Override
2923
public String storeOriginalPayload(String payload) {
3024
String s3Key = UUID.randomUUID().toString();
3125

32-
// Store the payload content in S3.
33-
s3Dao.storeTextInS3(s3BucketName, s3Key, serverSideEncryptionStrategy, payload);
26+
s3Dao.storeTextInS3(s3BucketName, s3Key, payload);
3427
LOG.info("S3 object created, Bucket name: " + s3BucketName + ", Object key: " + s3Key + ".");
3528

3629
// Convert S3 pointer (bucket name, key, etc) to JSON string

src/main/java/software/amazon/payloadoffloading/S3Dao.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
1111
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
1212
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
13+
import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
1314
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
1415
import software.amazon.awssdk.utils.IoUtils;
1516

@@ -21,9 +22,17 @@
2122
public class S3Dao {
2223
private static final Logger LOG = LoggerFactory.getLogger(S3Dao.class);
2324
private final S3Client s3Client;
25+
private final ServerSideEncryptionStrategy serverSideEncryptionStrategy;
26+
private final ObjectCannedACL objectCannedACL;
2427

2528
public S3Dao(S3Client s3Client) {
29+
this(s3Client, null, null);
30+
}
31+
32+
public S3Dao(S3Client s3Client, ServerSideEncryptionStrategy serverSideEncryptionStrategy, ObjectCannedACL objectCannedACL) {
2633
this.s3Client = s3Client;
34+
this.serverSideEncryptionStrategy = serverSideEncryptionStrategy;
35+
this.objectCannedACL = objectCannedACL;
2736
}
2837

2938
public String getTextFromS3(String s3BucketName, String s3Key) {
@@ -56,11 +65,15 @@ public String getTextFromS3(String s3BucketName, String s3Key) {
5665
return embeddedText;
5766
}
5867

59-
public void storeTextInS3(String s3BucketName, String s3Key, ServerSideEncryptionStrategy serverSideEncryptionStrategy, String payloadContentStr) {
68+
public void storeTextInS3(String s3BucketName, String s3Key, String payloadContentStr) {
6069
PutObjectRequest.Builder putObjectRequestBuilder = PutObjectRequest.builder()
6170
.bucket(s3BucketName)
6271
.key(s3Key);
6372

73+
if (objectCannedACL != null) {
74+
putObjectRequestBuilder.acl(objectCannedACL);
75+
}
76+
6477
// https://docs.aws.amazon.com/AmazonS3/latest/dev/kms-using-sdks.html
6578
if (serverSideEncryptionStrategy != null) {
6679
serverSideEncryptionStrategy.decorate(putObjectRequestBuilder);

src/test/java/software/amazon/payloadoffloading/PayloadStorageConfigurationTest.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.junit.Test;
44
import software.amazon.awssdk.services.s3.S3Client;
5+
import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
56

67
import static org.mockito.Mockito.mock;
78
import static org.junit.Assert.*;
@@ -12,8 +13,8 @@
1213
public class PayloadStorageConfigurationTest {
1314

1415
private static final String s3BucketName = "test-bucket-name";
15-
private static final String s3ServerSideEncryptionKMSKeyId = "test-customer-managed-kms-key-id";
1616
private static final ServerSideEncryptionStrategy SERVER_SIDE_ENCRYPTION_STRATEGY = ServerSideEncryptionFactory.awsManagedCmk();
17+
private final ObjectCannedACL objectCannelACL = ObjectCannedACL.BUCKET_OWNER_FULL_CONTROL;
1718

1819
@Test
1920
public void testCopyConstructor() {
@@ -27,14 +28,16 @@ public void testCopyConstructor() {
2728
payloadStorageConfiguration.withPayloadSupportEnabled(s3, s3BucketName)
2829
.withAlwaysThroughS3(alwaysThroughS3)
2930
.withPayloadSizeThreshold(payloadSizeThreshold)
30-
.withServerSideEncryption(SERVER_SIDE_ENCRYPTION_STRATEGY);
31+
.withServerSideEncryption(SERVER_SIDE_ENCRYPTION_STRATEGY)
32+
.withObjectCannedACL(objectCannelACL);
3133

3234
PayloadStorageConfiguration newPayloadStorageConfiguration = new PayloadStorageConfiguration(payloadStorageConfiguration);
3335

3436
assertEquals(s3, newPayloadStorageConfiguration.getS3Client());
3537
assertEquals(s3BucketName, newPayloadStorageConfiguration.getS3BucketName());
3638
assertEquals(SERVER_SIDE_ENCRYPTION_STRATEGY, newPayloadStorageConfiguration.getServerSideEncryptionStrategy());
3739
assertTrue(newPayloadStorageConfiguration.isPayloadSupportEnabled());
40+
assertEquals(objectCannelACL, newPayloadStorageConfiguration.getObjectCannedACL());
3841
assertEquals(alwaysThroughS3, newPayloadStorageConfiguration.isAlwaysThroughS3());
3942
assertEquals(payloadSizeThreshold, newPayloadStorageConfiguration.getPayloadSizeThreshold());
4043
assertNotSame(newPayloadStorageConfiguration, payloadStorageConfiguration);
@@ -80,4 +83,15 @@ public void testSseAwsKeyManagementParams() {
8083
payloadStorageConfiguration.setServerSideEncryptionStrategy(SERVER_SIDE_ENCRYPTION_STRATEGY);
8184
assertEquals(SERVER_SIDE_ENCRYPTION_STRATEGY, payloadStorageConfiguration.getServerSideEncryptionStrategy());
8285
}
86+
87+
@Test
88+
public void testCannedAccessControlList() {
89+
PayloadStorageConfiguration payloadStorageConfiguration = new PayloadStorageConfiguration();
90+
91+
assertFalse(payloadStorageConfiguration.isObjectCannedACLDefined());
92+
93+
payloadStorageConfiguration.withObjectCannedACL(objectCannelACL);
94+
assertTrue(payloadStorageConfiguration.isObjectCannedACLDefined());
95+
assertEquals(objectCannelACL, payloadStorageConfiguration.getObjectCannedACL());
96+
}
8397
}

src/test/java/software/amazon/payloadoffloading/S3BackedPayloadStoreTest.java

Lines changed: 11 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package software.amazon.payloadoffloading;
22

33
import junitparams.JUnitParamsRunner;
4-
import junitparams.Parameters;
54
import org.hamcrest.Matchers;
65
import org.junit.Before;
76
import org.junit.Rule;
@@ -11,8 +10,7 @@
1110
import org.mockito.ArgumentCaptor;
1211
import software.amazon.awssdk.core.exception.SdkClientException;
1312
import software.amazon.awssdk.core.exception.SdkException;
14-
15-
import java.util.Objects;
13+
import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
1614

1715
import static org.junit.Assert.*;
1816
import static org.mockito.ArgumentMatchers.any;
@@ -21,13 +19,9 @@
2119
@RunWith(JUnitParamsRunner.class)
2220
public class S3BackedPayloadStoreTest {
2321
private static final String S3_BUCKET_NAME = "test-bucket-name";
24-
private static final String S3_SERVER_SIDE_ENCRYPTION_KMS_KEY_ID = "test-customer-managed-kms-key-id";
25-
private static final ServerSideEncryptionStrategy KMS_WITH_CUSTOMER_KEY = ServerSideEncryptionFactory.customerKey(S3_SERVER_SIDE_ENCRYPTION_KMS_KEY_ID);
26-
private static final ServerSideEncryptionStrategy KMS_WITH_AWS_MANAGED_CMK = ServerSideEncryptionFactory.awsManagedCmk();
2722
private static final String ANY_PAYLOAD = "AnyPayload";
2823
private static final String ANY_S3_KEY = "AnyS3key";
2924
private static final String INCORRECT_POINTER_EXCEPTION_MSG = "Failed to read the S3 object pointer from given string";
30-
private static final Long ANY_PAYLOAD_LENGTH = 300000L;
3125
private PayloadStore payloadStore;
3226
private S3Dao s3Dao;
3327

@@ -40,63 +34,23 @@ public void setup() {
4034
payloadStore = new S3BackedPayloadStore(s3Dao, S3_BUCKET_NAME);
4135
}
4236

43-
private Object[] testData() {
44-
// Here, we create separate mock of S3Dao because JUnitParamsRunner collects parameters
45-
// for tests well before invocation of @Before or @BeforeClass methods.
46-
// That means our default s3Dao mock isn't instantiated until then. For parameterized tests,
47-
// we instantiate our local S3Dao mock per combination, pass it to S3BackedPayloadStore and also pass it
48-
// as test parameter to allow verifying calls to the mockS3Dao.
49-
S3Dao noEncryptionS3Dao = mock(S3Dao.class);
50-
S3Dao defaultEncryptionS3Dao = mock(S3Dao.class);
51-
S3Dao customerKMSKeyEncryptionS3Dao = mock(S3Dao.class);
52-
return new Object[][]{
53-
// No S3 SSE-KMS encryption
54-
{
55-
new S3BackedPayloadStore(noEncryptionS3Dao, S3_BUCKET_NAME),
56-
null,
57-
noEncryptionS3Dao
58-
},
59-
// S3 SSE-KMS encryption with AWS managed KMS keys
60-
{
61-
new S3BackedPayloadStore(defaultEncryptionS3Dao, S3_BUCKET_NAME, KMS_WITH_AWS_MANAGED_CMK),
62-
KMS_WITH_AWS_MANAGED_CMK,
63-
defaultEncryptionS3Dao
64-
},
65-
// S3 SSE-KMS encryption with customer managed KMS key
66-
{
67-
new S3BackedPayloadStore(customerKMSKeyEncryptionS3Dao, S3_BUCKET_NAME, KMS_WITH_CUSTOMER_KEY),
68-
KMS_WITH_CUSTOMER_KEY,
69-
customerKMSKeyEncryptionS3Dao
70-
}
71-
};
72-
}
73-
7437
@Test
75-
@Parameters(method = "testData")
76-
public void testStoreOriginalPayloadOnSuccess(PayloadStore payloadStore, ServerSideEncryptionStrategy expectedParams, S3Dao mockS3Dao) {
38+
public void testStoreOriginalPayloadOnSuccess() {
7739
String actualPayloadPointer = payloadStore.storeOriginalPayload(ANY_PAYLOAD);
7840

7941
ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
8042
ArgumentCaptor<ServerSideEncryptionStrategy> sseArgsCaptor = ArgumentCaptor.forClass(ServerSideEncryptionStrategy.class);
43+
ArgumentCaptor<ObjectCannedACL> cannedArgsCaptor = ArgumentCaptor.forClass(ObjectCannedACL.class);
8144

82-
verify(mockS3Dao, times(1)).storeTextInS3(eq(S3_BUCKET_NAME), keyCaptor.capture(),
83-
sseArgsCaptor.capture(), eq(ANY_PAYLOAD));
45+
verify(s3Dao, times(1)).storeTextInS3(eq(S3_BUCKET_NAME), keyCaptor.capture(),
46+
eq(ANY_PAYLOAD));
8447

8548
PayloadS3Pointer expectedPayloadPointer = new PayloadS3Pointer(S3_BUCKET_NAME, keyCaptor.getValue());
8649
assertEquals(expectedPayloadPointer.toJson(), actualPayloadPointer);
87-
88-
if (expectedParams == null) {
89-
assertTrue(sseArgsCaptor.getValue() == null);
90-
} else {
91-
assertEquals(expectedParams, sseArgsCaptor.getValue());
92-
}
9350
}
9451

9552
@Test
96-
@Parameters(method = "testData")
97-
public void testStoreOriginalPayloadDoesAlwaysCreateNewObjects(PayloadStore payloadStore,
98-
ServerSideEncryptionStrategy expectedParams,
99-
S3Dao mockS3Dao) {
53+
public void testStoreOriginalPayloadDoesAlwaysCreateNewObjects() {
10054
//Store any payload
10155
String anyActualPayloadPointer = payloadStore.storeOriginalPayload(ANY_PAYLOAD);
10256

@@ -105,9 +59,10 @@ public void testStoreOriginalPayloadDoesAlwaysCreateNewObjects(PayloadStore payl
10559

10660
ArgumentCaptor<String> anyOtherKeyCaptor = ArgumentCaptor.forClass(String.class);
10761
ArgumentCaptor<ServerSideEncryptionStrategy> sseArgsCaptor = ArgumentCaptor.forClass(ServerSideEncryptionStrategy.class);
62+
ArgumentCaptor<ObjectCannedACL> cannedArgsCaptor = ArgumentCaptor.forClass(ObjectCannedACL.class);
10863

109-
verify(mockS3Dao, times(2)).storeTextInS3(eq(S3_BUCKET_NAME), anyOtherKeyCaptor.capture(),
110-
sseArgsCaptor.capture(), eq(ANY_PAYLOAD));
64+
verify(s3Dao, times(2)).storeTextInS3(eq(S3_BUCKET_NAME), anyOtherKeyCaptor.capture(),
65+
eq(ANY_PAYLOAD));
11166

11267
String anyS3Key = anyOtherKeyCaptor.getAllValues().get(0);
11368
String anyOtherS3Key = anyOtherKeyCaptor.getAllValues().get(1);
@@ -120,25 +75,15 @@ public void testStoreOriginalPayloadDoesAlwaysCreateNewObjects(PayloadStore payl
12075

12176
assertThat(anyS3Key, Matchers.not(anyOtherS3Key));
12277
assertThat(anyActualPayloadPointer, Matchers.not(anyOtherActualPayloadPointer));
123-
124-
if (expectedParams == null) {
125-
assertTrue(sseArgsCaptor.getAllValues().stream().allMatch(Objects::isNull));
126-
} else {
127-
assertTrue(sseArgsCaptor.getAllValues().stream().allMatch(actualParams ->
128-
actualParams.equals(expectedParams)));
129-
}
13078
}
13179

13280
@Test
133-
@Parameters(method = "testData")
134-
public void testStoreOriginalPayloadOnS3Failure(PayloadStore payloadStore, ServerSideEncryptionStrategy awsKmsKeyId, S3Dao mockS3Dao) {
81+
public void testStoreOriginalPayloadOnS3Failure() {
13582
doThrow(SdkException.create("S3 Exception", new Throwable()))
136-
.when(mockS3Dao)
83+
.when(s3Dao)
13784
.storeTextInS3(
13885
any(String.class),
13986
any(String.class),
140-
// Can be String or null
141-
any(),
14287
any(String.class));
14388

14489
exception.expect(SdkException.class);

0 commit comments

Comments
 (0)