Skip to content

Commit 38e5527

Browse files
committed
Support POST Object requests
Not all parameters are supported right now. Fixes #2200
1 parent 32de437 commit 38e5527

File tree

11 files changed

+385
-115
lines changed

11 files changed

+385
-115
lines changed

CHANGELOG.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,11 @@ Version 4.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav
160160
* These were corner cases where error messages were incorrect, or proper validations were missing.
161161
* Migrate all integration tests to AWS SDK v2, remove AWS SDK v1 tests from the integration-tests module
162162
* Version updates (deliverable dependencies)
163-
* TBD
163+
* Bump aws-v2.version from 2.31.17 to 2.31.25
164+
* Bump aws.sdk.kotlin:s3-jvm from 1.4.59 to 1.4.67
165+
* Bump commons-io:commons-io from 2.18.0 to 2.19.0
164166
* Version updates (build dependencies)
167+
* Bump step-security/harden-runner from 2.11.1 to 2.12.0
165168
* Bump actions/setup-java from 4.7.0 to 4.7.1
166169

167170
## 4.0.0

README.md

+100-99
Large diffs are not rendered by default.

integration-tests/pom.xml

+25
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,31 @@
123123
<artifactId>s3-jvm</artifactId>
124124
<scope>test</scope>
125125
</dependency>
126+
<dependency>
127+
<groupId>org.apache.httpcomponents</groupId>
128+
<artifactId>httpclient</artifactId>
129+
<scope>test</scope>
130+
</dependency>
131+
<dependency>
132+
<groupId>org.apache.httpcomponents</groupId>
133+
<artifactId>httpcore</artifactId>
134+
<scope>test</scope>
135+
</dependency>
136+
<dependency>
137+
<groupId>org.apache.httpcomponents</groupId>
138+
<artifactId>httpmime</artifactId>
139+
<scope>test</scope>
140+
</dependency>
141+
<dependency>
142+
<!--
143+
Workaround, AWS SDK for Java v2 currently does not support presigned URLs for POST requests.
144+
see https://github.com/aws/aws-sdk-java-v2/issues/1493
145+
-->
146+
<groupId>tel.schich</groupId>
147+
<artifactId>aws-s3-post-object-presigner</artifactId>
148+
<version>0.1.1</version>
149+
<scope>test</scope>
150+
</dependency>
126151
</dependencies>
127152

128153
<build>

integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/PresignedUrlIT.kt

