From 3e238d03972d2fc957e6bf46ed25540ec787964f Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Mon, 21 Nov 2022 21:00:25 +0100 Subject: [PATCH 1/3] BucketNameFilter to access bucket name in domain BucketNameFilter will extract the bucket name from subdomain or path and add it as a request attribute for consumption in handler methods. Fixes #144 --- server/pom.xml | 5 + .../testing/s3mock/BucketNameFilter.java | 100 ++++++++++++++ .../testing/s3mock/S3MockConfiguration.java | 5 + .../adobe/testing/s3mock/dto/BucketName.java | 55 ++++++++ .../testing/s3mock/BucketNameFilterTest.java | 130 ++++++++++++++++++ .../testing/s3mock/FaviconControllerTest.java | 2 + 6 files changed, 297 insertions(+) create mode 100644 server/src/main/java/com/adobe/testing/s3mock/BucketNameFilter.java create mode 100644 server/src/main/java/com/adobe/testing/s3mock/dto/BucketName.java create mode 100644 server/src/test/java/com/adobe/testing/s3mock/BucketNameFilterTest.java diff --git a/server/pom.xml b/server/pom.xml index f64059c72..fdf0b001b 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -124,6 +124,11 @@ spring-boot-starter-test test + + com.google.guava + guava + 31.1-jre + org.springframework.boot spring-boot-starter-web diff --git a/server/src/main/java/com/adobe/testing/s3mock/BucketNameFilter.java b/server/src/main/java/com/adobe/testing/s3mock/BucketNameFilter.java new file mode 100644 index 000000000..36acd559f --- /dev/null +++ b/server/src/main/java/com/adobe/testing/s3mock/BucketNameFilter.java @@ -0,0 +1,100 @@ +/* + * Copyright 2017-2022 Adobe. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 com.adobe.testing.s3mock; + +import static org.springframework.http.HttpHeaders.HOST; + +import com.adobe.testing.s3mock.dto.BucketName; +import com.google.common.net.InetAddresses; +import java.io.IOException; +import java.util.regex.Pattern; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.filter.OncePerRequestFilter; + +class BucketNameFilter extends OncePerRequestFilter { + private static final Logger LOG = LoggerFactory.getLogger(BucketNameFilter.class); + private static final Pattern BUCKET_AND_KEY_PATTERN = Pattern.compile("/[a-z0-9.-]+/.*"); + private static final Pattern BUCKET_PATTERN = Pattern.compile("/[a-z0-9.-]+/?"); + static final String BUCKET_ATTRIBUTE = "bucketName"; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + BucketName bucketName = null; + try { + bucketName = fromHost(request); + if (bucketName == null) { + bucketName = fromURI(request); + } + if (bucketName != null) { + request.setAttribute(BUCKET_ATTRIBUTE, bucketName); + } + } finally { + LOG.info("Found bucketName {}", bucketName); + filterChain.doFilter(request, response); + } + } + + private BucketName fromURI(HttpServletRequest request) { + String requestURI = request.getRequestURI(); + LOG.info("Check for bucket name in request URI={}.", requestURI); + if (BUCKET_AND_KEY_PATTERN.matcher(requestURI).matches() + || BUCKET_PATTERN.matcher(requestURI).matches()) { + String bucketName = fromURIString(requestURI); + return new BucketName(bucketName); + } + + return null; + } + + private String fromURIString(String uri) { + String bucketName = null; + String[] uriComponents = uri.split("/"); + if (uriComponents.length > 1) { + bucketName = uriComponents[1]; + } + + return bucketName; + } + + private BucketName fromHost(HttpServletRequest request) { + String host = request.getHeader(HOST); + LOG.info("Check for bucket name in host={}.", host); + if (host == null || InetAddresses.isUriInetAddress(host)) { + return null; + } + + String bucketName = getBucketName(host); + if (bucketName != null) { + return new BucketName(bucketName); + } + return null; + } + + private String getBucketName(String hostName) { + if (hostName.contains(".")) { + String[] hostNameComponents = hostName.split("\\."); + return hostNameComponents[0]; + } + return null; + } +} diff --git a/server/src/main/java/com/adobe/testing/s3mock/S3MockConfiguration.java b/server/src/main/java/com/adobe/testing/s3mock/S3MockConfiguration.java index 5f4a37f0d..986674756 100644 --- a/server/src/main/java/com/adobe/testing/s3mock/S3MockConfiguration.java +++ b/server/src/main/java/com/adobe/testing/s3mock/S3MockConfiguration.java @@ -85,6 +85,11 @@ Filter kmsFilter(final KmsKeyStore kmsKeyStore, return new KmsValidationFilter(kmsKeyStore, messageConverter); } + @Bean + Filter bucketNameFilter() { + return new BucketNameFilter(); + } + @Override public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) { configurer diff --git a/server/src/main/java/com/adobe/testing/s3mock/dto/BucketName.java b/server/src/main/java/com/adobe/testing/s3mock/dto/BucketName.java new file mode 100644 index 000000000..c6fda7325 --- /dev/null +++ b/server/src/main/java/com/adobe/testing/s3mock/dto/BucketName.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017-2022 Adobe. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 com.adobe.testing.s3mock.dto; + +import java.util.Objects; + +public class BucketName { + private final String name; + + public BucketName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BucketName that = (BucketName) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String toString() { + return "BucketName{" + + "name='" + name + '\'' + + '}'; + } +} diff --git a/server/src/test/java/com/adobe/testing/s3mock/BucketNameFilterTest.java b/server/src/test/java/com/adobe/testing/s3mock/BucketNameFilterTest.java new file mode 100644 index 000000000..23ffa4fcd --- /dev/null +++ b/server/src/test/java/com/adobe/testing/s3mock/BucketNameFilterTest.java @@ -0,0 +1,130 @@ +/* + * Copyright 2017-2022 Adobe. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 com.adobe.testing.s3mock; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.springframework.http.HttpHeaders.HOST; + +import com.adobe.testing.s3mock.dto.BucketName; +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +class BucketNameFilterTest { + private final MockHttpServletResponse response = new MockHttpServletResponse(); + private final FilterChain filterChain = (request, response) -> { + }; + private MockHttpServletRequest request; + + @Test + void testGetBucketNameFromPath_awsV1() throws ServletException, IOException { + request = new MockHttpServletRequest("PUT", "/bucket-name/"); + BucketNameFilter iut = new BucketNameFilter(); + + iut.doFilterInternal(request, response, filterChain); + + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isNotNull(); + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isEqualTo( + new BucketName("bucket-name")); + } + + @Test + void testGetBucketNameFromPath_awsV2() throws ServletException, IOException { + request = new MockHttpServletRequest("GET", "/bucket-name"); + BucketNameFilter iut = new BucketNameFilter(); + + iut.doFilterInternal(request, response, filterChain); + + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isNotNull(); + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isEqualTo( + new BucketName("bucket-name")); + } + + @Test + void testGetBucketNameFromPath_withKey() throws ServletException, IOException { + request = new MockHttpServletRequest("GET", "/bucket-name/key-name"); + BucketNameFilter iut = new BucketNameFilter(); + + iut.doFilterInternal(request, response, filterChain); + + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isNotNull(); + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isEqualTo( + new BucketName("bucket-name")); + } + + @Test + void testGetBucketNameFromHost_OK() throws ServletException, IOException { + request = new MockHttpServletRequest("GET", "/"); + request.addHeader(HOST, "bucket-name.localhost"); + BucketNameFilter iut = new BucketNameFilter(); + + iut.doFilterInternal(request, response, filterChain); + + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isNotNull(); + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isEqualTo( + new BucketName("bucket-name")); + } + + @Test + void testGetBucketNameFromHost_noBucket() throws ServletException, IOException { + request = new MockHttpServletRequest("GET", "/"); + request.addHeader(HOST, "some-host-name"); + BucketNameFilter iut = new BucketNameFilter(); + + iut.doFilterInternal(request, response, filterChain); + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isNull(); + } + + @Test + void testGetBucketNameFromHost_withBucketInPath() throws ServletException, IOException { + request = new MockHttpServletRequest("GET", "/bucket-name/key-name"); + request.addHeader(HOST, "some-host-name"); + BucketNameFilter iut = new BucketNameFilter(); + + iut.doFilterInternal(request, response, filterChain); + + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isNotNull(); + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isEqualTo( + new BucketName("bucket-name")); + } + + @Test + void testGetBucketNameFromIP_noBucket() throws ServletException, IOException { + request = new MockHttpServletRequest("GET", "/"); + request.addHeader(HOST, "127.0.0.1"); + BucketNameFilter iut = new BucketNameFilter(); + + iut.doFilterInternal(request, response, filterChain); + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isNull(); + } + + @Test + void testGetBucketNameFromIP_withBucketInPath() throws ServletException, IOException { + request = new MockHttpServletRequest("GET", "/bucket-name/key-name"); + request.addHeader(HOST, "127.0.0.1"); + BucketNameFilter iut = new BucketNameFilter(); + + iut.doFilterInternal(request, response, filterChain); + + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isNotNull(); + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isEqualTo( + new BucketName("bucket-name")); + } +} diff --git a/server/src/test/java/com/adobe/testing/s3mock/FaviconControllerTest.java b/server/src/test/java/com/adobe/testing/s3mock/FaviconControllerTest.java index 914e066e6..4ee098a1d 100644 --- a/server/src/test/java/com/adobe/testing/s3mock/FaviconControllerTest.java +++ b/server/src/test/java/com/adobe/testing/s3mock/FaviconControllerTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import com.adobe.testing.s3mock.service.BucketService; import com.adobe.testing.s3mock.store.BucketStore; import com.adobe.testing.s3mock.store.KmsKeyStore; import com.adobe.testing.s3mock.store.ObjectStore; @@ -37,6 +38,7 @@ ObjectController.class, BucketStore.class, BucketController.class, + BucketService.class, MultipartController.class }) @SpringBootTest(classes = {S3MockConfiguration.class}) From 5984ed0d5d09e589c6036ba0658286349c279d52 Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Mon, 21 Nov 2022 21:10:35 +0100 Subject: [PATCH 2/3] WIP - Consume BucketName request attribute Trying out if this works (BucketNameFilter will take name from path), assert that bucket name is the same that is passed in the path to the handler method. Fixes #144 --- .../testing/s3mock/BucketController.java | 44 ++++++++++++++----- .../testing/s3mock/BucketNameFilter.java | 4 +- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/server/src/main/java/com/adobe/testing/s3mock/BucketController.java b/server/src/main/java/com/adobe/testing/s3mock/BucketController.java index c190ac27f..1aca73c6e 100644 --- a/server/src/main/java/com/adobe/testing/s3mock/BucketController.java +++ b/server/src/main/java/com/adobe/testing/s3mock/BucketController.java @@ -16,6 +16,7 @@ package com.adobe.testing.s3mock; +import static com.adobe.testing.s3mock.BucketNameFilter.BUCKET_ATTRIBUTE; import static com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_BUCKET_OBJECT_LOCK_ENABLED; import static com.adobe.testing.s3mock.util.AwsHttpParameters.CONTINUATION_TOKEN; import static com.adobe.testing.s3mock.util.AwsHttpParameters.ENCODING_TYPE; @@ -33,6 +34,7 @@ import static org.springframework.http.MediaType.APPLICATION_XML_VALUE; import com.adobe.testing.s3mock.dto.BucketLifecycleConfiguration; +import com.adobe.testing.s3mock.dto.BucketName; import com.adobe.testing.s3mock.dto.ListAllMyBucketsResult; import com.adobe.testing.s3mock.dto.ListBucketResult; import com.adobe.testing.s3mock.dto.ListBucketResultV2; @@ -42,6 +44,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; @@ -110,7 +113,10 @@ public ResponseEntity listBuckets() { ) public ResponseEntity createBucket(@PathVariable final String bucketName, @RequestHeader(value = X_AMZ_BUCKET_OBJECT_LOCK_ENABLED, - required = false, defaultValue = "false") boolean objectLockEnabled) { + required = false, defaultValue = "false") boolean objectLockEnabled, + @RequestAttribute(BUCKET_ATTRIBUTE) BucketName bucket) { + //TODO: does subdomain access work for #createBucket in S3? + assert bucketName.equals(bucket.getName()); bucketService.verifyBucketNameIsAllowed(bucketName); bucketService.verifyBucketDoesNotExist(bucketName); bucketService.createBucket(bucketName, objectLockEnabled); @@ -129,7 +135,9 @@ public ResponseEntity createBucket(@PathVariable final String bucketName, value = "/{bucketName:.+}", method = RequestMethod.HEAD ) - public ResponseEntity headBucket(@PathVariable final String bucketName) { + public ResponseEntity headBucket(@PathVariable final String bucketName, + @RequestAttribute(BUCKET_ATTRIBUTE) BucketName bucket) { + assert bucketName.equals(bucket.getName()); bucketService.verifyBucketExists(bucketName); return ResponseEntity.ok().build(); } @@ -149,7 +157,9 @@ public ResponseEntity headBucket(@PathVariable final String bucketName) { }, method = RequestMethod.DELETE ) - public ResponseEntity deleteBucket(@PathVariable String bucketName) { + public ResponseEntity deleteBucket(@PathVariable String bucketName, + @RequestAttribute(BUCKET_ATTRIBUTE) BucketName bucket) { + assert bucketName.equals(bucket.getName()); bucketService.verifyBucketExists(bucketName); bucketService.verifyBucketIsEmpty(bucketName); bucketService.deleteBucket(bucketName); @@ -176,7 +186,9 @@ public ResponseEntity deleteBucket(@PathVariable String bucketName) { } ) public ResponseEntity getObjectLockConfiguration( - @PathVariable String bucketName) { + @PathVariable String bucketName, + @RequestAttribute(BUCKET_ATTRIBUTE) BucketName bucket) { + assert bucketName.equals(bucket.getName()); bucketService.verifyBucketExists(bucketName); ObjectLockConfiguration configuration = bucketService.getObjectLockConfiguration(bucketName); return ResponseEntity.ok(configuration); @@ -200,7 +212,9 @@ public ResponseEntity getObjectLockConfiguration( ) public ResponseEntity putObjectLockConfiguration( @PathVariable String bucketName, - @RequestBody ObjectLockConfiguration configuration) { + @RequestBody ObjectLockConfiguration configuration, + @RequestAttribute(BUCKET_ATTRIBUTE) BucketName bucket) { + assert bucketName.equals(bucket.getName()); bucketService.verifyBucketExists(bucketName); bucketService.setObjectLockConfiguration(bucketName, configuration); return ResponseEntity.ok().build(); @@ -226,7 +240,9 @@ public ResponseEntity putObjectLockConfiguration( } ) public ResponseEntity getBucketLifecycleConfiguration( - @PathVariable String bucketName) { + @PathVariable String bucketName, + @RequestAttribute(BUCKET_ATTRIBUTE) BucketName bucket) { + assert bucketName.equals(bucket.getName()); bucketService.verifyBucketExists(bucketName); BucketLifecycleConfiguration configuration = bucketService.getBucketLifecycleConfiguration(bucketName); @@ -251,7 +267,9 @@ public ResponseEntity getBucketLifecycleConfigurat ) public ResponseEntity putBucketLifecycleConfiguration( @PathVariable String bucketName, - @RequestBody BucketLifecycleConfiguration configuration) { + @RequestBody BucketLifecycleConfiguration configuration, + @RequestAttribute(BUCKET_ATTRIBUTE) BucketName bucket) { + assert bucketName.equals(bucket.getName()); bucketService.verifyBucketExists(bucketName); bucketService.setBucketLifecycleConfiguration(bucketName, configuration); return ResponseEntity.ok().build(); @@ -273,7 +291,9 @@ public ResponseEntity putBucketLifecycleConfiguration( method = RequestMethod.DELETE ) public ResponseEntity deleteBucketLifecycleConfiguration( - @PathVariable String bucketName) { + @PathVariable String bucketName, + @RequestAttribute(BUCKET_ATTRIBUTE) BucketName bucket) { + assert bucketName.equals(bucket.getName()); bucketService.verifyBucketExists(bucketName); bucketService.deleteBucketLifecycleConfiguration(bucketName); return ResponseEntity.noContent().build(); @@ -332,7 +352,9 @@ public ResponseEntity listObjects( @RequestParam(required = false) String delimiter, @RequestParam(required = false) String marker, @RequestParam(name = ENCODING_TYPE, required = false) String encodingType, - @RequestParam(name = MAX_KEYS, defaultValue = "1000", required = false) Integer maxKeys) { + @RequestParam(name = MAX_KEYS, defaultValue = "1000", required = false) Integer maxKeys, + @RequestAttribute(BUCKET_ATTRIBUTE) BucketName bucket) { + assert bucketName.equals(bucket.getName()); bucketService.verifyBucketExists(bucketName); bucketService.verifyMaxKeys(maxKeys); bucketService.verifyEncodingType(encodingType); @@ -370,7 +392,9 @@ public ResponseEntity listObjectsV2( @RequestParam(name = ENCODING_TYPE, required = false) String encodingType, @RequestParam(name = START_AFTER, required = false) String startAfter, @RequestParam(name = MAX_KEYS, defaultValue = "1000", required = false) Integer maxKeys, - @RequestParam(name = CONTINUATION_TOKEN, required = false) String continuationToken) { + @RequestParam(name = CONTINUATION_TOKEN, required = false) String continuationToken, + @RequestAttribute(BUCKET_ATTRIBUTE) BucketName bucket) { + assert bucketName.equals(bucket.getName()); bucketService.verifyBucketExists(bucketName); bucketService.verifyMaxKeys(maxKeys); bucketService.verifyEncodingType(encodingType); diff --git a/server/src/main/java/com/adobe/testing/s3mock/BucketNameFilter.java b/server/src/main/java/com/adobe/testing/s3mock/BucketNameFilter.java index 36acd559f..99e262d60 100644 --- a/server/src/main/java/com/adobe/testing/s3mock/BucketNameFilter.java +++ b/server/src/main/java/com/adobe/testing/s3mock/BucketNameFilter.java @@ -32,8 +32,8 @@ class BucketNameFilter extends OncePerRequestFilter { private static final Logger LOG = LoggerFactory.getLogger(BucketNameFilter.class); - private static final Pattern BUCKET_AND_KEY_PATTERN = Pattern.compile("/[a-z0-9.-]+/.*"); - private static final Pattern BUCKET_PATTERN = Pattern.compile("/[a-z0-9.-]+/?"); + private static final Pattern BUCKET_AND_KEY_PATTERN = Pattern.compile("/.+/.*"); + private static final Pattern BUCKET_PATTERN = Pattern.compile("/.+/?"); static final String BUCKET_ATTRIBUTE = "bucketName"; @Override From dcefa5d8ea6da79f2e5b5713cc1e1bcc397baf61 Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Thu, 22 Dec 2022 01:37:28 +0100 Subject: [PATCH 3/3] Respect contextPath in BucketNameFilter --- .../testing/s3mock/BucketNameFilter.java | 13 ++++++++- .../testing/s3mock/S3MockConfiguration.java | 4 +-- .../testing/s3mock/BucketNameFilterTest.java | 28 +++++++++++++------ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/com/adobe/testing/s3mock/BucketNameFilter.java b/server/src/main/java/com/adobe/testing/s3mock/BucketNameFilter.java index 99e262d60..0b35714f0 100644 --- a/server/src/main/java/com/adobe/testing/s3mock/BucketNameFilter.java +++ b/server/src/main/java/com/adobe/testing/s3mock/BucketNameFilter.java @@ -36,6 +36,12 @@ class BucketNameFilter extends OncePerRequestFilter { private static final Pattern BUCKET_PATTERN = Pattern.compile("/.+/?"); static final String BUCKET_ATTRIBUTE = "bucketName"; + private final String contextPath; + + BucketNameFilter(String contextPath) { + this.contextPath = contextPath; + } + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { @@ -70,7 +76,12 @@ private String fromURIString(String uri) { String bucketName = null; String[] uriComponents = uri.split("/"); if (uriComponents.length > 1) { - bucketName = uriComponents[1]; + String firstElement = uriComponents[1]; + if (firstElement.equals(contextPath) && uriComponents.length > 2) { + bucketName = uriComponents[2]; + } else { + bucketName = firstElement; + } } return bucketName; diff --git a/server/src/main/java/com/adobe/testing/s3mock/S3MockConfiguration.java b/server/src/main/java/com/adobe/testing/s3mock/S3MockConfiguration.java index 986674756..b8949d380 100644 --- a/server/src/main/java/com/adobe/testing/s3mock/S3MockConfiguration.java +++ b/server/src/main/java/com/adobe/testing/s3mock/S3MockConfiguration.java @@ -86,8 +86,8 @@ Filter kmsFilter(final KmsKeyStore kmsKeyStore, } @Bean - Filter bucketNameFilter() { - return new BucketNameFilter(); + Filter bucketNameFilter(S3MockProperties properties) { + return new BucketNameFilter(properties.getContextPath()); } @Override diff --git a/server/src/test/java/com/adobe/testing/s3mock/BucketNameFilterTest.java b/server/src/test/java/com/adobe/testing/s3mock/BucketNameFilterTest.java index 23ffa4fcd..06aacaa2e 100644 --- a/server/src/test/java/com/adobe/testing/s3mock/BucketNameFilterTest.java +++ b/server/src/test/java/com/adobe/testing/s3mock/BucketNameFilterTest.java @@ -36,7 +36,7 @@ class BucketNameFilterTest { @Test void testGetBucketNameFromPath_awsV1() throws ServletException, IOException { request = new MockHttpServletRequest("PUT", "/bucket-name/"); - BucketNameFilter iut = new BucketNameFilter(); + BucketNameFilter iut = new BucketNameFilter(null); iut.doFilterInternal(request, response, filterChain); @@ -48,7 +48,7 @@ void testGetBucketNameFromPath_awsV1() throws ServletException, IOException { @Test void testGetBucketNameFromPath_awsV2() throws ServletException, IOException { request = new MockHttpServletRequest("GET", "/bucket-name"); - BucketNameFilter iut = new BucketNameFilter(); + BucketNameFilter iut = new BucketNameFilter(null); iut.doFilterInternal(request, response, filterChain); @@ -60,7 +60,19 @@ void testGetBucketNameFromPath_awsV2() throws ServletException, IOException { @Test void testGetBucketNameFromPath_withKey() throws ServletException, IOException { request = new MockHttpServletRequest("GET", "/bucket-name/key-name"); - BucketNameFilter iut = new BucketNameFilter(); + BucketNameFilter iut = new BucketNameFilter(null); + + iut.doFilterInternal(request, response, filterChain); + + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isNotNull(); + assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isEqualTo( + new BucketName("bucket-name")); + } + + @Test + void testGetBucketNameFromPath_withContextPath() throws ServletException, IOException { + request = new MockHttpServletRequest("GET", "/context/bucket-name/key-name"); + BucketNameFilter iut = new BucketNameFilter("context"); iut.doFilterInternal(request, response, filterChain); @@ -73,7 +85,7 @@ void testGetBucketNameFromPath_withKey() throws ServletException, IOException { void testGetBucketNameFromHost_OK() throws ServletException, IOException { request = new MockHttpServletRequest("GET", "/"); request.addHeader(HOST, "bucket-name.localhost"); - BucketNameFilter iut = new BucketNameFilter(); + BucketNameFilter iut = new BucketNameFilter(null); iut.doFilterInternal(request, response, filterChain); @@ -86,7 +98,7 @@ void testGetBucketNameFromHost_OK() throws ServletException, IOException { void testGetBucketNameFromHost_noBucket() throws ServletException, IOException { request = new MockHttpServletRequest("GET", "/"); request.addHeader(HOST, "some-host-name"); - BucketNameFilter iut = new BucketNameFilter(); + BucketNameFilter iut = new BucketNameFilter(null); iut.doFilterInternal(request, response, filterChain); assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isNull(); @@ -96,7 +108,7 @@ void testGetBucketNameFromHost_noBucket() throws ServletException, IOException { void testGetBucketNameFromHost_withBucketInPath() throws ServletException, IOException { request = new MockHttpServletRequest("GET", "/bucket-name/key-name"); request.addHeader(HOST, "some-host-name"); - BucketNameFilter iut = new BucketNameFilter(); + BucketNameFilter iut = new BucketNameFilter(null); iut.doFilterInternal(request, response, filterChain); @@ -109,7 +121,7 @@ void testGetBucketNameFromHost_withBucketInPath() throws ServletException, IOExc void testGetBucketNameFromIP_noBucket() throws ServletException, IOException { request = new MockHttpServletRequest("GET", "/"); request.addHeader(HOST, "127.0.0.1"); - BucketNameFilter iut = new BucketNameFilter(); + BucketNameFilter iut = new BucketNameFilter(null); iut.doFilterInternal(request, response, filterChain); assertThat(request.getAttribute(BucketNameFilter.BUCKET_ATTRIBUTE)).isNull(); @@ -119,7 +131,7 @@ void testGetBucketNameFromIP_noBucket() throws ServletException, IOException { void testGetBucketNameFromIP_withBucketInPath() throws ServletException, IOException { request = new MockHttpServletRequest("GET", "/bucket-name/key-name"); request.addHeader(HOST, "127.0.0.1"); - BucketNameFilter iut = new BucketNameFilter(); + BucketNameFilter iut = new BucketNameFilter(null); iut.doFilterInternal(request, response, filterChain);