From f28ca5249595de88f06255afd73f1c54c677635e Mon Sep 17 00:00:00 2001 From: gracechen09 Date: Wed, 8 Jul 2026 14:50:08 -0400 Subject: [PATCH 1/7] correlate opa server log with polaris request id --- .../extension/auth/opa/OpaPolarisAuthorizer.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java index 7799bb22a55..e6aa8663db9 100644 --- a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java +++ b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java @@ -68,6 +68,9 @@ import org.apache.polaris.extension.auth.opa.token.BearerTokenProvider; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; /** * OPA-based implementation of {@link PolarisAuthorizer}. @@ -81,6 +84,8 @@ * environments. */ class OpaPolarisAuthorizer implements PolarisAuthorizer { + private static final Logger LOGGER = LoggerFactory.getLogger(OpaPolarisAuthorizer.class); + private final URI policyUri; private final BearerTokenProvider tokenProvider; private final CloseableHttpClient httpClient; @@ -277,6 +282,7 @@ T httpClientExecute( private boolean queryOpaCheckResponse(ClassicHttpResponse response) throws IOException { int statusCode = response.getCode(); if (statusCode != 200) { + LOGGER.warn("OPA returned unexpected HTTP status {}, treating as deny", statusCode); return false; } @@ -334,7 +340,11 @@ private ImmutableActor buildActor(PolarisPrincipal principal) { } private ImmutableContext buildContext() { - return ImmutableContext.builder().requestId(UUID.randomUUID().toString()).build(); + // "requestId" matches RequestIdFilter.REQUEST_ID_KEY set by LoggingMDCFilter + String requestId = MDC.get("requestId"); + return ImmutableContext.builder() + .requestId(requestId != null ? requestId : UUID.randomUUID().toString()) + .build(); } private ImmutableResource buildResource( From 53292bb05efe082662e2cb1d3938165663a0251d Mon Sep 17 00:00:00 2001 From: gracechen09 Date: Wed, 8 Jul 2026 14:55:02 -0400 Subject: [PATCH 2/7] cleanup --- .../apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java index e6aa8663db9..d8ddf1f8082 100644 --- a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java +++ b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java @@ -340,7 +340,6 @@ private ImmutableActor buildActor(PolarisPrincipal principal) { } private ImmutableContext buildContext() { - // "requestId" matches RequestIdFilter.REQUEST_ID_KEY set by LoggingMDCFilter String requestId = MDC.get("requestId"); return ImmutableContext.builder() .requestId(requestId != null ? requestId : UUID.randomUUID().toString()) From 519286b54fcc2084cbecfe53bd6306051f19fce2 Mon Sep 17 00:00:00 2001 From: gracechen09 Date: Wed, 8 Jul 2026 21:27:49 -0400 Subject: [PATCH 3/7] use RequestIdSupplier --- .../auth/opa/OpaPolarisAuthorizer.java | 15 +++++++-- .../auth/opa/OpaPolarisAuthorizerFactory.java | 12 +++++-- .../opa/OpaPolarisAuthorizerFactoryTest.java | 6 ++-- .../auth/opa/OpaPolarisAuthorizerTest.java | 32 +++++++++---------- 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java index d8ddf1f8082..f13d02cacdd 100644 --- a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java +++ b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.annotations.VisibleForTesting; +import jakarta.enterprise.inject.Instance; import java.io.IOException; import java.net.URI; import java.util.ArrayList; @@ -55,6 +56,7 @@ import org.apache.polaris.core.auth.RootPrivilegeGrantAuthorizationIntent; import org.apache.polaris.core.auth.SingleTargetAuthorizationIntent; import org.apache.polaris.core.auth.TargetlessAuthorizationIntent; +import org.apache.polaris.core.context.RequestIdSupplier; import org.apache.polaris.core.entity.PolarisBaseEntity; import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper; import org.apache.polaris.core.persistence.ResolvedPolarisEntity; @@ -70,7 +72,6 @@ import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.MDC; /** * OPA-based implementation of {@link PolarisAuthorizer}. @@ -90,6 +91,7 @@ class OpaPolarisAuthorizer implements PolarisAuthorizer { private final BearerTokenProvider tokenProvider; private final CloseableHttpClient httpClient; private final ObjectMapper objectMapper; + private final Instance requestIdSupplier; /** * Public constructor that accepts a complete policy URI. @@ -101,17 +103,21 @@ class OpaPolarisAuthorizer implements PolarisAuthorizer { * @param objectMapper Jackson ObjectMapper for JSON serialization (required). Shared across * authorizer instances to avoid initialization overhead. * @param tokenProvider Token provider for authentication (optional) + * @param requestIdSupplier Supplier for the server-generated request ID (optional), used to + * correlate OPA queries with the originating HTTP request */ public OpaPolarisAuthorizer( @NonNull URI policyUri, @NonNull CloseableHttpClient httpClient, @NonNull ObjectMapper objectMapper, - @Nullable BearerTokenProvider tokenProvider) { + @Nullable BearerTokenProvider tokenProvider, + @Nullable Instance requestIdSupplier) { this.policyUri = policyUri; this.tokenProvider = tokenProvider; this.httpClient = httpClient; this.objectMapper = objectMapper; + this.requestIdSupplier = requestIdSupplier; } /** @@ -340,7 +346,10 @@ private ImmutableActor buildActor(PolarisPrincipal principal) { } private ImmutableContext buildContext() { - String requestId = MDC.get("requestId"); + String requestId = null; + if (requestIdSupplier != null && requestIdSupplier.isResolvable()) { + requestId = requestIdSupplier.get().getRequestId(); + } return ImmutableContext.builder() .requestId(requestId != null ? requestId : UUID.randomUUID().toString()) .build(); diff --git a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java index 2a0be78b78f..3d448e2331d 100644 --- a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java +++ b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java @@ -24,6 +24,7 @@ import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; import java.io.IOException; import java.net.URI; @@ -34,6 +35,7 @@ import org.apache.polaris.core.auth.PolarisAuthorizer; import org.apache.polaris.core.auth.PolarisAuthorizerFactory; import org.apache.polaris.core.config.RealmConfig; +import org.apache.polaris.core.context.RequestIdSupplier; import org.apache.polaris.extension.auth.opa.token.BearerTokenProvider; import org.apache.polaris.extension.auth.opa.token.FileBearerTokenProvider; import org.apache.polaris.extension.auth.opa.token.StaticBearerTokenProvider; @@ -52,15 +54,20 @@ class OpaPolarisAuthorizerFactory implements PolarisAuthorizerFactory { private final Clock clock; private final ObjectMapper objectMapper; private final AsyncExec asyncExec; + private final Instance requestIdSupplier; private CloseableHttpClient httpClient; private BearerTokenProvider bearerTokenProvider; @Inject public OpaPolarisAuthorizerFactory( - OpaAuthorizationConfig opaConfig, Clock clock, AsyncExec asyncExec) { + OpaAuthorizationConfig opaConfig, + Clock clock, + AsyncExec asyncExec, + Instance requestIdSupplier) { this.opaConfig = opaConfig; this.clock = clock; this.asyncExec = asyncExec; + this.requestIdSupplier = requestIdSupplier; this.objectMapper = JsonMapper.builder().build(); } @@ -96,7 +103,8 @@ public PolarisAuthorizer create(RealmConfig realmConfig) { new IllegalStateException( "OPA policy URI must be configured via polaris.authorization.opa.policy-uri")); - return new OpaPolarisAuthorizer(policyUri, httpClient, objectMapper, bearerTokenProvider); + return new OpaPolarisAuthorizer( + policyUri, httpClient, objectMapper, bearerTokenProvider, requestIdSupplier); } @PreDestroy diff --git a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java index 982d81b980d..7be3be7e20a 100644 --- a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java +++ b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java @@ -63,7 +63,7 @@ public void testFactoryWithStaticTokenConfiguration() { try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { OpaPolarisAuthorizerFactory factory = - new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec); + new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, null); // Create authorizer RealmConfig realmConfig = mock(RealmConfig.class); @@ -107,7 +107,7 @@ public void testFactoryWithFileBasedTokenConfiguration() throws IOException { try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { OpaPolarisAuthorizerFactory factory = - new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec); + new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, null); // Create authorizer RealmConfig realmConfig = mock(RealmConfig.class); @@ -151,7 +151,7 @@ public void testFactoryWithNoTokenConfiguration() { try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { OpaPolarisAuthorizerFactory factory = - new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec); + new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, null); // Create authorizer RealmConfig realmConfig = mock(RealmConfig.class); diff --git a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java index bfae6936714..31ed08c0f1c 100644 --- a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java +++ b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java @@ -87,7 +87,7 @@ void serializesBasicOpaInput() throws Exception { "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"); OpaPolarisAuthorizer authorizer = new OpaPolarisAuthorizer( - policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null); + policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null, null); PolarisPrincipal principal = PolarisPrincipal.of("eve", Map.of("department", "finance"), Set.of("auditor")); @@ -132,7 +132,7 @@ void serializesHierarchicalTarget() throws Exception { "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"); OpaPolarisAuthorizer authorizer = new OpaPolarisAuthorizer( - policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null); + policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null, null); // Set up a realistic principal PolarisPrincipal principal = @@ -263,7 +263,7 @@ void serializesMultiLevelNamespaceTarget() throws Exception { "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"); OpaPolarisAuthorizer authorizer = new OpaPolarisAuthorizer( - policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null); + policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null, null); // Set up a realistic principal PolarisPrincipal principal = @@ -427,7 +427,7 @@ void authorizeOrThrowHandlesEmptyTargetsAndSecondaries() throws Exception { "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"); OpaPolarisAuthorizer authorizer = new OpaPolarisAuthorizer( - policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null); + policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null, null); PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("admin")); @@ -473,7 +473,7 @@ public void testCreateWithHttpsAndBearerToken() { URI policyUri = URI.create("http://opa.example.com:8181/v1/data/polaris/allow"); OpaPolarisAuthorizer authorizer = new OpaPolarisAuthorizer( - policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), tokenProvider); + policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), tokenProvider, null); assertThat(authorizer).isNotNull(); } @@ -492,7 +492,7 @@ public void testBearerTokenIsAddedToHttpRequest() { policyUri, mock(CloseableHttpClient.class), JsonMapper.builder().build(), - tokenProvider) { + tokenProvider, null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -535,7 +535,7 @@ public void testBearerTokenFromBearerTokenProvider() { policyUri, mock(CloseableHttpClient.class), JsonMapper.builder().build(), - tokenProvider) { + tokenProvider, null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -573,7 +573,7 @@ void resolveAuthorizationInputsResolvesAll() { URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null); + null, null); PolarisResolutionManifest resolutionManifest = mock(PolarisResolutionManifest.class); AuthorizationState authzState = new AuthorizationState(resolutionManifest); PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("role-1")); @@ -600,7 +600,7 @@ void authorizeUsesIntentInputsAndAllows() throws Exception { URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null) { + null, null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -648,7 +648,7 @@ void authorizeDeniesWhenOpaDenies() { URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null) { + null, null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -695,7 +695,7 @@ void authorizeIncludesStructuredParentsFromSecurable() throws Exception { URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null) { + null, null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -739,7 +739,7 @@ void authorizeResolvedCatalogTargetPreservesRootParent() throws Exception { "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"); OpaPolarisAuthorizer authorizer = new OpaPolarisAuthorizer( - policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null); + policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null, null); PolarisEntity rootEntity = new PolarisEntity.Builder() @@ -808,7 +808,7 @@ void authorizeRootScopedOperationSerializesRootTarget() throws Exception { "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"); OpaPolarisAuthorizer authorizer = new OpaPolarisAuthorizer( - policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null); + policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null, null); PolarisEntity rootEntity = new PolarisEntity.Builder() @@ -879,7 +879,7 @@ void authorizeRenameIncludesTargetAndSecondaryPaths() throws Exception { URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null) { + null, null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -944,7 +944,7 @@ void authorizeSingleOperationMultiIntentRequestEvaluatesSequentially() throws Ex URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null) { + null, null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -1030,7 +1030,7 @@ void authorizeUpdateTableMultiIntentRequestEvaluatesSequentially() { URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null) { + null, null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) From 778e3eceb00abed8e02ddf72d06e9a6a9ace5c36 Mon Sep 17 00:00:00 2001 From: gracechen09 Date: Wed, 8 Jul 2026 21:45:16 -0400 Subject: [PATCH 4/7] tests for verifying requestId for OPA context --- .../auth/opa/OpaPolarisAuthorizerTest.java | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java index 31ed08c0f1c..e5e0db2f392 100644 --- a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java +++ b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java @@ -22,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -29,6 +30,7 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; +import jakarta.enterprise.inject.Instance; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; @@ -39,6 +41,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; @@ -58,6 +61,7 @@ import org.apache.polaris.core.auth.PolarisSecurable; import org.apache.polaris.core.auth.RenameAuthorizationIntent; import org.apache.polaris.core.auth.SingleTargetAuthorizationIntent; +import org.apache.polaris.core.context.RequestIdSupplier; import org.apache.polaris.core.entity.PolarisBaseEntity; import org.apache.polaris.core.entity.PolarisEntity; import org.apache.polaris.core.entity.PolarisEntityConstants; @@ -1055,6 +1059,137 @@ T httpClientExecute( assertThat(requestCount[0]).isEqualTo(2); } + @Test + void requestIdComesFromResolvableSupplier() throws Exception { + final String[] capturedRequestBody = new String[1]; + + HttpServer server = createServerWithRequestCapture(capturedRequestBody); + try { + URI policyUri = + URI.create( + "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"); + + RequestIdSupplier supplier = mock(RequestIdSupplier.class); + when(supplier.getRequestId()).thenReturn("test-id"); + @SuppressWarnings("unchecked") + Instance requestIdSupplier = mock(Instance.class); + when(requestIdSupplier.isResolvable()).thenReturn(true); + when(requestIdSupplier.get()).thenReturn(supplier); + + OpaPolarisAuthorizer authorizer = + new OpaPolarisAuthorizer( + policyUri, + HttpClients.createDefault(), + JsonMapper.builder().build(), + null, + requestIdSupplier); + + PolarisPrincipal principal = + PolarisPrincipal.of("eve", Map.of("department", "finance"), Set.of("auditor")); + PolarisResolvedPathWrapper target = new PolarisResolvedPathWrapper(List.of()); + PolarisResolvedPathWrapper secondary = new PolarisResolvedPathWrapper(List.of()); + + assertThatNoException() + .isThrownBy( + () -> + authorizer.authorizeOrThrow( + principal, + Set.of(), + PolarisAuthorizableOperation.LOAD_VIEW, + target, + secondary)); + + ObjectMapper mapper = JsonMapper.builder().build(); + JsonNode root = mapper.readTree(capturedRequestBody[0]); + assertThat(root.at("/input/context/request_id").asText()).isEqualTo("test-id"); + } finally { + server.stop(0); + } + } + + @Test + void requestIdFallsBackToRandomUuidWhenSupplierUnresolvable() throws Exception { + final String[] capturedRequestBody = new String[1]; + + HttpServer server = createServerWithRequestCapture(capturedRequestBody); + try { + URI policyUri = + URI.create( + "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"); + + @SuppressWarnings("unchecked") + Instance requestIdSupplier = mock(Instance.class); + when(requestIdSupplier.isResolvable()).thenReturn(false); + + OpaPolarisAuthorizer authorizer = + new OpaPolarisAuthorizer( + policyUri, + HttpClients.createDefault(), + JsonMapper.builder().build(), + null, + requestIdSupplier); + + PolarisPrincipal principal = + PolarisPrincipal.of("eve", Map.of("department", "finance"), Set.of("auditor")); + PolarisResolvedPathWrapper target = new PolarisResolvedPathWrapper(List.of()); + PolarisResolvedPathWrapper secondary = new PolarisResolvedPathWrapper(List.of()); + + assertThatNoException() + .isThrownBy( + () -> + authorizer.authorizeOrThrow( + principal, + Set.of(), + PolarisAuthorizableOperation.LOAD_VIEW, + target, + secondary)); + + ObjectMapper mapper = JsonMapper.builder().build(); + JsonNode root = mapper.readTree(capturedRequestBody[0]); + String requestId = root.at("/input/context/request_id").asText(); + assertThatNoException().isThrownBy(() -> UUID.fromString(requestId)); + } finally { + server.stop(0); + } + } + + @Test + void requestIdFallsBackToRandomUuidWhenSupplierIsNull() throws Exception { + final String[] capturedRequestBody = new String[1]; + + HttpServer server = createServerWithRequestCapture(capturedRequestBody); + try { + URI policyUri = + URI.create( + "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"); + OpaPolarisAuthorizer authorizer = + new OpaPolarisAuthorizer( + policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null, null); + + PolarisPrincipal principal = + PolarisPrincipal.of("eve", Map.of("department", "finance"), Set.of("auditor")); + PolarisResolvedPathWrapper target = new PolarisResolvedPathWrapper(List.of()); + PolarisResolvedPathWrapper secondary = new PolarisResolvedPathWrapper(List.of()); + + assertThatNoException() + .isThrownBy( + () -> + authorizer.authorizeOrThrow( + principal, + Set.of(), + PolarisAuthorizableOperation.LOAD_VIEW, + target, + secondary)); + + ObjectMapper mapper = JsonMapper.builder().build(); + JsonNode root = mapper.readTree(capturedRequestBody[0]); + String requestId = root.at("/input/context/request_id").asText(); + assertThatNoException().isThrownBy(() -> UUID.fromString(requestId)); + } finally { + server.stop(0); + } + } + private AuthorizationRequest requestWithCatalogTarget(PolarisPrincipal principal) { return new AuthorizationRequest( principal, From 9e1d0986d54d679811ac200d3b2ac38758234f93 Mon Sep 17 00:00:00 2001 From: gracechen09 Date: Mon, 13 Jul 2026 22:00:48 -0400 Subject: [PATCH 5/7] inject RequestIdSupplier directly --- .../auth/opa/OpaPolarisAuthorizer.java | 17 +- .../auth/opa/OpaPolarisAuthorizerFactory.java | 7 +- .../opa/OpaPolarisAuthorizerFactoryTest.java | 181 +++++++++++++++++- .../auth/opa/OpaPolarisAuthorizerTest.java | 95 +++------ 4 files changed, 213 insertions(+), 87 deletions(-) diff --git a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java index f13d02cacdd..fb662fddc3a 100644 --- a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java +++ b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.annotations.VisibleForTesting; -import jakarta.enterprise.inject.Instance; import java.io.IOException; import java.net.URI; import java.util.ArrayList; @@ -56,7 +55,6 @@ import org.apache.polaris.core.auth.RootPrivilegeGrantAuthorizationIntent; import org.apache.polaris.core.auth.SingleTargetAuthorizationIntent; import org.apache.polaris.core.auth.TargetlessAuthorizationIntent; -import org.apache.polaris.core.context.RequestIdSupplier; import org.apache.polaris.core.entity.PolarisBaseEntity; import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper; import org.apache.polaris.core.persistence.ResolvedPolarisEntity; @@ -91,7 +89,7 @@ class OpaPolarisAuthorizer implements PolarisAuthorizer { private final BearerTokenProvider tokenProvider; private final CloseableHttpClient httpClient; private final ObjectMapper objectMapper; - private final Instance requestIdSupplier; + private final String requestId; /** * Public constructor that accepts a complete policy URI. @@ -103,21 +101,22 @@ class OpaPolarisAuthorizer implements PolarisAuthorizer { * @param objectMapper Jackson ObjectMapper for JSON serialization (required). Shared across * authorizer instances to avoid initialization overhead. * @param tokenProvider Token provider for authentication (optional) - * @param requestIdSupplier Supplier for the server-generated request ID (optional), used to - * correlate OPA queries with the originating HTTP request + * @param requestId The server-generated request ID (optional), used to correlate OPA queries with + * the originating HTTP request. Resolved once by the caller since this authorizer is + * constructed fresh per request. */ public OpaPolarisAuthorizer( @NonNull URI policyUri, @NonNull CloseableHttpClient httpClient, @NonNull ObjectMapper objectMapper, @Nullable BearerTokenProvider tokenProvider, - @Nullable Instance requestIdSupplier) { + @Nullable String requestId) { this.policyUri = policyUri; this.tokenProvider = tokenProvider; this.httpClient = httpClient; this.objectMapper = objectMapper; - this.requestIdSupplier = requestIdSupplier; + this.requestId = requestId; } /** @@ -346,10 +345,6 @@ private ImmutableActor buildActor(PolarisPrincipal principal) { } private ImmutableContext buildContext() { - String requestId = null; - if (requestIdSupplier != null && requestIdSupplier.isResolvable()) { - requestId = requestIdSupplier.get().getRequestId(); - } return ImmutableContext.builder() .requestId(requestId != null ? requestId : UUID.randomUUID().toString()) .build(); diff --git a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java index 3d448e2331d..3bd5511b066 100644 --- a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java +++ b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java @@ -24,7 +24,6 @@ import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; import java.io.IOException; import java.net.URI; @@ -54,7 +53,7 @@ class OpaPolarisAuthorizerFactory implements PolarisAuthorizerFactory { private final Clock clock; private final ObjectMapper objectMapper; private final AsyncExec asyncExec; - private final Instance requestIdSupplier; + private final RequestIdSupplier requestIdSupplier; private CloseableHttpClient httpClient; private BearerTokenProvider bearerTokenProvider; @@ -63,7 +62,7 @@ public OpaPolarisAuthorizerFactory( OpaAuthorizationConfig opaConfig, Clock clock, AsyncExec asyncExec, - Instance requestIdSupplier) { + RequestIdSupplier requestIdSupplier) { this.opaConfig = opaConfig; this.clock = clock; this.asyncExec = asyncExec; @@ -104,7 +103,7 @@ public PolarisAuthorizer create(RealmConfig realmConfig) { "OPA policy URI must be configured via polaris.authorization.opa.policy-uri")); return new OpaPolarisAuthorizer( - policyUri, httpClient, objectMapper, bearerTokenProvider, requestIdSupplier); + policyUri, httpClient, objectMapper, bearerTokenProvider, requestIdSupplier.getRequestId()); } @PreDestroy diff --git a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java index 7be3be7e20a..27ccf1116a4 100644 --- a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java +++ b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java @@ -19,15 +19,32 @@ package org.apache.polaris.extension.auth.opa; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.Mockito.mock; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.time.Clock; import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.polaris.core.auth.PolarisAuthorizableOperation; +import org.apache.polaris.core.auth.PolarisPrincipal; import org.apache.polaris.core.config.RealmConfig; +import org.apache.polaris.core.entity.PolarisBaseEntity; +import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper; import org.apache.polaris.extension.auth.opa.token.FileBearerTokenProvider; import org.apache.polaris.nosql.async.java.JavaPoolAsyncExec; import org.junit.jupiter.api.Test; @@ -63,7 +80,7 @@ public void testFactoryWithStaticTokenConfiguration() { try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { OpaPolarisAuthorizerFactory factory = - new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, null); + new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, () -> null); // Create authorizer RealmConfig realmConfig = mock(RealmConfig.class); @@ -107,7 +124,7 @@ public void testFactoryWithFileBasedTokenConfiguration() throws IOException { try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { OpaPolarisAuthorizerFactory factory = - new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, null); + new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, () -> null); // Create authorizer RealmConfig realmConfig = mock(RealmConfig.class); @@ -151,7 +168,7 @@ public void testFactoryWithNoTokenConfiguration() { try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { OpaPolarisAuthorizerFactory factory = - new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, null); + new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, () -> null); // Create authorizer RealmConfig realmConfig = mock(RealmConfig.class); @@ -160,4 +177,162 @@ public void testFactoryWithNoTokenConfiguration() { assertThat(authorizer).isNotNull(); } } + + @Test + public void testFactoryPassesResolvedRequestIdToAuthorizerContext() throws Exception { + final String[] capturedRequestBody = new String[1]; + + HttpServer server = createServerWithRequestCapture(capturedRequestBody); + try { + OpaAuthorizationConfig opaConfig = + ImmutableOpaAuthorizationConfig.builder() + .policyUri( + URI.create( + "http://localhost:" + + server.getAddress().getPort() + + "/v1/data/polaris/allow")) + .auth( + ImmutableAuthenticationConfig.builder() + .type(OpaAuthorizationConfig.AuthenticationType.NONE) + .build()) + .http( + ImmutableHttpConfig.builder() + .timeout(Duration.ofSeconds(2)) + .verifySsl(true) + .build()) + .build(); + + try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { + OpaPolarisAuthorizerFactory factory = + new OpaPolarisAuthorizerFactory( + opaConfig, Clock.systemUTC(), asyncExec, () -> "factory-test-request-id"); + factory.initialize(); + + RealmConfig realmConfig = mock(RealmConfig.class); + OpaPolarisAuthorizer authorizer = (OpaPolarisAuthorizer) factory.create(realmConfig); + + PolarisPrincipal principal = + PolarisPrincipal.of("eve", Map.of("department", "finance"), Set.of("auditor")); + PolarisResolvedPathWrapper target = new PolarisResolvedPathWrapper(List.of()); + PolarisResolvedPathWrapper secondary = new PolarisResolvedPathWrapper(List.of()); + + assertThatNoException() + .isThrownBy( + () -> + authorizer.authorizeOrThrow( + principal, + Set.of(), + PolarisAuthorizableOperation.LOAD_VIEW, + target, + secondary)); + + JsonNode root = JsonMapper.builder().build().readTree(capturedRequestBody[0]); + assertThat(root.at("/input/context/request_id").asText()) + .isEqualTo("factory-test-request-id"); + } + } finally { + server.stop(0); + } + } + + @Test + public void testFactoryResolvesFreshRequestIdPerCreateCall() throws Exception { + final String[] capturedRequestBody = new String[1]; + + HttpServer server = createServerWithRequestCapture(capturedRequestBody); + try { + OpaAuthorizationConfig opaConfig = + ImmutableOpaAuthorizationConfig.builder() + .policyUri( + URI.create( + "http://localhost:" + + server.getAddress().getPort() + + "/v1/data/polaris/allow")) + .auth( + ImmutableAuthenticationConfig.builder() + .type(OpaAuthorizationConfig.AuthenticationType.NONE) + .build()) + .http( + ImmutableHttpConfig.builder() + .timeout(Duration.ofSeconds(2)) + .verifySsl(true) + .build()) + .build(); + + // Simulates the CDI proxy resolving to a different value on each request, the way the real + // RequestIdSupplier does via CurrentRequestManager. + AtomicInteger requestCounter = new AtomicInteger(); + try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { + OpaPolarisAuthorizerFactory factory = + new OpaPolarisAuthorizerFactory( + opaConfig, + Clock.systemUTC(), + asyncExec, + () -> "request-" + requestCounter.incrementAndGet()); + + factory.initialize(); + + RealmConfig realmConfig = mock(RealmConfig.class); + PolarisPrincipal principal = + PolarisPrincipal.of("eve", Map.of("department", "finance"), Set.of("auditor")); + PolarisResolvedPathWrapper target = new PolarisResolvedPathWrapper(List.of()); + PolarisResolvedPathWrapper secondary = new PolarisResolvedPathWrapper(List.of()); + + // First "request": factory.create() is called fresh, as it would be for each incoming + // HTTP request via the @RequestScoped PolarisAuthorizer producer. + OpaPolarisAuthorizer firstAuthorizer = (OpaPolarisAuthorizer) factory.create(realmConfig); + assertThatNoException() + .isThrownBy( + () -> + firstAuthorizer.authorizeOrThrow( + principal, + Set.of(), + PolarisAuthorizableOperation.LOAD_VIEW, + target, + secondary)); + JsonNode firstRoot = JsonMapper.builder().build().readTree(capturedRequestBody[0]); + assertThat(firstRoot.at("/input/context/request_id").asText()).isEqualTo("request-1"); + + // Second "request": a new authorizer is created against the same long-lived factory, + // and must pick up a distinct, freshly-resolved request ID rather than reusing the first. + OpaPolarisAuthorizer secondAuthorizer = (OpaPolarisAuthorizer) factory.create(realmConfig); + assertThatNoException() + .isThrownBy( + () -> + secondAuthorizer.authorizeOrThrow( + principal, + Set.of(), + PolarisAuthorizableOperation.LOAD_VIEW, + target, + secondary)); + JsonNode secondRoot = JsonMapper.builder().build().readTree(capturedRequestBody[0]); + assertThat(secondRoot.at("/input/context/request_id").asText()).isEqualTo("request-2"); + } + } finally { + server.stop(0); + } + } + + private HttpServer createServerWithRequestCapture(String[] capturedRequestBody) + throws IOException { + HttpServer server = HttpServer.create(new InetSocketAddress(0), 0); + server.createContext( + "/v1/data/polaris/allow", + new HttpHandler() { + @Override + public void handle(HttpExchange exchange) throws IOException { + byte[] requestBytes = exchange.getRequestBody().readAllBytes(); + capturedRequestBody[0] = new String(requestBytes, StandardCharsets.UTF_8); + + String response = "{\"result\":{\"allow\":true}}"; + exchange.getResponseHeaders().add("Content-Type", "application/json"); + exchange.sendResponseHeaders(200, response.length()); + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes(StandardCharsets.UTF_8)); + } + } + }); + server.start(); + return server; + } } diff --git a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java index e5e0db2f392..5be0cc220d0 100644 --- a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java +++ b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java @@ -22,7 +22,6 @@ import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -30,7 +29,6 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; -import jakarta.enterprise.inject.Instance; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; @@ -61,7 +59,6 @@ import org.apache.polaris.core.auth.PolarisSecurable; import org.apache.polaris.core.auth.RenameAuthorizationIntent; import org.apache.polaris.core.auth.SingleTargetAuthorizationIntent; -import org.apache.polaris.core.context.RequestIdSupplier; import org.apache.polaris.core.entity.PolarisBaseEntity; import org.apache.polaris.core.entity.PolarisEntity; import org.apache.polaris.core.entity.PolarisEntityConstants; @@ -477,7 +474,11 @@ public void testCreateWithHttpsAndBearerToken() { URI policyUri = URI.create("http://opa.example.com:8181/v1/data/polaris/allow"); OpaPolarisAuthorizer authorizer = new OpaPolarisAuthorizer( - policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), tokenProvider, null); + policyUri, + HttpClients.createDefault(), + JsonMapper.builder().build(), + tokenProvider, + null); assertThat(authorizer).isNotNull(); } @@ -496,7 +497,8 @@ public void testBearerTokenIsAddedToHttpRequest() { policyUri, mock(CloseableHttpClient.class), JsonMapper.builder().build(), - tokenProvider, null) { + tokenProvider, + null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -539,7 +541,8 @@ public void testBearerTokenFromBearerTokenProvider() { policyUri, mock(CloseableHttpClient.class), JsonMapper.builder().build(), - tokenProvider, null) { + tokenProvider, + null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -577,7 +580,8 @@ void resolveAuthorizationInputsResolvesAll() { URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null, null); + null, + null); PolarisResolutionManifest resolutionManifest = mock(PolarisResolutionManifest.class); AuthorizationState authzState = new AuthorizationState(resolutionManifest); PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("role-1")); @@ -604,7 +608,8 @@ void authorizeUsesIntentInputsAndAllows() throws Exception { URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null, null) { + null, + null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -652,7 +657,8 @@ void authorizeDeniesWhenOpaDenies() { URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null, null) { + null, + null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -699,7 +705,8 @@ void authorizeIncludesStructuredParentsFromSecurable() throws Exception { URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null, null) { + null, + null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -883,7 +890,8 @@ void authorizeRenameIncludesTargetAndSecondaryPaths() throws Exception { URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null, null) { + null, + null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -948,7 +956,8 @@ void authorizeSingleOperationMultiIntentRequestEvaluatesSequentially() throws Ex URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null, null) { + null, + null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -1034,7 +1043,8 @@ void authorizeUpdateTableMultiIntentRequestEvaluatesSequentially() { URI.create("http://opa.example.com:8181/v1/data/polaris/allow"), mock(CloseableHttpClient.class), JsonMapper.builder().build(), - null, null) { + null, + null) { @Override T httpClientExecute( ClassicHttpRequest request, HttpClientResponseHandler responseHandler) @@ -1060,7 +1070,7 @@ T httpClientExecute( } @Test - void requestIdComesFromResolvableSupplier() throws Exception { + void requestIdIsIncludedInContextWhenProvided() throws Exception { final String[] capturedRequestBody = new String[1]; HttpServer server = createServerWithRequestCapture(capturedRequestBody); @@ -1069,20 +1079,13 @@ void requestIdComesFromResolvableSupplier() throws Exception { URI.create( "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"); - RequestIdSupplier supplier = mock(RequestIdSupplier.class); - when(supplier.getRequestId()).thenReturn("test-id"); - @SuppressWarnings("unchecked") - Instance requestIdSupplier = mock(Instance.class); - when(requestIdSupplier.isResolvable()).thenReturn(true); - when(requestIdSupplier.get()).thenReturn(supplier); - OpaPolarisAuthorizer authorizer = new OpaPolarisAuthorizer( policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null, - requestIdSupplier); + "test-id"); PolarisPrincipal principal = PolarisPrincipal.of("eve", Map.of("department", "finance"), Set.of("auditor")); @@ -1108,53 +1111,7 @@ void requestIdComesFromResolvableSupplier() throws Exception { } @Test - void requestIdFallsBackToRandomUuidWhenSupplierUnresolvable() throws Exception { - final String[] capturedRequestBody = new String[1]; - - HttpServer server = createServerWithRequestCapture(capturedRequestBody); - try { - URI policyUri = - URI.create( - "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"); - - @SuppressWarnings("unchecked") - Instance requestIdSupplier = mock(Instance.class); - when(requestIdSupplier.isResolvable()).thenReturn(false); - - OpaPolarisAuthorizer authorizer = - new OpaPolarisAuthorizer( - policyUri, - HttpClients.createDefault(), - JsonMapper.builder().build(), - null, - requestIdSupplier); - - PolarisPrincipal principal = - PolarisPrincipal.of("eve", Map.of("department", "finance"), Set.of("auditor")); - PolarisResolvedPathWrapper target = new PolarisResolvedPathWrapper(List.of()); - PolarisResolvedPathWrapper secondary = new PolarisResolvedPathWrapper(List.of()); - - assertThatNoException() - .isThrownBy( - () -> - authorizer.authorizeOrThrow( - principal, - Set.of(), - PolarisAuthorizableOperation.LOAD_VIEW, - target, - secondary)); - - ObjectMapper mapper = JsonMapper.builder().build(); - JsonNode root = mapper.readTree(capturedRequestBody[0]); - String requestId = root.at("/input/context/request_id").asText(); - assertThatNoException().isThrownBy(() -> UUID.fromString(requestId)); - } finally { - server.stop(0); - } - } - - @Test - void requestIdFallsBackToRandomUuidWhenSupplierIsNull() throws Exception { + void requestIdFallsBackToRandomUuidWhenNull() throws Exception { final String[] capturedRequestBody = new String[1]; HttpServer server = createServerWithRequestCapture(capturedRequestBody); From afa9f1654ce426cc7aaf8db7a8945341867c3bc9 Mon Sep 17 00:00:00 2001 From: gracechen09 Date: Mon, 13 Jul 2026 22:05:20 -0400 Subject: [PATCH 6/7] add note to reusable helper --- .../extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java index 27ccf1116a4..9842e96da47 100644 --- a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java +++ b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java @@ -313,6 +313,8 @@ public void testFactoryResolvesFreshRequestIdPerCreateCall() throws Exception { } } + // Reused the helper from PR https://github.com/apache/polaris/pull/4992/changes#diff-276bca1175392a4ab4e29ac695a8442422f6a54378320cadfe495db6dd6cc6b2 + // Can be removed once the PR above is merged private HttpServer createServerWithRequestCapture(String[] capturedRequestBody) throws IOException { HttpServer server = HttpServer.create(new InetSocketAddress(0), 0); From 4413965c4f07d3b0629b39d0265536dcfd29c7b0 Mon Sep 17 00:00:00 2001 From: gracechen09 Date: Wed, 15 Jul 2026 12:25:01 -0400 Subject: [PATCH 7/7] merge from main; resolve merge conflict; apply spotlessCheck --- .../auth/opa/OpaPolarisAuthorizer.java | 19 ++---------- .../auth/opa/OpaPolarisAuthorizerFactory.java | 2 +- .../opa/OpaPolarisAuthorizerFactoryTest.java | 30 +++++++++++++------ .../auth/opa/OpaPolarisAuthorizerTest.java | 12 ++++++-- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java index aa4b20f8d02..286b7665a6c 100644 --- a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java +++ b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java @@ -89,11 +89,8 @@ class OpaPolarisAuthorizer implements PolarisAuthorizer { private final BearerTokenProvider tokenProvider; private final CloseableHttpClient httpClient; private final ObjectMapper objectMapper; -<<<<<<< HEAD private final String requestId; -======= private final String realm; ->>>>>>> main /** * Public constructor that accepts a complete policy URI and the current realm identifier. @@ -105,34 +102,25 @@ class OpaPolarisAuthorizer implements PolarisAuthorizer { * @param objectMapper Jackson ObjectMapper for JSON serialization (required). Shared across * authorizer instances to avoid initialization overhead. * @param tokenProvider Token provider for authentication (optional) -<<<<<<< HEAD * @param requestId The server-generated request ID (optional), used to correlate OPA queries with * the originating HTTP request. Resolved once by the caller since this authorizer is * constructed fresh per request. -======= * @param realm The realm identifier (from RealmContext) for isolation in OPA policies. ->>>>>>> main */ public OpaPolarisAuthorizer( @NonNull URI policyUri, @NonNull CloseableHttpClient httpClient, @NonNull ObjectMapper objectMapper, @Nullable BearerTokenProvider tokenProvider, -<<<<<<< HEAD - @Nullable String requestId) { -======= + @Nullable String requestId, @NonNull String realm) { ->>>>>>> main this.policyUri = policyUri; this.tokenProvider = tokenProvider; this.httpClient = httpClient; this.objectMapper = objectMapper; -<<<<<<< HEAD this.requestId = requestId; -======= this.realm = realm; ->>>>>>> main } /** @@ -361,13 +349,10 @@ private ImmutableActor buildActor(PolarisPrincipal principal) { } private ImmutableContext buildContext() { -<<<<<<< HEAD return ImmutableContext.builder() .requestId(requestId != null ? requestId : UUID.randomUUID().toString()) + .realm(realm) .build(); -======= - return ImmutableContext.builder().requestId(UUID.randomUUID().toString()).realm(realm).build(); ->>>>>>> main } private ImmutableResource buildResource( diff --git a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java index f7ecc5413f4..c27ee200e8e 100644 --- a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java +++ b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java @@ -34,8 +34,8 @@ import org.apache.polaris.core.auth.PolarisAuthorizer; import org.apache.polaris.core.auth.PolarisAuthorizerFactory; import org.apache.polaris.core.config.RealmConfig; -import org.apache.polaris.core.context.RequestIdSupplier; import org.apache.polaris.core.context.RealmContext; +import org.apache.polaris.core.context.RequestIdSupplier; import org.apache.polaris.extension.auth.opa.token.BearerTokenProvider; import org.apache.polaris.extension.auth.opa.token.FileBearerTokenProvider; import org.apache.polaris.extension.auth.opa.token.StaticBearerTokenProvider; diff --git a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java index 3b2bbe43829..f514205d893 100644 --- a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java +++ b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java @@ -38,14 +38,14 @@ import java.time.Clock; import java.time.Duration; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import org.apache.polaris.core.entity.PolarisBaseEntity; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.polaris.core.auth.PolarisAuthorizableOperation; import org.apache.polaris.core.auth.PolarisPrincipal; import org.apache.polaris.core.config.RealmConfig; import org.apache.polaris.core.context.RealmContext; +import org.apache.polaris.core.entity.PolarisBaseEntity; import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper; import org.apache.polaris.extension.auth.opa.token.FileBearerTokenProvider; import org.apache.polaris.nosql.async.java.JavaPoolAsyncExec; @@ -83,7 +83,8 @@ public void testFactoryWithStaticTokenConfiguration() { try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { RealmContext realmContext = () -> "test-realm"; OpaPolarisAuthorizerFactory factory = - new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext); + new OpaPolarisAuthorizerFactory( + opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext); // Create authorizer RealmConfig realmConfig = mock(RealmConfig.class); @@ -128,7 +129,8 @@ public void testFactoryWithFileBasedTokenConfiguration() throws IOException { try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { RealmContext realmContext = () -> "test-realm"; OpaPolarisAuthorizerFactory factory = - new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext); + new OpaPolarisAuthorizerFactory( + opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext); // Create authorizer RealmConfig realmConfig = mock(RealmConfig.class); @@ -174,7 +176,8 @@ public void testFactoryWithNoTokenConfiguration() { try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { RealmContext realmContext = () -> "test-realm"; OpaPolarisAuthorizerFactory factory = - new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext); + new OpaPolarisAuthorizerFactory( + opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext); // Create authorizer RealmConfig realmConfig = mock(RealmConfig.class); @@ -211,7 +214,8 @@ public void testFactoryPassesRealmToAuthorizerContext() throws Exception { try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { RealmContext realmContext = () -> "factory-realm"; OpaPolarisAuthorizerFactory factory = - new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, realmContext); + new OpaPolarisAuthorizerFactory( + opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext); factory.initialize(); OpaPolarisAuthorizer authorizer = @@ -266,7 +270,8 @@ public void testFactoryUsesDistinctRealmValues() throws Exception { try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { RealmContext realmContext = () -> "realm-b"; OpaPolarisAuthorizerFactory factory = - new OpaPolarisAuthorizerFactory(opaConfig, Clock.systemUTC(), asyncExec, realmContext); + new OpaPolarisAuthorizerFactory( + opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext); factory.initialize(); OpaPolarisAuthorizer authorizer = @@ -318,9 +323,14 @@ public void testFactoryPassesResolvedRequestIdToAuthorizerContext() throws Excep .build(); try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { + RealmContext realmContext = () -> "test-realm"; OpaPolarisAuthorizerFactory factory = new OpaPolarisAuthorizerFactory( - opaConfig, Clock.systemUTC(), asyncExec, () -> "factory-test-request-id"); + opaConfig, + Clock.systemUTC(), + asyncExec, + () -> "factory-test-request-id", + realmContext); factory.initialize(); RealmConfig realmConfig = mock(RealmConfig.class); @@ -378,12 +388,14 @@ public void testFactoryResolvesFreshRequestIdPerCreateCall() throws Exception { // RequestIdSupplier does via CurrentRequestManager. AtomicInteger requestCounter = new AtomicInteger(); try (JavaPoolAsyncExec asyncExec = new JavaPoolAsyncExec()) { + RealmContext realmContext = () -> "test-realm"; OpaPolarisAuthorizerFactory factory = new OpaPolarisAuthorizerFactory( opaConfig, Clock.systemUTC(), asyncExec, - () -> "request-" + requestCounter.incrementAndGet()); + () -> "request-" + requestCounter.incrementAndGet(), + realmContext); factory.initialize(); diff --git a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java index 1d782e22747..bab45c2751a 100644 --- a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java +++ b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java @@ -1137,6 +1137,7 @@ void serializesInputWithRealm() throws Exception { HttpClients.createDefault(), JsonMapper.builder().build(), null, + null, "explicit-realm"); PolarisPrincipal principal = PolarisPrincipal.of("eve", Map.of(), Set.of("auditor")); @@ -1176,6 +1177,7 @@ void serializesRealmInAuthorizePath() throws Exception { HttpClients.createDefault(), JsonMapper.builder().build(), null, + null, "tenant-xyz"); PolarisResolutionManifest resolutionManifest = mock(PolarisResolutionManifest.class); @@ -1209,7 +1211,8 @@ void requestIdIsIncludedInContextWhenProvided() throws Exception { HttpClients.createDefault(), JsonMapper.builder().build(), null, - "test-id"); + "test-id", + "test-realm"); PolarisPrincipal principal = PolarisPrincipal.of("eve", Map.of("department", "finance"), Set.of("auditor")); @@ -1245,7 +1248,12 @@ void requestIdFallsBackToRandomUuidWhenNull() throws Exception { "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"); OpaPolarisAuthorizer authorizer = new OpaPolarisAuthorizer( - policyUri, HttpClients.createDefault(), JsonMapper.builder().build(), null, null); + policyUri, + HttpClients.createDefault(), + JsonMapper.builder().build(), + null, + null, + "test-realm"); PolarisPrincipal principal = PolarisPrincipal.of("eve", Map.of("department", "finance"), Set.of("auditor"));