+99
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@ package com.adobe.testing.s3mock.its
1818
import com.adobe.testing.s3mock.dto.InitiateMultipartUploadResult
1919
import com.adobe.testing.s3mock.util.DigestUtil
2020
import org.apache.http.HttpHeaders
21+
import org.apache.http.HttpHeaders.CONTENT_TYPE
2122
import org.apache.http.HttpStatus
2223
import org.apache.http.client.methods.HttpDelete
2324
import org.apache.http.client.methods.HttpGet
2425
import org.apache.http.client.methods.HttpPost
2526
import org.apache.http.client.methods.HttpPut
27+
import org.apache.http.entity.ByteArrayEntity
28+
import org.apache.http.entity.ContentType
2629
import org.apache.http.entity.FileEntity
2730
import org.apache.http.entity.StringEntity
31+
import org.apache.http.entity.mime.MultipartEntityBuilder
2832
import org.apache.http.impl.client.CloseableHttpClient
2933
import org.apache.http.message.BasicHeader
3034
import org.assertj.core.api.Assertions.assertThat
@@ -34,6 +38,8 @@ import software.amazon.awssdk.core.sync.RequestBody
3438
import software.amazon.awssdk.services.s3.S3Client
3539
import software.amazon.awssdk.services.s3.model.CompletedPart
3640
import software.amazon.awssdk.services.s3.presigner.S3Presigner
41+
import tel.schich.awss3postobjectpresigner.S3PostObjectPresigner
42+
import tel.schich.awss3postobjectpresigner.S3PostObjectRequest
3743
import java.io.File
3844
import java.nio.file.Files
3945
import java.nio.file.Path
@@ -44,6 +50,62 @@ internal class PresignedUrlIT : S3TestBase() {
4450
private val httpClient: CloseableHttpClient = createHttpClient()
4551
private val s3Client: S3Client = createS3Client()
4652
private val s3Presigner: S3Presigner = createS3Presigner()
53+
private val s3PostObjectPresigner: S3PostObjectPresigner = createS3PostObjectPresigner()
54+
55+
@Test
56+
@S3VerifiedFailure(year = 2025,
57+
reason = "S3PostObjectPresigner does not create working presigned URLs.")
58+
fun testPresignedUrl_postObject_largeFile(testInfo: TestInfo) {
59+
val key = randomName
60+
val bucketName = givenBucket(testInfo)
61+
62+
val presignedUrlString = s3PostObjectPresigner.presignPost(
63+
S3PostObjectRequest
64+
.builder()
65+
.bucket(bucketName)
66+
.expiration(Duration.ofMinutes(1L))
67+
.build()
68+
).uri().toString()
69+
70+
assertThat(presignedUrlString).isNotBlank()
71+
72+
val randomMBytes = randomMBytes(20)
73+
HttpPost(presignedUrlString).apply {
74+
this.entity = MultipartEntityBuilder.create()
75+
.addTextBody("key", key)
76+
.addTextBody(CONTENT_TYPE, "application/octet-stream")
77+
//.addTextBody(X_AMZ_STORAGE_CLASS, "INTELLIGENT_TIERING")
78+
.addTextBody("tagging", "<Tagging><TagSet><Tag><Key>Tag Name</Key><Value>Tag Value</Value></Tag></TagSet></Tagging>")
79+
.addBinaryBody("file", randomMBytes.inputStream(), ContentType.APPLICATION_OCTET_STREAM, key)
80+
.build()
81+
}.also { post ->
82+
httpClient.execute(
83+
post
84+
).use {
85+
assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK)
86+
val expectedEtag = "\"${DigestUtil.hexDigest(randomMBytes.inputStream())}\""
87+
val actualEtag = it.getFirstHeader(HttpHeaders.ETAG).value
88+
assertThat(actualEtag).isEqualTo(expectedEtag)
89+
}
90+
}
91+
92+
s3Client.getObject {
93+
it.bucket(bucketName)
94+
it.key(key)
95+
}.use {
96+
val expectedEtag = "\"${DigestUtil.hexDigest(randomMBytes.inputStream())}\""
97+
val actualEtag = "\"${DigestUtil.hexDigest(it)}\""
98+
assertThat(actualEtag).isEqualTo(expectedEtag)
99+
}
100+
s3Client.getObjectTagging {
101+
it.bucket(bucketName)
102+
it.key(key)
103+
}.also {
104+
assertThat(it.tagSet()).hasSize(1)
105+
assertThat(it.tagSet()[0].key()).isEqualTo("Tag Name")
106+
assertThat(it.tagSet()[0].value()).isEqualTo("Tag Value")
107+
}
108+
}
47109

48110
@Test
49111
@S3VerifiedSuccess(year = 2025)
@@ -180,6 +242,43 @@ internal class PresignedUrlIT : S3TestBase() {
180242
}
181243
}
182244

