Skip to content

Deduplicate intent-to-resolved-path resolution across authorizers - #5486

Merged
flyrain merged 2 commits into
apache:mainfrom
ZephyrYWZhou:dedup-authorization-intent-resolver
Sep 10, 2026
Merged

flyrain merged 2 commits into
apache:mainfrom
ZephyrYWZhou:dedup-authorization-intent-resolver

Conversation

@ZephyrYWZhou

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #5194 (which routed the CatalogHandler / PolarisAdminService call sites through authorize(AuthorizationState, AuthorizationRequest)), as discussed on #5170.

After #5194, each PolarisAuthorizer implementation re-derives the securables it needs from an AuthorizationRequest's intents. The mechanical mapping from an AuthorizationIntent to its resolved target/secondary paths was duplicated verbatim in two authorizers:

  • PolarisAuthorizerImpl (built-in RBAC, polaris-core)
  • RangerPolarisAuthorizer (Apache Ranger extension)

Both contained the same per-intent-type dispatch over the seven sealed AuthorizationIntent types plus identical private getResolvedSecurable(...) and getPathNamesWithinCatalog(...) helpers. This is correctness-sensitive and drift-prone — for example, the CATALOG branch in getResolvedSecurable had to be added to both copies. A future change to the resolution rules or a new intent type risks being applied to one authorizer and missed in the other.

This PR extracts that translation into a single AuthorizationIntentResolver in org.apache.polaris.core.auth, so both authorizers (and any future resolved-path authorizer) share one implementation.

Changes

  • New AuthorizationIntentResolver (polaris-core): resolve(manifest, intent, prependRootContainer) returns a ResolvedIntent(targets, secondaries). It holds the per-intent dispatch, getResolvedSecurable (reference-catalog / top-level / path), and getPathNamesWithinCatalog.
  • PolarisAuthorizerImpl and RangerPolarisAuthorizer: authorizeIntent now calls the shared resolver; the duplicated private helpers are removed. Each authorizer keeps its own operation-semantics / rooting decision (passing its own prependRootContainer), its terminal decision call (authorizeRbacOrThrow / authorizeRangerOrThrow), and its exception handling.
  • Whether an operation roots its path at the root container is authorizer-specific, so that flag is passed in rather than decided by the resolver.
  • OpaPolarisAuthorizer is unchanged — it maps intents to its own ResourceEntity model and does not perform resolution-manifest path resolution.

Behavior

No behavioral change. The extracted logic is identical to what each authorizer previously ran, and the resolver executes inside the same try that translates a ForbiddenException into a decision.

Testing

  • New AuthorizationIntentResolverTest covering all seven intent types, the three securable-resolution branches (reference catalog / top-level / path), the rooted vs. non-rooted targetless cases, and the unresolvable-securable IllegalStateException.
  • Existing suites pass unchanged: PolarisAuthorizerImplTest, the runtime-service catalog handler authz tests (IcebergCatalogHandlerAuthzTest, IcebergCatalogHandlerFineGrainedDisabledTest, PolicyCatalogHandlerAuthzTest, PolarisGenericTableCatalogHandlerAuthzTest), the Ranger unit tests + intTest, and the OPA tests.
  • ./gradlew :polaris-core:check passes (Spotless + Checkstyle + tests).

Note

polaris-core compiles with --release 17, so the resolver uses an if / else if chain rather than a Java 21 pattern switch. A natural follow-up (noted in #5485) is to let each AuthorizationIntent expose its own securables, which would remove the dispatch entirely and provide compile-time exhaustiveness for future intent types.

Checklist

Extract the intent-to-resolved-path translation that was duplicated
verbatim in PolarisAuthorizerImpl and RangerPolarisAuthorizer into a
shared AuthorizationIntentResolver in polaris-core. Each authorizer
keeps its own operation semantics, rooting decision, terminal
authorization call, and exception handling.

Behavior-preserving; covered by the existing authorization test suites
plus a new AuthorizationIntentResolverTest.

Fixes apache#5485
@ZephyrYWZhou

Copy link
Copy Markdown
Contributor Author

The CI is green, kindly tagging @flyrain and @sungwy for review as the primary driver of the authorize(state, request) migration.

flyrain
flyrain previously approved these changes Sep 10, 2026

@flyrain flyrain 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.

+1 Thanks for the refactor, @ZephyrYWZhou !

prependRootContainer
? List.of(resolutionManifest.getResolvedRootContainerEntityAsPath())
: null;
resolvedSecondaries = null;

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.

nit: this line isn't necessary

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks @flyrain! Good catch on this and let me address this.

List.of(
getResolvedSecurable(
resolutionManifest, singleTargetIntent.target(), prependRootContainer));
resolvedSecondaries = null;

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.

same here

Comment on lines +784 to +785
resolvedIntent.targets(),
resolvedIntent.secondaries());

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.

I would pass it down to the method findMissingPrivileges(), given that the structure of targets and secondaries could change in the future. Not a blocker. I'd prefer to refactor further in a followup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sounds good that's a good call-out and I agree. I'll add a note on #5485 so this follow-up refactor (passing the resolved targets/secondaries down into findMissingPrivileges()) isn't missed. I'll start on it right after this PR merges.

@github-project-automation github-project-automation Bot moved this from PRs In Progress to Ready to merge in Basic Kanban Board Sep 10, 2026
…olver

Initialize resolvedSecondaries to null at its declaration and drop the
two redundant explicit null assignments in the targetless and
single-target branches, per review feedback.
@ZephyrYWZhou

Copy link
Copy Markdown
Contributor Author

Thanks for the review and the insight @flyrain!

For the two nits, I've pushed a follow-up commit that addresses both. For the findMissingPrivileges() refactor, I've added a note to the tracking issue (#5485) so it won't be missed - I'll pick it up right after this PR merges.

Since the new commit dismissed your earlier approval, could you take another look when you get a chance? And once it's approved, I'd appreciate it if you could help merge it - I don't appear to have merge permission yet. Thanks again!

@flyrain
flyrain merged commit 2e6bb12 into apache:main Sep 10, 2026
24 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to merge to Done in Basic Kanban Board Sep 10, 2026
@flyrain

flyrain commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Thanks @ZephyrYWZhou for the contribution!

@ZephyrYWZhou

Copy link
Copy Markdown
Contributor Author

Thanks @flyrain! Will pick up the recommended refactor next.

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.

Deduplicate intent-to-resolved-path resolution shared by PolarisAuthorizerImpl and RangerPolarisAuthorizer

2 participants