diff --git a/microprofile/cors/pom.xml b/microprofile/cors/pom.xml index 21d5ec57565..fe2acfec018 100644 --- a/microprofile/cors/pom.xml +++ b/microprofile/cors/pom.xml @@ -85,5 +85,10 @@ helidon-microprofile-tests-junit5 test + + org.mockito + mockito-core + test + diff --git a/microprofile/cors/src/main/java/io/helidon/microprofile/cors/CorsSupportMp.java b/microprofile/cors/src/main/java/io/helidon/microprofile/cors/CorsSupportMp.java index dee9a4ba820..371822f9e7f 100644 --- a/microprofile/cors/src/main/java/io/helidon/microprofile/cors/CorsSupportMp.java +++ b/microprofile/cors/src/main/java/io/helidon/microprofile/cors/CorsSupportMp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package io.helidon.microprofile.cors; import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.function.Supplier; @@ -147,7 +149,17 @@ public void next() { @Override public String toString() { return String.format("RequestAdapterMp{path=%s, method=%s, headers=%s}", path(), method(), - requestContext.getHeaders()); + filteredHeaders()); + } + + private Map> filteredHeaders() { + MultivaluedMap result = new MultivaluedHashMap<>(); + for (Map.Entry> header : requestContext.getHeaders().entrySet()) { + if (HEADERS_FOR_CORS_DIAGNOSTICS.contains(header.getKey().toLowerCase(Locale.getDefault()))) { + result.put(header.getKey(), header.getValue()); + } + } + return result; } } diff --git a/microprofile/cors/src/test/java/io/helidon/microprofile/cors/HeadersTest.java b/microprofile/cors/src/test/java/io/helidon/microprofile/cors/HeadersTest.java new file mode 100644 index 00000000000..a8a74611e18 --- /dev/null +++ b/microprofile/cors/src/test/java/io/helidon/microprofile/cors/HeadersTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * 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 io.helidon.microprofile.cors; + +import java.net.URI; +import java.util.Map; + +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.UriInfo; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class HeadersTest { + @Test + void checkHeaders() { + + UriInfo uriInfo = mock(UriInfo.class); + when(uriInfo.getRequestUri()).thenReturn(URI.create("http://myhost.com/testpath")); + ContainerRequestContext context = mock(ContainerRequestContext.class); + when(context.getHeaders()).thenReturn(new MultivaluedHashMap<>(Map.of("Origin", "http://myhost.com", + "Host", "otherhost", + "Authorization", "some-auth", + "X-Custom", "myValue"))); + when(context.getMethod()).thenReturn("POST"); + when(context.getUriInfo()).thenReturn(uriInfo); + + CorsSupportMp.RequestAdapterMp requestAdapterMp = new CorsSupportMp.RequestAdapterMp(context); + + assertThat("Headers", + requestAdapterMp.toString(), + allOf( + containsString("path=/testpath"), + containsString("method=POST"), + containsString("Origin=[http://myhost.com]"), + containsString("Host=[otherhost]"), + not(containsString("Authorization")), + not(containsString("X-Custom")))); + } +} diff --git a/webserver/cors/pom.xml b/webserver/cors/pom.xml index 34ac3dbc89e..3a362d17de2 100644 --- a/webserver/cors/pom.xml +++ b/webserver/cors/pom.xml @@ -65,6 +65,11 @@ hamcrest-all test + + org.mockito + mockito-core + test + io.helidon.config helidon-config-yaml diff --git a/webserver/cors/src/main/java/io/helidon/webserver/cors/CorsSupportBase.java b/webserver/cors/src/main/java/io/helidon/webserver/cors/CorsSupportBase.java index 961b77b0932..1620483877e 100644 --- a/webserver/cors/src/main/java/io/helidon/webserver/cors/CorsSupportBase.java +++ b/webserver/cors/src/main/java/io/helidon/webserver/cors/CorsSupportBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -271,6 +272,13 @@ private void reportUseOfMissingConfig(Config config) { */ protected interface RequestAdapter { + /** + * Header names useful for CORS diagnostic logging messages. + */ + Set HEADERS_FOR_CORS_DIAGNOSTICS = Set.of("origin", + "host", + "access-control-request-method"); + /** * * @return possibly unnormalized path from the request diff --git a/webserver/cors/src/main/java/io/helidon/webserver/cors/RequestAdapterSe.java b/webserver/cors/src/main/java/io/helidon/webserver/cors/RequestAdapterSe.java index 0732accf5e4..883a780970a 100644 --- a/webserver/cors/src/main/java/io/helidon/webserver/cors/RequestAdapterSe.java +++ b/webserver/cors/src/main/java/io/helidon/webserver/cors/RequestAdapterSe.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,10 @@ */ package io.helidon.webserver.cors; +import java.util.HashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.Optional; import io.helidon.webserver.ServerRequest; @@ -68,6 +71,16 @@ public ServerRequest request() { @Override public String toString() { - return String.format("RequestAdapterSe{path=%s, method=%s, headers=%s}", path(), method(), request.headers().toMap()); + return String.format("RequestAdapterSe{path=%s, method=%s, headers=%s}", path(), method(), headersDisplay()); + } + + private Map> headersDisplay() { + Map> result = new HashMap<>(); + for (Map.Entry> header : request.headers().toMap().entrySet()) { + if (HEADERS_FOR_CORS_DIAGNOSTICS.contains(header.getKey().toLowerCase(Locale.getDefault()))) { + result.put(header.getKey(), header.getValue()); + } + } + return result; } } diff --git a/webserver/cors/src/test/java/io/helidon/webserver/cors/HeadersTest.java b/webserver/cors/src/test/java/io/helidon/webserver/cors/HeadersTest.java new file mode 100644 index 00000000000..6939acb9b05 --- /dev/null +++ b/webserver/cors/src/test/java/io/helidon/webserver/cors/HeadersTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * 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 io.helidon.webserver.cors; + +import java.util.List; +import java.util.Map; + +import io.helidon.common.http.Http; +import io.helidon.common.http.HttpRequest; +import io.helidon.webserver.RequestHeaders; +import io.helidon.webserver.ServerRequest; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class HeadersTest { + + @Test + void checkHeaders() { + HttpRequest.Path path = mock(HttpRequest.Path.class); + when(path.toString()) + .thenReturn("/testpath"); + + RequestHeaders requestHeaders = mock(RequestHeaders.class); + when(requestHeaders.toMap()) + .thenReturn(Map.of("Origin", List.of("http://myhost.com"), + "Host", List.of("otherhost"), + "Authorization", List.of("some-auth"), + "X-Custom", List.of("myValue"))); + + Http.RequestMethod requestMethod = mock(Http.RequestMethod.class); + when(requestMethod.name()).thenReturn("POST"); + + ServerRequest serverRequest = mock(ServerRequest.class); + when(serverRequest.path()).thenReturn(path); + when(serverRequest.method()).thenReturn(requestMethod); + when(serverRequest.headers()).thenReturn(requestHeaders); + + RequestAdapterSe requestAdapterSe = new RequestAdapterSe(serverRequest); + assertThat("Headers", + requestAdapterSe.toString(), + allOf( + containsString("path=/testpath"), + containsString("method=POST"), + containsString("Origin=[http://myhost.com]"), + containsString("Host=[otherhost]"), + not(containsString("Authorization")), + not(containsString("X-Custom")))); + } +}