245+
@Test
246+
@S3VerifiedSuccess(year = 2025)
247+
fun testPresignedUrl_putObject_largeFile(testInfo: TestInfo) {
248+
val key = randomName
249+
val bucketName = givenBucket(testInfo)
250+
251+
val presignedUrlString = s3Presigner.presignPutObject {
252+
it.putObjectRequest {
253+
it.bucket(bucketName)
254+
it.key(key)
255+
}
256+
it.signatureDuration(Duration.ofMinutes(1L))
257+
}.url().toString()
258+
259+
assertThat(presignedUrlString).isNotBlank()
260+
261+
val randomMBytes = randomMBytes(20)
262+
HttpPut(presignedUrlString).apply {
263+
this.entity = ByteArrayEntity(randomMBytes)
264+
}.also { put ->
265+
httpClient.execute(
266+
put
267+
).use {
268+
assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK)
269+
}
270+
}
271+
272+
s3Client.getObject {
273+
it.bucket(bucketName)
274+
it.key(key)
275+
}.use {
276+
val expectedEtag = "\"${DigestUtil.hexDigest(randomMBytes.inputStream())}\""
277+
val actualEtag = "\"${DigestUtil.hexDigest(it)}\""
278+
assertThat(actualEtag).isEqualTo(expectedEtag)
279+
}
280+
}
281+
183282
@Test
184283
@S3VerifiedSuccess(year = 2025)
185284
fun testPresignedUrl_createMultipartUpload(testInfo: TestInfo) {

integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/S3TestBase.kt

+13-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import software.amazon.awssdk.services.s3.model.UploadPartResponse
5555
import software.amazon.awssdk.services.s3.presigner.S3Presigner
5656
import software.amazon.awssdk.transfer.s3.S3TransferManager
5757
import software.amazon.awssdk.utils.AttributeMap
58+
import tel.schich.awss3postobjectpresigner.S3PostObjectPresigner
5859
import java.io.ByteArrayInputStream
5960
import java.io.ByteArrayOutputStream
6061
import java.io.File
@@ -209,6 +210,17 @@ internal abstract class S3TestBase {
209210
.build()
210211
}
211212

213+
protected fun createS3PostObjectPresigner(endpoint: String = serviceEndpoint): S3PostObjectPresigner {
214+
return S3PostObjectPresigner.builder()
215+
.region(Region.of(s3Region))
216+
.credentialsProvider(
217+
StaticCredentialsProvider.create(AwsBasicCredentials.create(s3AccessKeyId, s3SecretAccessKey))
218+
)
219+
.serviceConfiguration(S3Configuration.builder().pathStyleAccessEnabled(true).build())
220+
.endpointOverride(URI.create(endpoint))
221+
.build()
222+
}
223+
212224
/**
213225
* Deletes all existing buckets.
214226
*/
@@ -467,7 +479,7 @@ internal abstract class S3TestBase {
467479
return randomMBytes(_5MB.toInt())
468480
}
469481

470-
private fun randomMBytes(size: Int): ByteArray {
482+
protected fun randomMBytes(size: Int): ByteArray {
471483
val bytes = ByteArray(size)
472484
Random.nextBytes(bytes)
473485
return bytes

pom.xml

+35-8
Original file line numberDiff line numberDiff line change
@@ -89,26 +89,32 @@
8989
</distributionManagement>
9090

9191
<properties>
92+
<java.version>17</java.version>
93+
<kotlin.version>2.1.20</kotlin.version>
94+
<kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
95+
<kotlin.compiler.apiVersion>2.1</kotlin.compiler.apiVersion>
96+
<kotlin.compiler.languageVersion>2.1</kotlin.compiler.languageVersion>
97+
9298
<aws-v2.version>2.31.25</aws-v2.version>
9399

94100
<aws.version>1.12.782</aws.version>
95101

96102
<aws-kotlin.version>1.4.67</aws-kotlin.version>
97103

98-
<checkstyle.version>10.23.0</checkstyle.version>
99104
<commons-codec.version>1.15</commons-codec.version>
100105
<commons-io.version>2.19.0</commons-io.version>
106+
<httpclient.version>4.5.14</httpclient.version>
107+
<httpmime.version>4.5.14</httpmime.version>
108+
<httpcore.version>4.4.16</httpcore.version>
109+
101110
<docker-builder.image.name>s3mock-buildx</docker-builder.image.name>
102111
<docker-maven-plugin.version>0.46.0</docker-maven-plugin.version>
103-
104112
<docker.image.name>adobe/s3mock</docker.image.name>
105-
<java.version>17</java.version>
113+
106114
<junit-jupiter.version>5.7.2</junit-jupiter.version>
107115
<junit.version>4.13.2</junit.version>
108-
<kotlin.version>2.1.20</kotlin.version>
109-
<kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
110-
<kotlin.compiler.apiVersion>2.1</kotlin.compiler.apiVersion>
111-
<kotlin.compiler.languageVersion>2.1</kotlin.compiler.languageVersion>
116+
117+
<checkstyle.version>10.23.0</checkstyle.version>
112118
<license-maven-plugin-git.version>5.0.0</license-maven-plugin-git.version>
113119
<maven-checkstyle-plugin.version>3.6.0</maven-checkstyle-plugin.version>
114120
<maven-clean-plugin.version>3.4.1</maven-clean-plugin.version>
@@ -124,6 +130,7 @@
124130
<maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
125131
<maven-source-plugin.version>3.3.1</maven-source-plugin.version>
126132
<maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
133+
<exec-maven-plugin.version>3.5.0</exec-maven-plugin.version>
127134
<mockito-kotlin.version>5.4.0</mockito-kotlin.version>
128135
<nexus-staging-maven-plugin.version>1.7.0</nexus-staging-maven-plugin.version>
129136
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -280,6 +287,26 @@
280287
<artifactId>s3-jvm</artifactId>
281288
<version>${aws-kotlin.version}</version>
282289
</dependency>
290+
<dependency>
291+
<groupId>org.apache.httpcomponents</groupId>
292+
<artifactId>httpclient</artifactId>
293+
<version>${httpclient.version}</version>
294+
</dependency>
295+
<dependency>
296+
<groupId>org.apache.httpcomponents</groupId>
297+
<artifactId>httpcore</artifactId>
298+
<version>${httpcore.version}</version>
299+
</dependency>
300+
<dependency>
301+
<groupId>org.apache.httpcomponents</groupId>
302+
<artifactId>httpmime</artifactId>
303+
<version>${httpmime.version}</version>
304+
</dependency>
305+
<dependency>
306+
<groupId>org.mockito</groupId>
307+
<artifactId>mockito-core</artifactId>
308+
<scope>test</scope>
309+
</dependency>
283310
</dependencies>
284311
</dependencyManagement>
285312

@@ -554,7 +581,7 @@
554581
<plugin>
555582
<groupId>org.codehaus.mojo</groupId>
556583
<artifactId>exec-maven-plugin</artifactId>
557-
<version>3.5.0</version>
584+
<version>${exec-maven-plugin.version}</version>
558585
</plugin>
559586
<plugin>
560587
<!--

server/src/main/java/com/adobe/testing/s3mock/BucketController.java

-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package com.adobe.testing.s3mock;
1818

19-
import static com.adobe.testing.s3mock.S3Exception.NOT_FOUND_BUCKET_VERSIONING_CONFIGURATION;
2019
import static com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_BUCKET_OBJECT_LOCK_ENABLED;
2120
import static com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_OBJECT_OWNERSHIP;
2221
import static com.adobe.testing.s3mock.util.AwsHttpParameters.CONTINUATION_TOKEN;
@@ -49,7 +48,6 @@
4948
import com.adobe.testing.s3mock.dto.ObjectLockConfiguration;
5049
import com.adobe.testing.s3mock.dto.VersioningConfiguration;
5150
import com.adobe.testing.s3mock.service.BucketService;
52-
import com.adobe.testing.s3mock.store.BucketMetadata;
5351
import org.springframework.http.ResponseEntity;
5452
import org.springframework.stereotype.Controller;
5553
import org.springframework.web.bind.annotation.CrossOrigin;

0 commit comments

Comments
 (0)