Skip to content

[#13251] improvement(authz): Reuse request context during list filtering - #13252

Merged
yuqi1129 merged 2 commits into
apache:mainfrom
yuqi1129:improvement/reuse-list-auth-context
Sep 21, 2026
Merged

yuqi1129 merged 2 commits into
apache:mainfrom
yuqi1129:improvement/reuse-list-auth-context

Conversation

@yuqi1129

@yuqi1129 yuqi1129 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Reuse entry authorization state for parent-scope checks and per-object list filtering in read-only REST requests. A scoped binding is opened by the interceptor, bound only for read methods, reused only for the same principal instance and metalake, and cleared when the request completes. Filter workers receive the context explicitly; mutation operations retain independent contexts.

Why are the changes needed?

Separate contexts repeat user and role-version lookups during one list request. Reusing the context lets filtering use state already loaded by entry authorization.

Fix: #13251

Does this PR introduce any user-facing change?

No API or configuration changes. Authorization semantics remain unchanged.

How was this patch tested?

139 targeted unit tests passed across server-common, server, and iceberg-rest-server. Tests cover context reuse through REST interceptors, parallel filtering with table denies, security-context isolation, exception cleanup, and JCasbin SQL-prefetch reuse with revalidation on the next request. Ran Spotless on the changed modules.

Copilot AI lite review requested due to automatic review settings September 16, 2026 14:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown

Code Coverage Report

Overall Project 70.5% +0.08% 🟢
Files changed 86.08% 🟢

Module Coverage
aliyun 19.74% 🔴
api 51.99% 🟢
authorization-common 85.96% 🟢
authorization-ranger 4.38% 🔴
aws 53.54% 🟢
aws-bundle 0.0% 🔴
azure 32.1% 🔴
azure-bundle 0.0% 🔴
catalog-common 28.55% 🔴
catalog-fileset 82.15% 🟢
catalog-glue 71.52% 🟢
catalog-hive 83.08% 🟢
catalog-jdbc-common 45.72% 🟢
catalog-jdbc-doris 83.47% 🟢
catalog-jdbc-mysql 81.8% 🟢
catalog-jdbc-postgresql 83.89% 🟢
catalog-jdbc-starrocks 79.16% 🟢
catalog-kafka 75.21% 🟢
catalog-lakehouse-generic 63.27% 🟢
catalog-lakehouse-hudi 79.1% 🟢
catalog-lakehouse-iceberg 85.98% 🟢
catalog-lakehouse-paimon 84.31% 🟢
catalog-model 77.99% 🟢
cli 44.62% 🟢
client-java 77.96% 🟢
common 58.29% 🟢
core 84.82% -0.03% 🟢
filesystem-hadoop3 76.48% 🟢
flink 18.07% 🔴
flink-common 53.95% 🟢
flink-runtime 0.0% 🔴
gcp 32.2% 🔴
hadoop-auth 68.0% 🟢
hadoop-common 17.84% 🔴
hive-metastore-common 54.41% 🟢
iceberg-aliyun-bundle 0.0% 🔴
iceberg-common 66.16% 🟢
iceberg-rest-server 77.1% 🟢
idp-basic 86.77% 🟢
integration-test-common 0.0% 🔴
jobs 59.96% 🟢
lance-common 36.14% 🔴
lance-rest-server 69.51% 🟢
lineage 59.39% 🟢
optimizer 83.4% 🟢
optimizer-api 42.56% 🟢
server 90.17% +0.17% 🟢
server-common 82.44% +0.23% 🟢
spark 60.5% 🟢
tencent 81.78% 🟢
trino-connector 61.99% 🟢
Files
Module File Coverage
core AuthorizationRequestContext.java 69.23% 🟢
server GravitinoInterceptionService.java 93.79% 🟢
server-common AuthorizationRequestScope.java 100.0% 🟢
BaseMetadataAuthorizationMethodInterceptor.java 100.0% 🟢
MetadataAuthzHelper.java 76.67% 🟢

@yuqi1129 yuqi1129 self-assigned this Sep 17, 2026
@yuqi1129
yuqi1129 requested a review from roryqi September 18, 2026 02:01
Drop the derivable authorizer parameter and the redundant active-roles check
from AuthorizationRequestScope, bind the principal at bind time, remove the
unused nested-scope restore, and keep the read-only bind rule in one helper
shared by both interceptors. filterByExpression returns early when
authorization is disabled or the list is empty and resolves the principal and
authorizer once. Tests no longer swap the filter executor by reflection.
@jerryshao

Copy link
Copy Markdown
Contributor

Verdict: ship it — no blocking issues found.

Findings

  1. server-common/src/main/java/org/apache/gravitino/server/authorization/AuthorizationRequestScope.java:58,114open() overwrites CURRENT without saving the previous scope and close() unconditionally calls CURRENT.remove(), so the class is not nest-safe. This is latent rather than live: the only two openers are GravitinoInterceptionService.java:166, whose getMethodInterceptors at :134 returns exactly one interceptor, and BaseMetadataAuthorizationMethodInterceptor.java:226, whose only subclasses are the Iceberg and Lance interceptors, each installed on its own resource methods — so no invocation can nest today. If an intercepted method ever calls another intercepted method, the inner close() silently unbinds the outer scope and reuse stops with no signal (authorization stays correct, since getOrCreate falls back to a fresh context). Save and restore the previous value in open()/close(), or assert CURRENT.get() == null in open(). (verified by: reading both openers and getMethodInterceptors, and grepping every AuthorizationRequestScope. reference in the repo — those are the only non-test open() sites.)

  2. server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataAuthzHelper.java:302,506 — now that getOrCreate at :396 can hand back the interceptor's context, allVisibleViaParentScope and doFilter overwrite originalAuthorizationExpression on it, making that field last-writer-wins across entry authorization and filtering. No functional impact today: it is read only for DEBUG logging in JcasbinAuthorizer.java:301,332,372 and for the denial-event message at GravitinoInterceptionService.java:264, which runs before bindIfRead at :274. Worth a sentence on the field in AuthorizationRequestContext so a future reader after proceed() doesn't assume it still carries the entry expression. (verified by: grepping every get/set of the field and checking the call ordering inside the interceptor.)

Tests

Coverage of the new behaviour is good: parallel filtering with a table-level deny plus the short-circuit hit/miss split (TestBaseMetadataAuthorizationMethodInterceptor#testReadListReusesEntryContext), cleanup after the endpoint throws, POST non-reuse, principal-identity and metalake isolation and worker non-inheritance (TestAuthorizationRequestScope), and prefetch reuse with revalidation on the next request (TestJcasbinAuthorizer#testReadScopeReusesEntryRolePrefetch).

Two gaps:

  • BaseMetadataAuthorizationMethodInterceptor.java:329 — the skipStandardCheck ? null : metalakeIdent branch is never exercised. shouldSkipAuthorization is overridden only by IcebergMetadataAuthorizationMethodInterceptor.java:183, and the new TestInterceptor does not override it, so nothing asserts that a skipped standard check leaves the scope unbound on a @GET method. A one-line override in the test interceptor covers it.
  • No test opens a nested scope, which is what would catch finding 1 if it ever becomes reachable.

Verified separately, since it is the crux of whether the optimization fires at all: the identity check at AuthorizationRequestScope.java:104 holds in production. AuthenticationFilter.runAsPrincipal (server-common/.../authentication/AuthenticationFilter.java:248) publishes the same finalized Principal instance both as the request attribute and into the Subject, and Utils.doAs (server-common/.../web/Utils.java:282) re-binds that same instance, so PrincipalUtils.getCurrentPrincipal() inside the resource method returns the object bound by the interceptor. The @GET gate also misses nothing: all 17 filterByExpression call sites under server/.../web/rest/ and the Lance list endpoints (LanceNamespaceOperations.java:69,82,189) are @GET, and every module uses javax.ws.rs, so the annotation check matches.

Nits

  • AuthorizationRequestScope.java:86bind is public but only bindIfRead and tests call it; @VisibleForTesting would document that the read-only gate lives in bindIfRead and discourage future callers from going around it.
  • AuthorizationRequestScope.java:101 — a cache miss silently returns a fresh context. A DEBUG log on the mismatch branch (principal vs. metalake) would make "why is reuse not happening here" diagnosable without a debugger.

Generated by Claude Code

@yuqi1129
yuqi1129 merged commit c5f76b2 into apache:main Sep 21, 2026
38 checks passed
@yuqi1129
yuqi1129 deleted the improvement/reuse-list-auth-context branch September 21, 2026 01:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Improvement] Reuse authorization context between request authorization and list filtering

4 participants