diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisApiEndpoints.java b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisApiEndpoints.java index ff4b17485a..05f3c9fa88 100644 --- a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisApiEndpoints.java +++ b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisApiEndpoints.java @@ -28,7 +28,11 @@ */ public final class PolarisApiEndpoints implements Serializable { - public static String REALM_HEADER = "realm"; + /** + * The header name for the realm ID. Tests must make sure that Polaris is configured with this + * header name. + */ + public static String REALM_HEADER = "Polaris-Realm"; private final URI baseUri; private final String realm; diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/env/RestApi.java b/integration-tests/src/main/java/org/apache/polaris/service/it/env/RestApi.java index e862fc2c82..de6cd1adae 100644 --- a/integration-tests/src/main/java/org/apache/polaris/service/it/env/RestApi.java +++ b/integration-tests/src/main/java/org/apache/polaris/service/it/env/RestApi.java @@ -22,6 +22,7 @@ import jakarta.ws.rs.client.Invocation; import jakarta.ws.rs.client.WebTarget; import java.net.URI; +import java.util.HashMap; import java.util.Map; /** Base class for API helper classes. */ @@ -48,6 +49,19 @@ public Invocation.Builder request(String path, Map templateValue public Invocation.Builder request( String path, Map templateValues, Map queryParams) { + Map headers = new HashMap<>(); + headers.put(PolarisApiEndpoints.REALM_HEADER, endpoints.realm()); + if (authToken != null) { + headers.put("Authorization", "Bearer " + authToken); + } + return request(path, templateValues, queryParams, headers); + } + + public Invocation.Builder request( + String path, + Map templateValues, + Map queryParams, + Map headers) { WebTarget target = client.target(uri).path(path); for (Map.Entry entry : templateValues.entrySet()) { target = target.resolveTemplate(entry.getKey(), entry.getValue()); @@ -56,10 +70,7 @@ public Invocation.Builder request( target = target.queryParam(entry.getKey(), entry.getValue()); } Invocation.Builder request = target.request("application/json"); - request = request.header(PolarisApiEndpoints.REALM_HEADER, endpoints.realm()); - if (authToken != null) { - request = request.header("Authorization", "Bearer " + authToken); - } + headers.forEach(request::header); return request; } } diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java index 894926fa0a..681be017a1 100644 --- a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java +++ b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java @@ -28,6 +28,7 @@ import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Invocation; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -85,6 +86,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; /** * @implSpec This test expects the server to be configured with the following features configured: @@ -97,6 +100,8 @@ * * The server must also be configured to reject request body sizes larger than 1MB (1000000 * bytes). + *

The server must also be configured with the following realms: POLARIS (default), and + * OTHER. */ @ExtendWith(PolarisIntegrationTestExtension.class) public class PolarisApplicationIntegrationTest { @@ -657,4 +662,47 @@ public void testRequestBodyTooLarge() throws Exception { }); } } + + @Test + public void testDefaultRealm() { + try (Response response = + managementApi + .request( + "v1/principal-roles", + Map.of(), + Map.of(), + Map.of("Authorization", "Bearer " + authToken)) + .get()) { + assertThat(response.getStatus()).isEqualTo(Status.OK.getStatusCode()); + } + } + + @ParameterizedTest + @ValueSource(strings = {"POLARIS", "OTHER"}) + public void testNonDefaultRealm(String realmId) { + try (Response response = + managementApi + .request( + "v1/principal-roles", + Map.of(), + Map.of(), + Map.of("Authorization", "Bearer " + authToken, REALM_HEADER, realmId)) + .get()) { + assertThat(response.getStatus()).isEqualTo(Status.OK.getStatusCode()); + } + } + + @Test + public void testInvalidRealm() { + try (Response response = + managementApi + .request( + "v1/principal-roles", + Map.of(), + Map.of(), + Map.of("Authorization", "Bearer " + authToken, REALM_HEADER, "unknown-realm")) + .get()) { + assertThat(response.getStatus()).isEqualTo(Status.UNAUTHORIZED.getStatusCode()); + } + } } diff --git a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusFilterPriorities.java b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusFilterPriorities.java new file mode 100644 index 0000000000..63b1c78763 --- /dev/null +++ b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusFilterPriorities.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.polaris.service.quarkus.config; + +import org.apache.polaris.service.config.PolarisFilterPriorities; + +public final class QuarkusFilterPriorities { + public static final int MDC_FILTER = PolarisFilterPriorities.REALM_ID_FILTER + 1; + public static final int TRACING_FILTER = PolarisFilterPriorities.REALM_ID_FILTER + 2; +} diff --git a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusProducers.java b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusProducers.java index 446d737169..298dc137b6 100644 --- a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusProducers.java +++ b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusProducers.java @@ -21,7 +21,6 @@ import io.quarkus.runtime.StartupEvent; import io.smallrye.common.annotation.Identifier; import io.smallrye.context.SmallRyeManagedExecutor; -import io.vertx.core.http.HttpServerRequest; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.RequestScoped; import jakarta.enterprise.event.Observes; @@ -30,9 +29,9 @@ import jakarta.enterprise.inject.Instance; import jakarta.enterprise.inject.Produces; import jakarta.inject.Singleton; +import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.core.Context; import java.time.Clock; -import java.util.HashMap; import org.apache.polaris.core.PolarisConfigurationStore; import org.apache.polaris.core.PolarisDefaultDiagServiceImpl; import org.apache.polaris.core.PolarisDiagnostics; @@ -53,6 +52,7 @@ import org.apache.polaris.service.catalog.io.FileIOFactory; import org.apache.polaris.service.config.RealmEntityManagerFactory; import org.apache.polaris.service.context.RealmContextConfiguration; +import org.apache.polaris.service.context.RealmIdFilter; import org.apache.polaris.service.context.RealmIdResolver; import org.apache.polaris.service.persistence.InMemoryPolarisMetaStoreManagerFactory; import org.apache.polaris.service.quarkus.auth.QuarkusAuthenticationConfiguration; @@ -100,13 +100,8 @@ public PolarisDiagnostics polarisDiagnostics() { @Produces @RequestScoped - public RealmId realmId(@Context HttpServerRequest request, RealmIdResolver realmIdResolver) { - return realmIdResolver.resolveRealmContext( - request.absoluteURI(), - request.method().name(), - request.path(), - request.headers().entries().stream() - .collect(HashMap::new, (m, e) -> m.put(e.getKey(), e.getValue()), HashMap::putAll)); + public RealmId realmId(@Context ContainerRequestContext request) { + return (RealmId) request.getProperty(RealmIdFilter.REALM_ID_KEY); } @Produces diff --git a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/logging/QuarkusLoggingMDCFilter.java b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/logging/QuarkusLoggingMDCFilter.java index 79664c7053..f099de32ba 100644 --- a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/logging/QuarkusLoggingMDCFilter.java +++ b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/logging/QuarkusLoggingMDCFilter.java @@ -18,55 +18,42 @@ */ package org.apache.polaris.service.quarkus.logging; -import io.quarkus.vertx.web.RouteFilter; -import io.vertx.ext.web.RoutingContext; +import static org.apache.polaris.service.context.RealmIdFilter.REALM_ID_KEY; + +import jakarta.annotation.Priority; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.PreMatching; +import jakarta.ws.rs.ext.Provider; import org.apache.polaris.core.context.RealmId; +import org.apache.polaris.service.quarkus.config.QuarkusFilterPriorities; import org.slf4j.MDC; +@PreMatching @ApplicationScoped -public class QuarkusLoggingMDCFilter { - - public static final int PRIORITY = RouteFilter.DEFAULT_PRIORITY + 100; +@Priority(QuarkusFilterPriorities.MDC_FILTER) +@Provider +public class QuarkusLoggingMDCFilter implements ContainerRequestFilter { - private static final String REQUEST_ID_KEY = "requestId"; - private static final String REALM_ID_KEY = "realmId"; - - @Inject RealmId realmId; + public static final String REQUEST_ID_KEY = "requestId"; @Inject QuarkusLoggingConfiguration loggingConfiguration; - public static String requestId(RoutingContext rc) { - return rc.get(REQUEST_ID_KEY); - } - - public static String realmId(RoutingContext rc) { - return rc.get(REALM_ID_KEY); - } - - @RouteFilter(value = PRIORITY) - public void applyMDCContext(RoutingContext rc) { + @Override + public void filter(ContainerRequestContext rc) { // The request scope is active here, so any MDC values set here will be propagated to // threads handling the request. // Also put the MDC values in the request context for use by other filters and handlers loggingConfiguration.mdc().forEach(MDC::put); - loggingConfiguration.mdc().forEach(rc::put); - var requestId = rc.request().getHeader(loggingConfiguration.requestIdHeaderName()); + loggingConfiguration.mdc().forEach(rc::setProperty); + var requestId = rc.getHeaderString(loggingConfiguration.requestIdHeaderName()); if (requestId != null) { MDC.put(REQUEST_ID_KEY, requestId); - rc.put(REQUEST_ID_KEY, requestId); + rc.setProperty(REQUEST_ID_KEY, requestId); } + RealmId realmId = (RealmId) rc.getProperty(REALM_ID_KEY); MDC.put(REALM_ID_KEY, realmId.id()); - rc.put(REALM_ID_KEY, realmId.id()); - // Do not explicitly remove the MDC values from the request context with an end handler, - // as this could remove MDC context still in use in TaskExecutor threads - // rc.addEndHandler( - // (v) -> { - // MDC.remove(REQUEST_ID_MDC_KEY); - // MDC.remove(REALM_ID_MDC_KEY); - // loggingConfiguration.mdc().keySet().forEach(MDC::remove); - // }); - rc.next(); } } diff --git a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagContributor.java b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagContributor.java index df7b1f1372..a6af72ba06 100644 --- a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagContributor.java +++ b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagContributor.java @@ -38,12 +38,17 @@ public class RealmIdTagContributor implements HttpServerMetricsTagsContributor { public Tags contribute(Context context) { // FIXME request scope does not work here, so we have to resolve the realm context manually HttpServerRequest request = context.request(); - RealmId realmId = resolveRealmContext(request); - return Tags.of(TAG_REALM, realmId.id()); + try { + RealmId realmId = resolveRealmContext(request); + return Tags.of(TAG_REALM, realmId.id()); + } catch (Exception ignored) { + // ignore, the RealmIdFilter will handle the error + return Tags.empty(); + } } private RealmId resolveRealmContext(HttpServerRequest request) { - return realmIdResolver.resolveRealmContext( + return realmIdResolver.resolveRealmId( request.absoluteURI(), request.method().name(), request.path(), diff --git a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/tracing/QuarkusTracingFilter.java b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/tracing/QuarkusTracingFilter.java index 28811ad91c..0806cb4051 100644 --- a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/tracing/QuarkusTracingFilter.java +++ b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/tracing/QuarkusTracingFilter.java @@ -19,14 +19,23 @@ package org.apache.polaris.service.quarkus.tracing; import io.opentelemetry.api.trace.Span; -import io.quarkus.vertx.web.RouteFilter; -import io.vertx.ext.web.RoutingContext; +import jakarta.annotation.Priority; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.PreMatching; +import jakarta.ws.rs.ext.Provider; +import org.apache.polaris.core.context.RealmId; +import org.apache.polaris.service.context.RealmIdFilter; +import org.apache.polaris.service.quarkus.config.QuarkusFilterPriorities; import org.apache.polaris.service.quarkus.logging.QuarkusLoggingMDCFilter; import org.eclipse.microprofile.config.inject.ConfigProperty; +@PreMatching @ApplicationScoped -public class QuarkusTracingFilter { +@Priority(QuarkusFilterPriorities.TRACING_FILTER) +@Provider +public class QuarkusTracingFilter implements ContainerRequestFilter { public static final String REQUEST_ID_ATTRIBUTE = "polaris.request.id"; public static final String REALM_ID_ATTRIBUTE = "polaris.realm"; @@ -34,17 +43,16 @@ public class QuarkusTracingFilter { @ConfigProperty(name = "quarkus.otel.sdk.disabled") boolean sdkDisabled; - @RouteFilter(QuarkusLoggingMDCFilter.PRIORITY - 1) - public void applySpanAttributes(RoutingContext rc) { + @Override + public void filter(ContainerRequestContext rc) { if (!sdkDisabled) { Span span = Span.current(); - String requestId = QuarkusLoggingMDCFilter.requestId(rc); - String realmId = QuarkusLoggingMDCFilter.realmId(rc); + String requestId = (String) rc.getProperty(QuarkusLoggingMDCFilter.REQUEST_ID_KEY); + RealmId realmId = (RealmId) rc.getProperty(RealmIdFilter.REALM_ID_KEY); if (requestId != null) { span.setAttribute(REQUEST_ID_ATTRIBUTE, requestId); } - span.setAttribute(REALM_ID_ATTRIBUTE, realmId); + span.setAttribute(REALM_ID_ATTRIBUTE, realmId.id()); } - rc.next(); } } diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusApplicationIntegrationTest.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusApplicationIntegrationTest.java index 56f67fd959..83d6558b08 100644 --- a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusApplicationIntegrationTest.java +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusApplicationIntegrationTest.java @@ -48,6 +48,7 @@ public static class Profile implements QuarkusTestProfile { public Map getConfigOverrides() { return Map.of( "quarkus.http.limits.max-body-size", "1000000", + "polaris.realm-context.realms", "POLARIS,OTHER", "polaris.features.defaults.\"ALLOW_OVERLAPPING_CATALOG_URLS\"", "true", "polaris.features.defaults.\"SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION\"", "true"); } diff --git a/service/common/src/main/java/org/apache/polaris/service/auth/PolarisPrincipalAuthenticatorFilter.java b/service/common/src/main/java/org/apache/polaris/service/auth/PolarisPrincipalAuthenticatorFilter.java index 502742ee9c..27fc6d312b 100644 --- a/service/common/src/main/java/org/apache/polaris/service/auth/PolarisPrincipalAuthenticatorFilter.java +++ b/service/common/src/main/java/org/apache/polaris/service/auth/PolarisPrincipalAuthenticatorFilter.java @@ -22,7 +22,6 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.NotAuthorizedException; -import jakarta.ws.rs.Priorities; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.PreMatching; @@ -31,9 +30,10 @@ import java.security.Principal; import java.util.Optional; import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal; +import org.apache.polaris.service.config.PolarisFilterPriorities; @PreMatching -@Priority(Priorities.AUTHENTICATION) +@Priority(PolarisFilterPriorities.AUTHENTICATOR_FILTER) @ApplicationScoped @Provider public class PolarisPrincipalAuthenticatorFilter implements ContainerRequestFilter { diff --git a/service/common/src/main/java/org/apache/polaris/service/auth/PolarisPrincipalRolesProviderFilter.java b/service/common/src/main/java/org/apache/polaris/service/auth/PolarisPrincipalRolesProviderFilter.java index e45c6caf00..255f9b222f 100644 --- a/service/common/src/main/java/org/apache/polaris/service/auth/PolarisPrincipalRolesProviderFilter.java +++ b/service/common/src/main/java/org/apache/polaris/service/auth/PolarisPrincipalRolesProviderFilter.java @@ -21,7 +21,6 @@ import jakarta.annotation.Priority; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; -import jakarta.ws.rs.Priorities; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.PreMatching; @@ -30,9 +29,10 @@ import java.security.Principal; import java.util.Set; import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal; +import org.apache.polaris.service.config.PolarisFilterPriorities; @PreMatching -@Priority(Priorities.AUTHENTICATION + 1) +@Priority(PolarisFilterPriorities.ROLES_PROVIDER_FILTER) @RequestScoped @Provider public class PolarisPrincipalRolesProviderFilter implements ContainerRequestFilter { diff --git a/service/common/src/main/java/org/apache/polaris/service/config/PolarisFilterPriorities.java b/service/common/src/main/java/org/apache/polaris/service/config/PolarisFilterPriorities.java new file mode 100644 index 0000000000..222583963d --- /dev/null +++ b/service/common/src/main/java/org/apache/polaris/service/config/PolarisFilterPriorities.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.polaris.service.config; + +import jakarta.ws.rs.Priorities; + +public final class PolarisFilterPriorities { + public static final int REALM_ID_FILTER = Priorities.AUTHENTICATION - 100; + public static final int AUTHENTICATOR_FILTER = Priorities.AUTHENTICATION; + public static final int ROLES_PROVIDER_FILTER = Priorities.AUTHENTICATION + 1; + public static final int RATE_LIMITER_FILTER = Priorities.USER; +} diff --git a/service/common/src/main/java/org/apache/polaris/service/context/DefaultRealmIdResolver.java b/service/common/src/main/java/org/apache/polaris/service/context/DefaultRealmIdResolver.java index 7e6a3b4247..e02d56b179 100644 --- a/service/common/src/main/java/org/apache/polaris/service/context/DefaultRealmIdResolver.java +++ b/service/common/src/main/java/org/apache/polaris/service/context/DefaultRealmIdResolver.java @@ -21,6 +21,7 @@ import io.smallrye.common.annotation.Identifier; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.ws.rs.NotAuthorizedException; import java.util.Map; import org.apache.polaris.core.context.RealmId; @@ -36,7 +37,7 @@ public DefaultRealmIdResolver(RealmContextConfiguration configuration) { } @Override - public RealmId resolveRealmContext( + public RealmId resolveRealmId( String requestURL, String method, String path, Map headers) { String realm; @@ -44,7 +45,7 @@ public RealmId resolveRealmContext( if (headers.containsKey(configuration.headerName())) { realm = headers.get(configuration.headerName()); if (!configuration.realms().contains(realm)) { - throw new IllegalArgumentException("Unknown realm: " + realm); + throw new NotAuthorizedException("Unknown realm: " + realm); } } else { realm = configuration.defaultRealm(); diff --git a/service/common/src/main/java/org/apache/polaris/service/context/RealmIdFilter.java b/service/common/src/main/java/org/apache/polaris/service/context/RealmIdFilter.java new file mode 100644 index 0000000000..d9c5b88130 --- /dev/null +++ b/service/common/src/main/java/org/apache/polaris/service/context/RealmIdFilter.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.polaris.service.context; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.NotAuthorizedException; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.PreMatching; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; +import jakarta.ws.rs.ext.Provider; +import java.util.HashMap; +import java.util.Map; +import org.apache.polaris.core.context.RealmId; +import org.apache.polaris.service.config.PolarisFilterPriorities; + +@PreMatching +@ApplicationScoped +@Priority(PolarisFilterPriorities.REALM_ID_FILTER) +@Provider +public class RealmIdFilter implements ContainerRequestFilter { + + public static final String REALM_ID_KEY = "realmId"; + + @Inject RealmIdResolver realmIdResolver; + + @Override + public void filter(ContainerRequestContext rc) { + RealmId realmId = null; + try { + realmId = resolveRealmContext(rc); + } catch (NotAuthorizedException e) { + rc.abortWith(Response.status(Status.UNAUTHORIZED).build()); + } catch (Exception e) { + rc.abortWith(Response.status(Status.INTERNAL_SERVER_ERROR).build()); + } + rc.setProperty(REALM_ID_KEY, realmId); + } + + protected RealmId resolveRealmContext(ContainerRequestContext rc) { + return realmIdResolver.resolveRealmId( + rc.getUriInfo().getRequestUri().toString(), + rc.getMethod(), + rc.getUriInfo().getPath(), + rc.getHeaders().entrySet().stream() + .collect( + HashMap::new, (m, e) -> m.put(e.getKey(), e.getValue().getFirst()), Map::putAll)); + } +} diff --git a/service/common/src/main/java/org/apache/polaris/service/context/RealmIdResolver.java b/service/common/src/main/java/org/apache/polaris/service/context/RealmIdResolver.java index 7ee2d80501..49c2d64e45 100644 --- a/service/common/src/main/java/org/apache/polaris/service/context/RealmIdResolver.java +++ b/service/common/src/main/java/org/apache/polaris/service/context/RealmIdResolver.java @@ -23,6 +23,12 @@ public interface RealmIdResolver { - RealmId resolveRealmContext( + /** + * Resolves the realm id for the given request. + * + * @return the resolved realm id + * @throws jakarta.ws.rs.NotAuthorizedException if the realm id is not valid + */ + RealmId resolveRealmId( String requestURL, String method, String path, Map headers); } diff --git a/service/common/src/main/java/org/apache/polaris/service/context/TestRealmIdResolver.java b/service/common/src/main/java/org/apache/polaris/service/context/TestRealmIdResolver.java index 6962ae38e8..fd1ba24105 100644 --- a/service/common/src/main/java/org/apache/polaris/service/context/TestRealmIdResolver.java +++ b/service/common/src/main/java/org/apache/polaris/service/context/TestRealmIdResolver.java @@ -49,7 +49,7 @@ public TestRealmIdResolver(RealmContextConfiguration configuration) { } @Override - public RealmId resolveRealmContext( + public RealmId resolveRealmId( String requestURL, String method, String path, Map headers) { // Since this default resolver is strictly for use in test/dev environments, we'll consider // it safe to log all contents. Any "real" resolver used in a prod environment should make diff --git a/service/common/src/main/java/org/apache/polaris/service/ratelimiter/RateLimiterFilter.java b/service/common/src/main/java/org/apache/polaris/service/ratelimiter/RateLimiterFilter.java index 3047b52fab..28bb60589d 100644 --- a/service/common/src/main/java/org/apache/polaris/service/ratelimiter/RateLimiterFilter.java +++ b/service/common/src/main/java/org/apache/polaris/service/ratelimiter/RateLimiterFilter.java @@ -21,20 +21,20 @@ import jakarta.annotation.Priority; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import jakarta.ws.rs.Priorities; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.PreMatching; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; import java.io.IOException; +import org.apache.polaris.service.config.PolarisFilterPriorities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Request filter that returns a 429 Too Many Requests if the rate limiter says so */ @Provider @PreMatching -@Priority(Priorities.USER) +@Priority(PolarisFilterPriorities.RATE_LIMITER_FILTER) @ApplicationScoped public class RateLimiterFilter implements ContainerRequestFilter { private static final Logger LOGGER = LoggerFactory.getLogger(RateLimiterFilter.class);