-
Notifications
You must be signed in to change notification settings - Fork 332
Prefer PolarisPrincipal.getRoles in Resolver #2925
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -28,6 +28,7 @@ | |||||
| import java.util.Iterator; | ||||||
| import java.util.List; | ||||||
| import java.util.Map; | ||||||
| import java.util.Objects; | ||||||
| import java.util.Set; | ||||||
| import java.util.stream.Collectors; | ||||||
| import org.apache.polaris.core.PolarisCallContext; | ||||||
|
|
@@ -69,7 +70,6 @@ public class Resolver { | |||||
|
|
||||||
| // the id of the principal making the call or 0 if unknown | ||||||
| private final @Nonnull PolarisPrincipal polarisPrincipal; | ||||||
| private final @Nonnull SecurityContext securityContext; | ||||||
|
|
||||||
| // reference catalog name for name resolution | ||||||
| private final String referenceCatalogName; | ||||||
|
|
@@ -137,7 +137,6 @@ public Resolver( | |||||
| this.diagnostics = diagnostics; | ||||||
| this.polarisMetaStoreManager = polarisMetaStoreManager; | ||||||
| this.cache = cache; | ||||||
| this.securityContext = securityContext; | ||||||
| this.referenceCatalogName = referenceCatalogName; | ||||||
|
|
||||||
| // validate inputs | ||||||
|
|
@@ -467,11 +466,11 @@ private void updateResolved() { | |||||
|
|
||||||
| // update all principal roles with latest | ||||||
| if (!this.resolvedCallerPrincipalRoles.isEmpty()) { | ||||||
| List<ResolvedPolarisEntity> refreshedResolvedCallerPrincipalRoles = | ||||||
| new ArrayList<>(this.resolvedCallerPrincipalRoles.size()); | ||||||
| this.resolvedCallerPrincipalRoles.forEach( | ||||||
| ce -> refreshedResolvedCallerPrincipalRoles.add(this.getFreshlyResolved(ce))); | ||||||
| this.resolvedCallerPrincipalRoles = refreshedResolvedCallerPrincipalRoles; | ||||||
| this.resolvedCallerPrincipalRoles = | ||||||
| resolvedCallerPrincipalRoles.stream() | ||||||
| .map(this::getFreshlyResolved) | ||||||
| .filter(Objects::nonNull) | ||||||
| .collect(Collectors.toList()); | ||||||
| } | ||||||
|
|
||||||
| // update referenced catalog | ||||||
|
|
@@ -776,13 +775,11 @@ private ResolverStatus resolveCallerPrincipalAndPrincipalRoles( | |||||
| /** | ||||||
| * Resolve all principal roles that the principal has grants for | ||||||
| * | ||||||
| * @param toValidate | ||||||
| * @param resolvedCallerPrincipal1 | ||||||
| * @return the list of resolved principal roles the principal has grants for | ||||||
| */ | ||||||
| private List<ResolvedPolarisEntity> resolveAllPrincipalRoles( | ||||||
| List<ResolvedPolarisEntity> toValidate, ResolvedPolarisEntity resolvedCallerPrincipal1) { | ||||||
| return resolvedCallerPrincipal1.getGrantRecordsAsGrantee().stream() | ||||||
| List<ResolvedPolarisEntity> toValidate, ResolvedPolarisEntity callerPrincipal) { | ||||||
| return callerPrincipal.getGrantRecordsAsGrantee().stream() | ||||||
| .filter(gr -> gr.getPrivilegeCode() == PolarisPrivilege.PRINCIPAL_ROLE_USAGE.getCode()) | ||||||
| .map( | ||||||
| gr -> | ||||||
|
|
@@ -791,22 +788,22 @@ private List<ResolvedPolarisEntity> resolveAllPrincipalRoles( | |||||
| PolarisEntityType.PRINCIPAL_ROLE, | ||||||
| PolarisEntityConstants.getRootEntityId(), | ||||||
| gr.getSecurableId())) | ||||||
| .filter(Objects::nonNull) | ||||||
| .collect(Collectors.toList()); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Resolve the specified list of principal roles. The SecurityContext is used to determine whether | ||||||
| * the principal actually has the roles specified. | ||||||
| * Resolve the specified list of principal roles. The PolarisPrincipal is used to determine | ||||||
| * whether the principal actually has the roles specified. | ||||||
| * | ||||||
| * @param toValidate | ||||||
| * @param roleNames | ||||||
| * @return the filtered list of resolved principal roles | ||||||
| */ | ||||||
| private List<ResolvedPolarisEntity> resolvePrincipalRolesByName( | ||||||
| List<ResolvedPolarisEntity> toValidate, Set<String> roleNames) { | ||||||
| return roleNames.stream() | ||||||
| .filter(securityContext::isUserInRole) | ||||||
| .filter(roleName -> polarisPrincipal.getRoles().contains(roleName)) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that from the |
||||||
| .map(roleName -> resolveByName(toValidate, PolarisEntityType.PRINCIPAL_ROLE, roleName)) | ||||||
| .filter(Objects::nonNull) | ||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. polaris/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/BaseResolverTest.java Lines 182 to 183 in 2c77fbf
this test was failing with NPE without null filtering... tbh i am not sure i understand it... it's creating a Principal with a non-existing role... shouldnt the Resolver be unsuccessful in that case?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is generally the responsibility of the I do not think the resolver should fail in this case. |
||||||
| .collect(Collectors.toList()); | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think that there is something fishy with the fact that we re-resolve the principal entity and principal role entities here, at each pass.
The only authoritative source of truth for principal names and roles should be the
SecurityIdentityproduced during authentication.If the principal and/or principal roles are modified after the authentication by some other process, we could get in the awkward situation where we would fetch different entities from the database, causing a mismatch with what
SecurityIdentityexposes.IMHO, we should stop resolving principals and principal roles altogether here. That's a prerogative of the
Authenticator. This class should focus on resolving catalog roles and paths.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, @adutra ! I agree that roles should be established during authentication and not re-resolved after that.
However, I may be a pretty big change. IIRC, in case of Polaris-manages Principals the idea is that the state of permission assignments should be consistent with catalog data during request execution. This probably means that the roles entities used by the authenticator should be made available to the
Resolverin order to take their version numbers into account when traversing grant records.I think the change to use Principal role names in this PR is consistent with current code. Let's merge this PR and refactor more in follow-up PRs. WDYT?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure that works for me.
Maybe, but this needs more investigation imho.
I still think it should possible to avoid that. Looking at the
Resolver#resolveByName()method, we see that it ultimately callsPolarisMetaStoreManager#loadResolvedEntityByName()which returns the grant records for the entity. That creates a "snapshot" of [principal name + principal roles + catalog roles + grant records] that is consistent for a given pass, afaict (at least as consistent as it can be, given that the operation implies several reads that are not in the same transaction).