Deduplicate intent-to-resolved-path resolution across authorizers - #5486
Conversation
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
flyrain
left a comment
There was a problem hiding this comment.
+1 Thanks for the refactor, @ZephyrYWZhou !
| prependRootContainer | ||
| ? List.of(resolutionManifest.getResolvedRootContainerEntityAsPath()) | ||
| : null; | ||
| resolvedSecondaries = null; |
There was a problem hiding this comment.
nit: this line isn't necessary
There was a problem hiding this comment.
Thanks @flyrain! Good catch on this and let me address this.
| List.of( | ||
| getResolvedSecurable( | ||
| resolutionManifest, singleTargetIntent.target(), prependRootContainer)); | ||
| resolvedSecondaries = null; |
| resolvedIntent.targets(), | ||
| resolvedIntent.secondaries()); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
…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.
|
Thanks for the review and the insight @flyrain! For the two nits, I've pushed a follow-up commit that addresses both. For the 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! |
|
Thanks @ZephyrYWZhou for the contribution! |
|
Thanks @flyrain! Will pick up the recommended refactor next. |
Summary
Follow-up to #5194 (which routed the
CatalogHandler/PolarisAdminServicecall sites throughauthorize(AuthorizationState, AuthorizationRequest)), as discussed on #5170.After #5194, each
PolarisAuthorizerimplementation re-derives the securables it needs from anAuthorizationRequest's intents. The mechanical mapping from anAuthorizationIntentto 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
AuthorizationIntenttypes plus identical privategetResolvedSecurable(...)andgetPathNamesWithinCatalog(...)helpers. This is correctness-sensitive and drift-prone — for example, theCATALOGbranch ingetResolvedSecurablehad 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
AuthorizationIntentResolverinorg.apache.polaris.core.auth, so both authorizers (and any future resolved-path authorizer) share one implementation.Changes
AuthorizationIntentResolver(polaris-core):resolve(manifest, intent, prependRootContainer)returns aResolvedIntent(targets, secondaries). It holds the per-intent dispatch,getResolvedSecurable(reference-catalog / top-level / path), andgetPathNamesWithinCatalog.PolarisAuthorizerImplandRangerPolarisAuthorizer:authorizeIntentnow calls the shared resolver; the duplicated private helpers are removed. Each authorizer keeps its own operation-semantics / rooting decision (passing its ownprependRootContainer), its terminal decision call (authorizeRbacOrThrow/authorizeRangerOrThrow), and its exception handling.OpaPolarisAuthorizeris unchanged — it maps intents to its ownResourceEntitymodel 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
trythat translates aForbiddenExceptioninto a decision.Testing
AuthorizationIntentResolverTestcovering all seven intent types, the three securable-resolution branches (reference catalog / top-level / path), the rooted vs. non-rooted targetless cases, and the unresolvable-securableIllegalStateException.PolarisAuthorizerImplTest, the runtime-service catalog handler authz tests (IcebergCatalogHandlerAuthzTest,IcebergCatalogHandlerFineGrainedDisabledTest,PolicyCatalogHandlerAuthzTest,PolarisGenericTableCatalogHandlerAuthzTest), the Ranger unit tests +intTest, and the OPA tests../gradlew :polaris-core:checkpasses (Spotless + Checkstyle + tests).Note
polaris-corecompiles with--release 17, so the resolver uses anif/else ifchain rather than a Java 21 patternswitch. A natural follow-up (noted in #5485) is to let eachAuthorizationIntentexpose its own securables, which would remove the dispatch entirely and provide compile-time exhaustiveness for future intent types.Checklist
AuthorizationIntentResolverTest; existing authz suites pass