diff --git a/server/pom.xml b/server/pom.xml index b80583997..564a0d81e 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -119,6 +119,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 eb6886b2d..c40b9678a 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})