From 2ef661be1cc958580e14383445dfe7a4edd5cff4 Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Tue, 15 Aug 2023 16:57:15 -0700 Subject: [PATCH 1/2] Add support for presigned DeleteObject --- .../feature-AmazonS3-95e65fa.json | 6 + .../s3/S3PresignerIntegrationTest.java | 33 +++++ .../internal/signing/DefaultS3Presigner.java | 19 +++ .../services/s3/presigner/S3Presigner.java | 47 +++++++ .../model/DeleteObjectPresignRequest.java | 108 ++++++++++++++++ .../model/PresignedDeleteObjectRequest.java | 102 +++++++++++++++ .../awssdk/services/s3/S3PresignerTest.java | 120 ++++++++++++++++-- 7 files changed, 426 insertions(+), 9 deletions(-) create mode 100644 .changes/next-release/feature-AmazonS3-95e65fa.json create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/DeleteObjectPresignRequest.java create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/PresignedDeleteObjectRequest.java diff --git a/.changes/next-release/feature-AmazonS3-95e65fa.json b/.changes/next-release/feature-AmazonS3-95e65fa.json new file mode 100644 index 000000000000..12be93c59c8f --- /dev/null +++ b/.changes/next-release/feature-AmazonS3-95e65fa.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "Amazon S3", + "contributor": "", + "description": "Add support for presigned `DeleteObject` in `S3Presigner`." +} diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/s3/S3PresignerIntegrationTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/s3/S3PresignerIntegrationTest.java index 1ca5526a1e4b..c95d47f8fe54 100644 --- a/services/s3/src/it/java/software/amazon/awssdk/services/s3/S3PresignerIntegrationTest.java +++ b/services/s3/src/it/java/software/amazon/awssdk/services/s3/S3PresignerIntegrationTest.java @@ -50,6 +50,7 @@ import software.amazon.awssdk.services.s3.presigner.model.PresignedAbortMultipartUploadRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedCompleteMultipartUploadRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedCreateMultipartUploadRequest; +import software.amazon.awssdk.services.s3.presigner.model.PresignedDeleteObjectRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedUploadPartRequest; @@ -200,6 +201,38 @@ public void getObject_PresignedHttpRequestCanBeInvokedDirectlyBySdk() throws IOE } } + @Test + public void deleteObject_PresignedHttpRequestCanBeInvokedDirectlyBySdk() throws IOException { + String objectKey = generateRandomObjectKey(); + S3TestUtils.addCleanupTask(S3PresignerIntegrationTest.class, + () -> client.deleteObject(r -> r.bucket(testBucket).key(objectKey))); + client.putObject(r -> r.bucket(testBucket).key(objectKey), RequestBody.fromString("DeleteObjectPresignRequestTest")); + + PresignedDeleteObjectRequest presigned = + presigner.presignDeleteObject(r -> r.signatureDuration(Duration.ofMinutes(5)) + .deleteObjectRequest(delo -> delo.bucket(testBucket) + .key(testGetObjectKey) + .requestPayer(RequestPayer.REQUESTER))); + + assertThat(presigned.isBrowserExecutable()).isFalse(); + + SdkHttpClient httpClient = ApacheHttpClient.builder().build(); // or UrlConnectionHttpClient.builder().build() + + ContentStreamProvider requestPayload = presigned.signedPayload() + .map(SdkBytes::asContentStreamProvider) + .orElse(null); + + HttpExecuteRequest request = HttpExecuteRequest.builder() + .request(presigned.httpRequest()) + .contentStreamProvider(requestPayload) + .build(); + + HttpExecuteResponse response = httpClient.prepareRequest(request).call(); + + assertThat(response.responseBody()).isEmpty(); + assertThat(response.httpResponse().statusCode()).isEqualTo(204); + } + @Test public void putObject_PresignedHttpRequestCanBeInvokedDirectlyBySdk() throws IOException { String objectKey = generateRandomObjectKey(); diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java index ec79bb132ffe..ed9fed5f9103 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java @@ -74,6 +74,7 @@ import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest; import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest; import software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.UploadPartRequest; @@ -81,10 +82,12 @@ import software.amazon.awssdk.services.s3.presigner.model.AbortMultipartUploadPresignRequest; import software.amazon.awssdk.services.s3.presigner.model.CompleteMultipartUploadPresignRequest; import software.amazon.awssdk.services.s3.presigner.model.CreateMultipartUploadPresignRequest; +import software.amazon.awssdk.services.s3.presigner.model.DeleteObjectPresignRequest; import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedAbortMultipartUploadRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedCompleteMultipartUploadRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedCreateMultipartUploadRequest; +import software.amazon.awssdk.services.s3.presigner.model.PresignedDeleteObjectRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedUploadPartRequest; @@ -93,6 +96,7 @@ import software.amazon.awssdk.services.s3.transform.AbortMultipartUploadRequestMarshaller; import software.amazon.awssdk.services.s3.transform.CompleteMultipartUploadRequestMarshaller; import software.amazon.awssdk.services.s3.transform.CreateMultipartUploadRequestMarshaller; +import software.amazon.awssdk.services.s3.transform.DeleteObjectRequestMarshaller; import software.amazon.awssdk.services.s3.transform.GetObjectRequestMarshaller; import software.amazon.awssdk.services.s3.transform.PutObjectRequestMarshaller; import software.amazon.awssdk.services.s3.transform.UploadPartRequestMarshaller; @@ -118,6 +122,7 @@ public final class DefaultS3Presigner extends DefaultSdkPresigner implements S3P private final PutObjectRequestMarshaller putObjectRequestMarshaller; private final CreateMultipartUploadRequestMarshaller createMultipartUploadRequestMarshaller; private final UploadPartRequestMarshaller uploadPartRequestMarshaller; + private final DeleteObjectRequestMarshaller deleteObjectRequestMarshaller; private final CompleteMultipartUploadRequestMarshaller completeMultipartUploadRequestMarshaller; private final AbortMultipartUploadRequestMarshaller abortMultipartUploadRequestMarshaller; private final SdkClientConfiguration clientConfiguration; @@ -172,6 +177,9 @@ private DefaultS3Presigner(Builder b) { // Copied from DefaultS3Client#uploadPart this.uploadPartRequestMarshaller = new UploadPartRequestMarshaller(protocolFactory); + // Copied from DefaultS3Client#deleteObject + this.deleteObjectRequestMarshaller = new DeleteObjectRequestMarshaller(protocolFactory); + // Copied from DefaultS3Client#completeMultipartUpload this.completeMultipartUploadRequestMarshaller = new CompleteMultipartUploadRequestMarshaller(protocolFactory); @@ -247,6 +255,17 @@ public PresignedPutObjectRequest presignPutObject(PutObjectPresignRequest reques .build(); } + @Override + public PresignedDeleteObjectRequest presignDeleteObject(DeleteObjectPresignRequest request) { + return presign(PresignedDeleteObjectRequest.builder(), + request, + request.deleteObjectRequest(), + DeleteObjectRequest.class, + deleteObjectRequestMarshaller::marshall, + "DeleteObject") + .build(); + } + @Override public PresignedCreateMultipartUploadRequest presignCreateMultipartUpload(CreateMultipartUploadPresignRequest request) { return presign(PresignedCreateMultipartUploadRequest.builder(), diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/S3Presigner.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/S3Presigner.java index c2aa3e457403..81a55e7bece1 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/S3Presigner.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/S3Presigner.java @@ -35,16 +35,19 @@ import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest; import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest; import software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.UploadPartRequest; import software.amazon.awssdk.services.s3.presigner.model.AbortMultipartUploadPresignRequest; import software.amazon.awssdk.services.s3.presigner.model.CompleteMultipartUploadPresignRequest; import software.amazon.awssdk.services.s3.presigner.model.CreateMultipartUploadPresignRequest; +import software.amazon.awssdk.services.s3.presigner.model.DeleteObjectPresignRequest; import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedAbortMultipartUploadRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedCompleteMultipartUploadRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedCreateMultipartUploadRequest; +import software.amazon.awssdk.services.s3.presigner.model.PresignedDeleteObjectRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedUploadPartRequest; @@ -339,6 +342,50 @@ default PresignedPutObjectRequest presignPutObject(Consumer + * Example Usage + * + *
+     * {@code
+     *     S3Presigner presigner = ...;
+     *
+     *     // Create a DeleteObjectRequest to be pre-signed
+     *     DeleteObjectRequest deleteObjectRequest = ...;
+     *
+     *     // Create a PutObjectPresignRequest to specify the signature duration
+     *     DeleteObjectPresignRequest deleteObjectPresignRequest =
+     *         DeleteObjectPresignRequest.builder()
+     *                                   .signatureDuration(Duration.ofMinutes(10))
+     *                                   .deleteObjectRequest(deleteObjectRequest)
+     *                                   .build();
+     *
+     *     // Generate the presigned request
+     *     PresignedDeleteObjectRequest presignedDeleteObjectRequest =
+     *         presigner.presignDeleteObject(deleteObjectPresignRequest);
+     * }
+     * 
+ */ + PresignedDeleteObjectRequest presignDeleteObject(DeleteObjectPresignRequest request); + + /** + * Presign a {@link DeleteObjectRequest} so that it can be executed at a later time without requiring additional + * signing or authentication. + *

+ * This is a shorter method of invoking {@link #presignDeleteObject(DeleteObjectPresignRequest)} without needing + * to call {@code DeleteObjectPresignRequest.builder()} or {@code .build()}. + * + * @see #presignDeleteObject(PresignedDeleteObjectRequest) + */ + default PresignedDeleteObjectRequest presignDeleteObject(Consumer request) { + DeleteObjectPresignRequest.Builder builder = DeleteObjectPresignRequest.builder(); + request.accept(builder); + return presignDeleteObject(builder.build()); + } + + /** * Presign a {@link CreateMultipartUploadRequest} so that it can be executed at a later time without requiring additional * signing or authentication. diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/DeleteObjectPresignRequest.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/DeleteObjectPresignRequest.java new file mode 100644 index 000000000000..887d5635597f --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/DeleteObjectPresignRequest.java @@ -0,0 +1,108 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.s3.presigner.model; + +import java.time.Duration; +import java.util.function.Consumer; +import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.awscore.presigner.PresignRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * A request to pre-sign a {@link DeleteObjectRequest} so that it can be executed at a later time without requiring additional + * signing or authentication. + * + * @see S3Presigner#presignDeleteObject(DeleteObjectPresignRequest + * @see #builder() + */ +@SdkPublicApi +@Immutable +@ThreadSafe +public final class DeleteObjectPresignRequest extends PresignRequest + implements ToCopyableBuilder { + private final DeleteObjectRequest deleteObjectRequest; + + protected DeleteObjectPresignRequest(DefaultBuilder builder) { + super(builder); + this.deleteObjectRequest = builder.deleteObjectRequest; + } + + /** + * Retrieve the {@link DeleteObjectRequest} that should be presigned. + */ + public DeleteObjectRequest deleteObjectRequest() { + return deleteObjectRequest; + } + + @Override + public Builder toBuilder() { + return new DefaultBuilder(this); + } + + /** + * Create a builder that can be used to create a {@link DeleteObjectPresignRequest}. + * + * @see S3Presigner#presignDeleteObject(DeleteObjectPresignRequest) + */ + public static Builder builder() { + return new DefaultBuilder(); + } + + public interface Builder extends PresignRequest.Builder, + CopyableBuilder { + Builder deleteObjectRequest(DeleteObjectRequest deleteObjectRequest); + + default Builder deleteObjectRequest(Consumer deleteObjectRequest) { + DeleteObjectRequest.Builder builder = DeleteObjectRequest.builder(); + deleteObjectRequest.accept(builder); + return deleteObjectRequest(builder.build()); + } + + @Override + Builder signatureDuration(Duration signatureDuration); + + @Override + DeleteObjectPresignRequest build(); + } + + private static final class DefaultBuilder extends PresignRequest.DefaultBuilder implements Builder { + private DeleteObjectRequest deleteObjectRequest; + + private DefaultBuilder() { + } + + private DefaultBuilder(DeleteObjectPresignRequest deleteObjectPresignRequest) { + super(deleteObjectPresignRequest); + this.deleteObjectRequest = deleteObjectPresignRequest.deleteObjectRequest; + } + + @Override + public Builder deleteObjectRequest(DeleteObjectRequest deleteObjectRequest) { + this.deleteObjectRequest = deleteObjectRequest; + return this; + } + + @Override + public DeleteObjectPresignRequest build() { + return new DeleteObjectPresignRequest(this); + } + } +} diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/PresignedDeleteObjectRequest.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/PresignedDeleteObjectRequest.java new file mode 100644 index 000000000000..5fa4fa116cf5 --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/PresignedDeleteObjectRequest.java @@ -0,0 +1,102 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.s3.presigner.model; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.annotations.NotThreadSafe; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.awscore.presigner.PresignedRequest; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * A pre-signed a {@link DeleteObjectRequest} that can be executed at a later time without requiring additional signing or + * authentication. + * + * @see S3Presigner#presignDeleteObject(DeleteObjectPresignRequest) + * @see #builder() + */ +public class PresignedDeleteObjectRequest extends PresignedRequest + implements ToCopyableBuilder { + + protected PresignedDeleteObjectRequest(DefaultBuilder builder) { + super(builder); + } + + @Override + public Builder toBuilder() { + return new DefaultBuilder(this); + } + + /** + * Create a builder that can be used to create a {@link PresignedDeleteObjectRequest}. + * + * @see S3Presigner#presignDeleteObject(DeleteObjectPresignRequest) + */ + public static Builder builder() { + return new DefaultBuilder(); + } + + /** + * A builder for a {@link PresignedDeleteObjectRequest}, created with {@link #builder()}. + */ + @SdkPublicApi + @NotThreadSafe + public interface Builder extends PresignedRequest.Builder, + CopyableBuilder { + @Override + Builder expiration(Instant expiration); + + @Override + Builder isBrowserExecutable(Boolean isBrowserExecutable); + + @Override + Builder signedHeaders(Map> signedHeaders); + + @Override + Builder signedPayload(SdkBytes signedPayload); + + @Override + Builder httpRequest(SdkHttpRequest httpRequest); + + @Override + PresignedDeleteObjectRequest build(); + } + + @SdkInternalApi + private static final class DefaultBuilder extends PresignedRequest.DefaultBuilder + implements PresignedDeleteObjectRequest.Builder { + + private DefaultBuilder() { + } + + private DefaultBuilder(PresignedDeleteObjectRequest presignedDeleteObjectRequest) { + super(presignedDeleteObjectRequest); + } + + @Override + public PresignedDeleteObjectRequest build() { + return new PresignedDeleteObjectRequest(this); + } + } +} diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/S3PresignerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/S3PresignerTest.java index 760eb86b959a..b413f7b33e01 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/S3PresignerTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/S3PresignerTest.java @@ -23,8 +23,6 @@ import java.time.Clock; import java.time.Duration; import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import org.assertj.core.data.Offset; @@ -32,32 +30,26 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.auth.signer.AwsS3V4Signer; -import software.amazon.awssdk.auth.signer.internal.AbstractAws4Signer; import software.amazon.awssdk.auth.signer.internal.AbstractAwsS3V4Signer; -import software.amazon.awssdk.auth.signer.internal.Aws4SignerRequestParams; import software.amazon.awssdk.auth.signer.internal.SignerConstant; import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams; -import software.amazon.awssdk.auth.signer.params.AwsS3V4SignerParams; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.exception.SdkClientException; -import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.signer.NoOpSigner; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.s3.checksums.ChecksumConstant; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.RequestPayer; import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.services.s3.presigner.model.PresignedDeleteObjectRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest; -import software.amazon.awssdk.utils.DateUtils; @RunWith(MockitoJUnitRunner.class) public class S3PresignerTest { @@ -349,6 +341,116 @@ public void putObject_Sigv4PresignerHonorsSignatureDuration() { }); } + @Test + public void deleteObject_IsNotUrlCompatible() { + PresignedDeleteObjectRequest presigned = + presigner.presignDeleteObject(r -> r.signatureDuration(Duration.ofMinutes(5)) + .deleteObjectRequest(delo -> delo.bucket("foo34343434") + .key("bar"))); + assertThat(presigned.isBrowserExecutable()).isFalse(); + assertThat(presigned.signedHeaders().keySet()).containsExactlyInAnyOrder("host"); + assertThat(presigned.signedPayload()).isEmpty(); + } + + @Test + public void deleteObject_EndpointOverrideIsIncludedInPresignedUrl() { + S3Presigner presigner = presignerBuilder().endpointOverride(URI.create("http://foo.com")).build(); + PresignedDeleteObjectRequest presigned = + presigner.presignDeleteObject(r -> r.signatureDuration(Duration.ofMinutes(5)) + .deleteObjectRequest(delo -> delo.bucket("foo34343434") + .key("bar"))); + + assertThat(presigned.url().toString()).startsWith("http://foo34343434.foo.com/bar?"); + assertThat(presigned.signedHeaders().get("host")).containsExactly("foo34343434.foo.com"); + assertThat(presigned.signedPayload()).isEmpty(); + } + + @Test + public void deleteObject_CredentialsCanBeOverriddenAtTheRequestLevel() { + AwsCredentials clientCredentials = AwsBasicCredentials.create("a", "a"); + AwsCredentials requestCredentials = AwsBasicCredentials.create("b", "b"); + + S3Presigner presigner = presignerBuilder().credentialsProvider(() -> clientCredentials).build(); + + + AwsRequestOverrideConfiguration overrideConfiguration = + AwsRequestOverrideConfiguration.builder() + .credentialsProvider(() -> requestCredentials) + .build(); + + PresignedDeleteObjectRequest presignedWithClientCredentials = + presigner.presignDeleteObject(r -> r.signatureDuration(Duration.ofMinutes(5)) + .deleteObjectRequest(delo -> delo.bucket("foo34343434") + .key("bar"))); + + PresignedDeleteObjectRequest presignedWithRequestCredentials = + presigner.presignDeleteObject(r -> r.signatureDuration(Duration.ofMinutes(5)) + .deleteObjectRequest(delo -> delo.bucket("foo34343434") + .key("bar") + .overrideConfiguration(overrideConfiguration))); + + + assertThat(presignedWithClientCredentials.httpRequest().rawQueryParameters().get("X-Amz-Credential").get(0)) + .startsWith("a"); + assertThat(presignedWithRequestCredentials.httpRequest().rawQueryParameters().get("X-Amz-Credential").get(0)) + .startsWith("b"); + } + + @Test + public void deleteObject_AdditionalHeadersAndQueryStringsCanBeAdded() { + AwsRequestOverrideConfiguration override = + AwsRequestOverrideConfiguration.builder() + .putHeader("X-Amz-AdditionalHeader", "foo1") + .putRawQueryParameter("additionalQueryParam", "foo2") + .build(); + + PresignedDeleteObjectRequest presigned = + presigner.presignDeleteObject(r -> r.signatureDuration(Duration.ofMinutes(5)) + .deleteObjectRequest(delo -> delo.bucket("foo34343434") + .key("bar") + .overrideConfiguration(override))); + + assertThat(presigned.isBrowserExecutable()).isFalse(); + assertThat(presigned.signedHeaders()).containsOnlyKeys("host", "x-amz-additionalheader"); + assertThat(presigned.signedHeaders().get("x-amz-additionalheader")).containsExactly("foo1"); + assertThat(presigned.httpRequest().headers()).containsKeys("x-amz-additionalheader"); + assertThat(presigned.httpRequest().rawQueryParameters().get("additionalQueryParam").get(0)).isEqualTo("foo2"); + } + + @Test + public void deleteObject_NonSigV4SignersRaisesException() { + AwsRequestOverrideConfiguration override = + AwsRequestOverrideConfiguration.builder() + .signer(new NoOpSigner()) + .build(); + + assertThatThrownBy(() -> presigner.presignDeleteObject(r -> r.signatureDuration(Duration.ofMinutes(5)) + .deleteObjectRequest(delo -> delo.bucket("foo34343434") + .key("bar") + .overrideConfiguration(override)))) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("NoOpSigner"); + } + + @Test + public void deleteObject_Sigv4PresignerHonorsSignatureDuration() { + AwsRequestOverrideConfiguration override = + AwsRequestOverrideConfiguration.builder() + .signer(AwsS3V4Signer.create()) + .build(); + + PresignedDeleteObjectRequest presigned = + presigner.presignDeleteObject(r -> r.signatureDuration(Duration.ofSeconds(1234)) + .deleteObjectRequest(delo -> delo.bucket("a") + .key("b") + .overrideConfiguration(override))); + + assertThat(presigned.httpRequest().rawQueryParameters().get("X-Amz-Expires").get(0)).satisfies(expires -> { + assertThat(expires).containsOnlyDigits(); + assertThat(Integer.parseInt(expires)).isCloseTo(1234, Offset.offset(2)); + }); + } + @Test public void getObject_S3ConfigurationCanBeOverriddenToLeverageTransferAcceleration() { S3Presigner presigner = presignerBuilder().serviceConfiguration(S3Configuration.builder() From ddafaf42f389b033b91e4a7f1350b142f9344790 Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Wed, 23 Aug 2023 12:26:43 -0700 Subject: [PATCH 2/2] Review comments - Add proper annotations - Add equals and hashCode --- .../model/DeleteObjectPresignRequest.java | 32 ++++++++++++++++++- .../model/PresignedDeleteObjectRequest.java | 5 +++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/DeleteObjectPresignRequest.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/DeleteObjectPresignRequest.java index 887d5635597f..3fce17b22f5d 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/DeleteObjectPresignRequest.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/DeleteObjectPresignRequest.java @@ -18,11 +18,14 @@ import java.time.Duration; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Immutable; +import software.amazon.awssdk.annotations.NotThreadSafe; +import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.awscore.presigner.PresignRequest; import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.CopyableBuilder; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -42,7 +45,7 @@ public final class DeleteObjectPresignRequest extends PresignRequest protected DeleteObjectPresignRequest(DefaultBuilder builder) { super(builder); - this.deleteObjectRequest = builder.deleteObjectRequest; + this.deleteObjectRequest = Validate.notNull(builder.deleteObjectRequest, "deleteObjectRequest"); } /** @@ -66,6 +69,32 @@ public static Builder builder() { return new DefaultBuilder(); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + DeleteObjectPresignRequest that = (DeleteObjectPresignRequest) o; + + return deleteObjectRequest.equals(that.deleteObjectRequest); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + deleteObjectRequest.hashCode(); + return result; + } + + @SdkPublicApi + @NotThreadSafe public interface Builder extends PresignRequest.Builder, CopyableBuilder { Builder deleteObjectRequest(DeleteObjectRequest deleteObjectRequest); @@ -83,6 +112,7 @@ default Builder deleteObjectRequest(Consumer delete DeleteObjectPresignRequest build(); } + @SdkInternalApi private static final class DefaultBuilder extends PresignRequest.DefaultBuilder implements Builder { private DeleteObjectRequest deleteObjectRequest; diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/PresignedDeleteObjectRequest.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/PresignedDeleteObjectRequest.java index 5fa4fa116cf5..3ce2d2569965 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/PresignedDeleteObjectRequest.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presigner/model/PresignedDeleteObjectRequest.java @@ -18,9 +18,11 @@ import java.time.Instant; import java.util.List; import java.util.Map; +import software.amazon.awssdk.annotations.Immutable; import software.amazon.awssdk.annotations.NotThreadSafe; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.awscore.presigner.PresignedRequest; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.http.SdkHttpRequest; @@ -36,6 +38,9 @@ * @see S3Presigner#presignDeleteObject(DeleteObjectPresignRequest) * @see #builder() */ +@SdkPublicApi +@Immutable +@ThreadSafe public class PresignedDeleteObjectRequest extends PresignedRequest implements ToCopyableBuilder {