core: refactor PolarisSecurable with PathSegment#4016
core: refactor PolarisSecurable with PathSegment#4016sungwy wants to merge 4 commits intoapache:mainfrom
PolarisSecurable with PathSegment#4016Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors PolarisSecurable to represent authorization targets as an ordered list of typed path segments, and updates authorization request logic/tests to use the new representation and validate basic hierarchy rules.
Changes:
- Introduces
PathSegmentand changesPolarisSecurableto storeList<PathSegment>with derivedgetLeaf()/getParents()plus validation. - Updates
AuthorizationRequest.containsTypeto use the securable leaf segment type. - Updates/adds unit tests to construct securables via
PathSegmentpaths and to assert validation behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisSecurable.java | Switches securable identity from (entityType, nameParts) to a fully-qualified pathSegments model with validation. |
| polaris-core/src/main/java/org/apache/polaris/core/auth/PathSegment.java | Adds a record to represent a single (entityType, name) segment in a resource path. |
| polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationRequest.java | Updates type detection to look at the leaf segment’s entity type. |
| polaris-core/src/test/java/org/apache/polaris/core/auth/AuthorizationRequestTest.java | Migrates tests to the new PathSegment-based PolarisSecurable.of(...) API and adds a validation test. |
| polaris-core/src/test/java/org/apache/polaris/core/auth/PolarisSecurableTest.java | Adds unit tests for getLeaf()/getParents() and invalid hierarchy validation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisSecurable.java
Outdated
Show resolved
Hide resolved
polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisSecurable.java
Outdated
Show resolved
Hide resolved
polaris-core/src/test/java/org/apache/polaris/core/auth/PolarisSecurableTest.java
Show resolved
Hide resolved
| List<PathSegment> pathSegments = getPathSegments(); | ||
| return pathSegments.get(pathSegments.size() - 1); |
There was a problem hiding this comment.
| List<PathSegment> pathSegments = getPathSegments(); | |
| return pathSegments.get(pathSegments.size() - 1); | |
| return getPathSegments().getLast(); |
There was a problem hiding this comment.
Looks like we are still compiling at 17 with polaris-client:
Maybe we can keep this PR with the current syntax and raise a DISCUSS thread to assess moving polaris-client compilation to 21?
There was a problem hiding this comment.
Java 21 discussion is on-going (although slowly 😅 ): https://lists.apache.org/thread/v8ghm3k5nh9xp9x0xdo2kknk5ph0rnmb
polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisSecurable.java
Outdated
Show resolved
Hide resolved
| for (PathSegment parent : getParents()) { | ||
| Preconditions.checkState( | ||
| parent.entityType() != PolarisEntityType.ROOT, | ||
| "PathSegments must not include ROOT for securable leaf=%s", |
There was a problem hiding this comment.
I'm not following, shouldn't the highest ancestor path segments be allowed to be ROOT for some securables?
There was a problem hiding this comment.
I'm rooting for removing ROOT in the parent chain in PolarisSecurable, because including ROOT as a parent is a strange RBAC specific semantic. I don't think external PolarisAuthorizer implementations like OPA or Ranger would care about a ROOT parent.
There was a problem hiding this comment.
I agree that ROOT should not be a factor in authZ decisions in general. Existing native RBAC uses it, which is fine... historical reasons.
It's like appending . to DNS names - it makes them formally absolute, but it makes no practical difference in most cases 😅
Let's standardize on starting paths at the catalog in AuthZ SPI.
| "PathSegments must not include ROOT for securable leaf=%s", | ||
| getLeaf()); | ||
| } | ||
| if (getLeaf().entityType().isTopLevel()) { |
There was a problem hiding this comment.
I think we are missing unit tests for some of the validation failures.
There was a problem hiding this comment.
Happy to add more coverage
polaris-core/src/test/java/org/apache/polaris/core/auth/AuthorizationRequestTest.java
Outdated
Show resolved
Hide resolved
polaris-core/src/test/java/org/apache/polaris/core/auth/AuthorizationRequestTest.java
Outdated
Show resolved
Hide resolved
| default PathSegment getLeaf() { | ||
| List<PathSegment> pathSegments = getPathSegments(); | ||
| Preconditions.checkState( | ||
| !pathSegments.isEmpty(), "PathSegments must contain at least one segment"); |
There was a problem hiding this comment.
This check is a bit pointless, given the validate() method 🤔
There was a problem hiding this comment.
I agree, but I thought of just keeping this to make the error more descriptive in the future, if our API evolves... it shouldn't ever happen and PathSegments must contain at least one segment feels more descriptive than ArrayIndexOutOfBoundsException.
There was a problem hiding this comment.
well, I do not see how this check can ever fail 🤔 I believe the failure will happen in the validate() method and the object will never get constructed for this method to be called on 🤔
| Preconditions.checkState( | ||
| getPathSegments().get(0).entityType().isTopLevel(), | ||
| "PathSegments must start with a top-level entity"); | ||
| for (PathSegment parent : getParents()) { |
There was a problem hiding this comment.
Why check only parents? I guess ROOT is not meaningful as a leaf element either 🤔
There was a problem hiding this comment.
yeah I guess we could expand it to the leaf as well
| List.of( | ||
| AuthorizationTargetBinding.of( | ||
| PolarisSecurable.of(PolarisEntityType.PRINCIPAL, List.of("alice")), null))); | ||
| PolarisSecurable.of(new PathSegment(PolarisEntityType.PRINCIPAL, "alice")), |
There was a problem hiding this comment.
nit: maybe add a convenience .of() method for this? It looks like a common pattern.
There was a problem hiding this comment.
that's a nice neat trick :)
There was a problem hiding this comment.
hmm I thought about this a bit more, and I'm thinking of just keeping the plain constructors for now. I think it's explicit and consistent for securables with single PathSegment and multiple segments
| import org.apache.polaris.core.entity.PolarisEntityType; | ||
|
|
||
| /** One segment in a fully qualified resource path. */ | ||
| public record PathSegment(PolarisEntityType entityType, String name) { |
There was a problem hiding this comment.
nit: maybe add a convenience .pathSegment() builder method? Callers would be able to statically import it and have less verbose code.
While working on #3999 we realized that
PolarisSecurablerequires an Entity representation with its parent relationships and their correspondingEntityType.This PR introduces a new record type
PathSegmentthat hasnameandEntityTypethat represents a segment of the resource path, that can be ordered to construct aPolarisSecurable.This is not yet a breaking change, because a release hasn't been made since
PolarisSecurablewas introduced.Checklist
CHANGELOG.md(if needed)site/content/in-dev/unreleased(if needed)