Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use requested URI info in CORS decision-making #7585

Merged
merged 2 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cors/src/main/java/io/helidon/cors/CorsRequestAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.List;
import java.util.Optional;

import io.helidon.common.uri.UriInfo;
import io.helidon.http.HeaderName;

/**
Expand All @@ -36,11 +37,11 @@ public interface CorsRequestAdapter<T> {
String path();

/**
* Authority of the request (host header, or obtained from forwarded header).
* Returns the {@link io.helidon.common.uri.UriInfo} for the request.
*
* @return authority
* @return URI info for the request
*/
String authority();
UriInfo requestedUri();

/**
* Retrieves the first value for the specified header as a String.
Expand Down
6 changes: 3 additions & 3 deletions cors/src/main/java/io/helidon/cors/CorsSupportHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,10 @@ public Aggregator aggregator() {
private boolean isRequestTypeNormal(CorsRequestAdapter<Q> requestAdapter, boolean silent) {
// If no origin header or same as host, then just normal
Optional<String> originOpt = requestAdapter.firstHeader(HeaderNames.ORIGIN);
Optional<String> hostOpt = requestAdapter.firstHeader(HeaderNames.HOST);
String host = requestAdapter.requestedUri().host();

boolean result = originOpt.isEmpty() || (hostOpt.isPresent() && originOpt.get().contains("://" + hostOpt.get()));
LogHelper.logIsRequestTypeNormal(result, silent, requestAdapter, originOpt, hostOpt);
boolean result = originOpt.isEmpty() || originOpt.get().contains("://" + host);
LogHelper.logIsRequestTypeNormal(result, silent, requestAdapter, originOpt, host);
return result;
}

Expand Down
14 changes: 7 additions & 7 deletions cors/src/main/java/io/helidon/cors/LogHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ void setAndLog(BiConsumer<HeaderName, Object> consumer, String note) {
}

static <T> void logIsRequestTypeNormal(boolean result, boolean silent, CorsRequestAdapter<T> requestAdapter,
Optional<String> originOpt, Optional<String> hostOpt) {
Optional<String> originOpt, String host) {
if (silent || !CorsSupportHelper.LOGGER.isLoggable(DECISION_LEVEL)) {
return;
}
Expand All @@ -84,21 +84,21 @@ static <T> void logIsRequestTypeNormal(boolean result, boolean silent, CorsReque
factorsWhyCrossHost.add(String.format("header %s is present (%s)", HeaderNames.ORIGIN, originOpt.get()));
}

if (hostOpt.isEmpty()) {
if (host.isEmpty()) {
reasonsWhyNormal.add("header " + HeaderNames.HOST + " is absent");
} else {
factorsWhyCrossHost.add(String.format("header %s is present (%s)", HeaderNames.HOST, hostOpt.get()));
factorsWhyCrossHost.add(String.format("header %s is present (%s)", HeaderNames.HOST, host));
}

if (hostOpt.isPresent() && originOpt.isPresent()) {
String partOfOriginMatchingHost = "://" + hostOpt.get();
if (originOpt.isPresent()) {
String partOfOriginMatchingHost = "://" + host;
if (originOpt.get()
.contains(partOfOriginMatchingHost)) {
reasonsWhyNormal.add(String.format("header %s '%s' matches header %s '%s'", HeaderNames.ORIGIN,
originOpt.get(), HeaderNames.HOST, hostOpt.get()));
originOpt.get(), HeaderNames.HOST, host));
} else {
factorsWhyCrossHost.add(String.format("header %s '%s' does not match header %s '%s'", HeaderNames.ORIGIN,
originOpt.get(), HeaderNames.HOST, hostOpt.get()));
originOpt.get(), HeaderNames.HOST, host));
}
}

Expand Down
15 changes: 15 additions & 0 deletions docs/includes/cors.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,21 @@ matches an incoming request's path pattern and HTTP method.
// end::mapped-config-suffix[]
// end::mapped-config[]

// tag::cors-and-requested-uri-intro[]
=== CORS and the Requested URI Feature
The decisions the Helidon CORS feature makes depend on accurate information about each incoming request,
particularly the host to which the request is sent.
Conveyed as headers in the request, this information can be changed or overwritten by intermediate nodes--such as load
balancers--between the origin of the request and your service.

Well-behaved intermediate nodes preserve this important data in other headers, such as `Forwarded`.
// end::cors-and-requested-uri-intro[]

// tag::cors-and-requested-uri-wrapup[]
The CORS support in Helidon uses the requested URI feature to discover the correct information about each request,
according to your configuration, so it can make accurate decisions about whether to permit cross-origin accesses.
// end::cors-and-requested-uri-wrapup[]

// tag::understanding-cors-support-in-services[]
=== Using CORS Support in Built-in Helidon Services

Expand Down
5 changes: 5 additions & 0 deletions docs/mp/cors/cors.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ cors.paths.1.allow-origins=http://foo.com

== Additional Information

include::{rootdir}/includes/cors.adoc[tag=cors-and-requested-uri-intro]
You can configure how the Helidon server handles these headers as described in the documentation for xref:{rootdir}/mp/server.adoc#_using_requested_uri_discovery[requested URI discovery].

include::{rootdir}/includes/cors.adoc[tag=cors-and-requested-uri-wrapup]

include::{rootdir}/includes/cors.adoc[tag=understanding-cors-support-in-services]

include::{rootdir}/includes/cors.adoc[tag=builtin-getting-started]
Expand Down
5 changes: 5 additions & 0 deletions docs/se/cors.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ For a complete example, see {helidon-github-tree-url}/examples/cors[Helidon SE C

== Additional Information

include::{rootdir}/includes/cors.adoc[tag=cors-and-requested-uri-intro]
You can configure how the Helidon server handles these headers as described in the documentation for xref:{rootdir}/se/webserver.adoc#_requested_uri_discovery[requested URI discovery].

include::{rootdir}/includes/cors.adoc[tag=cors-and-requested-uri-wrapup]

include::{rootdir}/includes/cors.adoc[tag=understanding-cors-support-in-services]
include::{rootdir}/includes/cors.adoc[tag=builtin-getting-started]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@
import java.util.Optional;
import java.util.function.Supplier;

import io.helidon.common.LazyValue;
import io.helidon.common.uri.UriInfo;
import io.helidon.cors.CorsRequestAdapter;
import io.helidon.cors.CorsResponseAdapter;
import io.helidon.cors.CorsSupportBase;
import io.helidon.cors.CrossOriginConfig;
import io.helidon.http.HeaderName;
import io.helidon.http.HeaderNames;

import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerResponseContext;
Expand Down Expand Up @@ -104,15 +105,17 @@ protected Builder secondaryLookupSupplier(
static class RequestAdapterMp implements CorsRequestAdapter<ContainerRequestContext> {

private final ContainerRequestContext requestContext;
private final LazyValue<UriInfo> uriInfo;

@SuppressWarnings("unchecked")
RequestAdapterMp(ContainerRequestContext requestContext) {
this.requestContext = requestContext;
uriInfo = LazyValue.create(() -> ((Supplier<UriInfo>) requestContext.getProperty(UriInfo.class.getName())).get());
}

@Override
public String authority() {
// TODO we want authority - we should set it in integration with WebServer as request property
return firstHeader(HeaderNames.HOST).orElse("localhost");
public UriInfo requestedUri() {
return uriInfo.get();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
import java.util.WeakHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import io.helidon.common.context.Context;
import io.helidon.common.context.Contexts;
import io.helidon.common.uri.UriInfo;
import io.helidon.common.uri.UriPath;
import io.helidon.http.Header;
import io.helidon.http.HeaderNames;
Expand Down Expand Up @@ -63,7 +65,6 @@
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ContainerResponse;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.server.spi.Container;
import org.glassfish.jersey.server.spi.ContainerResponseWriter;

Expand Down Expand Up @@ -99,12 +100,6 @@ static JaxRsService create(ResourceConfig resourceConfig, InjectionManager injec
resourceConfig.property(PROVIDER_DEFAULT_DISABLE, "ALL");
resourceConfig.property(WADL_FEATURE_DISABLE, "true");

// TODO - temporary until MP OpenAPI TCK bug fix released. https://github.com/eclipse/microprofile-open-api/issues/557
if (System.getProperties().containsKey("io.helidon." + ServerProperties.RESOURCE_VALIDATION_IGNORE_ERRORS)) {
resourceConfig.property(ServerProperties.RESOURCE_VALIDATION_IGNORE_ERRORS,
Boolean.getBoolean("io.helidon." + ServerProperties.RESOURCE_VALIDATION_IGNORE_ERRORS));
}

InjectionManager ij = injectionManager == null ? null : new InjectionManagerWrapper(injectionManager, resourceConfig);
ApplicationHandler appHandler = new ApplicationHandler(resourceConfig,
new WebServerBinder(),
Expand Down Expand Up @@ -219,6 +214,10 @@ private void doHandle(Context ctx, ServerRequest req, ServerResponse res) {
req.prologue().method().text(),
new HelidonMpSecurityContext(), new MapPropertiesDelegate(),
resourceConfig);
/*
MP CORS supports needs a way to obtain the UriInfo from the request context.
*/
requestContext.setProperty(UriInfo.class.getName(), ((Supplier<UriInfo>) req::requestedUri));

for (Header header : req.headers()) {
requestContext.headers(header.name(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.List;
import java.util.Optional;

import io.helidon.common.uri.UriInfo;
import io.helidon.cors.CorsRequestAdapter;
import io.helidon.http.HeaderName;
import io.helidon.http.ServerRequestHeaders;
Expand All @@ -40,8 +41,8 @@ class CorsServerRequestAdapter implements CorsRequestAdapter<ServerRequest> {
}

@Override
public String authority() {
return request.authority();
public UriInfo requestedUri() {
return request.requestedUri();
}

@Override
Expand All @@ -52,7 +53,7 @@ public String path() {
@Override
public Optional<String> firstHeader(HeaderName key) {
if (headers.contains(key)) {
return Optional.of(headers.get(key).value());
return Optional.of(headers.get(key).get());
}
return Optional.empty();
}
Expand Down
Loading