Skip to content

Commit

Permalink
Reorder filters and let RealmIdResolver throw NotAuthorized
Browse files Browse the repository at this point in the history
  • Loading branch information
adutra committed Jan 22, 2025
1 parent 3491e5f commit e542f5c
Show file tree
Hide file tree
Showing 17 changed files with 256 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -48,6 +49,19 @@ public Invocation.Builder request(String path, Map<String, String> templateValue

public Invocation.Builder request(
String path, Map<String, String> templateValues, Map<String, String> queryParams) {
Map<String, String> 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<String, String> templateValues,
Map<String, String> queryParams,
Map<String, String> headers) {
WebTarget target = client.target(uri).path(path);
for (Map.Entry<String, String> entry : templateValues.entrySet()) {
target = target.resolveTemplate(entry.getKey(), entry.getValue());
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand All @@ -97,6 +100,8 @@
* </ul>
* The server must also be configured to reject request body sizes larger than 1MB (1000000
* bytes).
* <p>The server must also be configured with the following realms: POLARIS (default), and
* OTHER.
*/
@ExtendWith(PolarisIntegrationTestExtension.class)
public class PolarisApplicationIntegrationTest {
Expand Down Expand Up @@ -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());
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,40 @@
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";

@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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public static class Profile implements QuarkusTestProfile {
public Map<String, String> 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");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
Loading

0 comments on commit e542f5c

Please sign in to comment.