Skip to content

Commit db13aad

Browse files
committed
Address PR feedback
1 parent 40605b3 commit db13aad

3 files changed

Lines changed: 187 additions & 1 deletion

File tree

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/s3express/S3ExpressUtils.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
public final class S3ExpressUtils {
3535

3636
public static final String S3_EXPRESS = "S3Express";
37+
private static final String S3_EXPRESS_BUCKET_SUFFIX = "--x-s3";
3738

3839
private S3ExpressUtils() {
3940
}
@@ -43,7 +44,7 @@ private S3ExpressUtils() {
4344
*/
4445
public static boolean isS3ExpressBucket(SdkRequest request) {
4546
return request.getValueForField("Bucket", String.class)
46-
.map(b -> b.endsWith("--x-s3"))
47+
.map(b -> b.endsWith(S3_EXPRESS_BUCKET_SUFFIX))
4748
.orElse(false);
4849
}
4950

services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import org.junit.jupiter.api.Test;
2323
import org.junit.jupiter.params.ParameterizedTest;
2424
import org.junit.jupiter.params.provider.ValueSource;
25+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
2526
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
27+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
2628
import software.amazon.awssdk.auth.signer.AwsS3V4Signer;
2729
import software.amazon.awssdk.core.async.AsyncRequestBody;
2830
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
@@ -31,8 +33,10 @@
3133
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
3234
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
3335
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
36+
import software.amazon.awssdk.http.SdkHttpExecutionAttributes;
3437
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
3538
import software.amazon.awssdk.identity.spi.IdentityProvider;
39+
import software.amazon.awssdk.regions.Region;
3640
import software.amazon.awssdk.services.s3.DelegatingS3AsyncClient;
3741
import software.amazon.awssdk.services.s3.S3AsyncClient;
3842
import software.amazon.awssdk.services.s3.endpoints.S3ClientContextParams;
@@ -161,4 +165,75 @@ void build_withAdvancedOptions() {
161165
assertThat(client).isInstanceOf(DefaultS3CrtAsyncClient.class);
162166
}
163167
}
168+
169+
@Test
170+
void s3ExpressBucket_defaultConfig_useS3ExpressAuthIsTrue() {
171+
AtomicReference<Boolean> capturedUseS3ExpressAuth = new AtomicReference<>();
172+
173+
ExecutionInterceptor captor = new ExecutionInterceptor() {
174+
@Override
175+
public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) {
176+
SdkHttpExecutionAttributes httpAttrs =
177+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.SDK_HTTP_EXECUTION_ATTRIBUTES);
178+
if (httpAttrs != null) {
179+
capturedUseS3ExpressAuth.set(httpAttrs.getAttribute(S3InternalSdkHttpExecutionAttribute.USE_S3_EXPRESS_AUTH));
180+
}
181+
throw new RuntimeException("STOP");
182+
}
183+
};
184+
185+
DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder builder =
186+
(DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder) S3CrtAsyncClient.builder();
187+
builder.addExecutionInterceptor(captor);
188+
189+
try (S3AsyncClient client = builder
190+
.region(Region.US_EAST_1)
191+
.credentialsProvider(StaticCredentialsProvider.create(
192+
AwsBasicCredentials.create("key", "secret")))
193+
.build()) {
194+
195+
assertThatThrownBy(() -> client.getObject(
196+
r -> r.bucket("my-bucket--usw2-az1--x-s3").key("key"),
197+
AsyncResponseTransformer.toBytes()).join())
198+
.hasMessageContaining("STOP");
199+
}
200+
201+
assertThat(capturedUseS3ExpressAuth.get()).isTrue();
202+
}
203+
204+
@Test
205+
void s3ExpressBucket_disableS3ExpressSessionAuth_useS3ExpressAuthIsFalse() {
206+
AtomicReference<Boolean> capturedUseS3ExpressAuth = new AtomicReference<>();
207+
208+
ExecutionInterceptor captor = new ExecutionInterceptor() {
209+
@Override
210+
public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) {
211+
SdkHttpExecutionAttributes httpAttrs =
212+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.SDK_HTTP_EXECUTION_ATTRIBUTES);
213+
if (httpAttrs != null) {
214+
capturedUseS3ExpressAuth.set(httpAttrs.getAttribute(S3InternalSdkHttpExecutionAttribute.USE_S3_EXPRESS_AUTH));
215+
}
216+
throw new RuntimeException("STOP");
217+
}
218+
};
219+
220+
DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder builder =
221+
(DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder) S3CrtAsyncClient.builder();
222+
builder.addExecutionInterceptor(captor);
223+
224+
try (S3AsyncClient client = builder
225+
.region(Region.US_EAST_1)
226+
.credentialsProvider(StaticCredentialsProvider.create(
227+
AwsBasicCredentials.create("key", "secret")))
228+
.disableS3ExpressSessionAuth(true)
229+
.build()) {
230+
231+
assertThatThrownBy(() -> client.getObject(
232+
r -> r.bucket("my-bucket--usw2-az1--x-s3").key("key"),
233+
AsyncResponseTransformer.toBytes()).join())
234+
.hasMessageContaining("STOP");
235+
}
236+
237+
assertThat(capturedUseS3ExpressAuth.get()).isFalse();
238+
}
164239
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services.s3.internal.s3express;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import com.fasterxml.jackson.databind.JsonNode;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import java.io.IOException;
23+
import java.nio.file.Path;
24+
import java.nio.file.Paths;
25+
import java.util.ArrayList;
26+
import java.util.List;
27+
import org.junit.jupiter.api.Test;
28+
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
29+
30+
class S3ExpressUtilsTest {
31+
32+
private static final ObjectMapper MAPPER = new ObjectMapper();
33+
34+
@Test
35+
void isS3ExpressBucket_bucketWithS3ExpressSuffix_returnsTrue() {
36+
GetObjectRequest request = GetObjectRequest.builder()
37+
.bucket("my-bucket--use1-az1--x-s3")
38+
.key("key")
39+
.build();
40+
assertThat(S3ExpressUtils.isS3ExpressBucket(request)).isTrue();
41+
}
42+
43+
@Test
44+
void isS3ExpressBucket_regularBucket_returnsFalse() {
45+
GetObjectRequest request = GetObjectRequest.builder()
46+
.bucket("my-regular-bucket")
47+
.key("key")
48+
.build();
49+
assertThat(S3ExpressUtils.isS3ExpressBucket(request)).isFalse();
50+
}
51+
52+
@Test
53+
void isS3ExpressBucket_noBucketField_returnsFalse() {
54+
GetObjectRequest request = GetObjectRequest.builder()
55+
.key("key")
56+
.build();
57+
assertThat(S3ExpressUtils.isS3ExpressBucket(request)).isFalse();
58+
}
59+
60+
/**
61+
* Validates that the S3Express bucket suffix used in {@link S3ExpressUtils#isS3ExpressBucket} matches the suffix
62+
* defined in the endpoint ruleset.
63+
*/
64+
@Test
65+
void isS3ExpressBucket_suffixMatchesEndpointRuleset() throws IOException {
66+
String rulesetSuffix = extractBucketSuffixFromRuleset();
67+
GetObjectRequest request = GetObjectRequest.builder()
68+
.bucket("test-bucket" + rulesetSuffix)
69+
.key("key")
70+
.build();
71+
assertThat(S3ExpressUtils.isS3ExpressBucket(request))
72+
.as("isS3ExpressBucket should recognize the suffix '%s' from the endpoint ruleset", rulesetSuffix)
73+
.isTrue();
74+
}
75+
76+
/**
77+
* Parses the endpoint-rule-set.json and extracts the S3Express bucket suffix.
78+
*/
79+
private String extractBucketSuffixFromRuleset() throws IOException {
80+
Path rulesetPath = Paths.get("src/main/resources/codegen-resources/endpoint-rule-set.json");
81+
assertThat(rulesetPath.toFile()).as("endpoint-rule-set.json should exist").exists();
82+
JsonNode root = MAPPER.readTree(rulesetPath.toFile());
83+
List<String> suffixes = new ArrayList<>();
84+
findBucketSuffixValues(root, suffixes);
85+
assertThat(suffixes)
86+
.as("Expected exactly one bucketSuffix stringEquals check in the endpoint ruleset")
87+
.hasSize(1);
88+
return suffixes.get(0);
89+
}
90+
91+
private void findBucketSuffixValues(JsonNode node, List<String> results) {
92+
if (node.isObject() && "stringEquals".equals(node.path("fn").asText(null))) {
93+
JsonNode argv = node.path("argv");
94+
if (argv.isArray() && argv.size() == 2) {
95+
for (int i = 0; i < 2; i++) {
96+
JsonNode arg = argv.get(i);
97+
JsonNode other = argv.get(1 - i);
98+
if (arg.isObject() && "bucketSuffix".equals(arg.path("ref").asText(null))
99+
&& other.isTextual()) {
100+
results.add(other.asText());
101+
return;
102+
}
103+
}
104+
}
105+
}
106+
for (JsonNode child : node) {
107+
findBucketSuffixValues(child, results);
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)