Skip to content

Add support for federated principal and role with block for manual role assignment #1353

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

collado-mike
Copy link
Contributor

First PR to begin implementing the design outlined in https://docs.google.com/document/d/15_3ZiRB6Lhzw0nxij341QUdxEIyFGTrI9_18bFIyJVo/edit?tab=t.0#heading=h.cu1a1acu4lc5 .

Implements #441

Adds a FEDERATED_ENTITY property, which can be defined in Principals and PrincipalRoles. FEDERATED Principals cannot be created via the API, but FEDERATED roles can be. This allows for creating roles which can be assigned privileges, while actual principal creation is delegated to the JIT creation mechanism (not in this PR).

@@ -182,6 +182,12 @@ public static List<NameAndId> toNameAndIdList(List<EntityNameLookupRecord> entit
.orElse(null);
}

public static boolean isFederated(PolarisBaseEntity entity) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If only a subset of entity types support this, I don't think it should be present in the base type PolarisEntity

Copy link
Contributor

Choose a reason for hiding this comment

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

I tend to agree with @eric-maynard 's point here

return principalRole;
return new PrincipalRole(
getName(),
PolarisEntity.isFederated(this),
Copy link
Contributor

Choose a reason for hiding this comment

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

If we have to add an argument, it's best to add it at the end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Normally, I'd agree, but here I am trying to stick to the convention of keeping the entity-specific args at the front and the general, id/created/updated-at timestamps at the end. That's the convention that every entity type follows.

Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense, it sounds like this folds into another comment above

@@ -65,6 +65,13 @@ public Builder(PrincipalRoleEntity original) {
super(original);
}

public Builder setFederated(Boolean isFederated) {
if (isFederated != null && isFederated) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This logic doesn't look right to me. This entity would come out federated:

new PolarisEntity.Builder()
. . .
.setFederated(true)
.setFederated(false)
.build()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the idea is to avoid adding any entry for non-federated identities. I don't think the logic snippet you posted is reasonable or should be supported. I'd be in favor of an IllegalStateException if someone typed that code. Happy to add that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why should it not be supported? It sounds like you're saying that .setFederated(false) is never a valid call, in which case setFederated shouldn't take a boolean arg. Perhaps you are even looking for something like buildFederated?

Comment on lines +252 to +276
public void testCannotAddFederatedPrincipalToNonFederatedRole() {
PolarisMetaStoreManager metaStoreManager = setupMetaStoreManager();
PolarisCallContext callContext = setupCallContext(metaStoreManager);
PolarisAdminService polarisAdminService =
setupPolarisAdminService(metaStoreManager, callContext);

PrincipalEntity federatedPrincipal =
createPrincipal(metaStoreManager, callContext, "federated_id", true);
metaStoreManager.createPrincipal(callContext, federatedPrincipal);

PrincipalRoleEntity nonFederatedRole =
createRole(metaStoreManager, callContext, "non_federated_role", false);
EntityResult result =
metaStoreManager.createEntityIfNotExists(callContext, null, nonFederatedRole);
assertThat(result.isSuccess()).isTrue();

assertThatThrownBy(
() ->
polarisAdminService.assignPrincipalRole(
federatedPrincipal.getName(), nonFederatedRole.getName()))
.isInstanceOf(ValidationException.class);
}

@Test
public void testCannotAddNonFederatedPrincipalToFederatedRole() {
Copy link
Contributor

Choose a reason for hiding this comment

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

It could make sense to use a parameterized test here. IIUC there is basically a table like the following:

Principal Type Principal Role Type Can Attach
Non-Federated Non-Federated Yes
Non-Federated Federated No
Federated Non-Federated No
Federated Federated No

Copy link
Contributor

Choose a reason for hiding this comment

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

The restrictions proposed in the table above make sense to me 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll skip the first row because it's already covered exhaustively elsewhere. Personally, I tend to shy away from parameterized tests, as I think they're overused and oftentimes obfuscate the test code with a bunch of if/else statements, but I'm ok adding it here.

@@ -871,6 +873,42 @@ public void testCreatePrincipalAndRotateCredentials() {
// rotation that makes the old secret fall off retention.
}

@Test
public void testCreateFederatedPrincipalFails() {
Copy link
Contributor

@eric-maynard eric-maynard Apr 11, 2025

Choose a reason for hiding this comment

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

Given how different the semantics for federated principal (role)s appear to be, I wonder if we should consider a subtype? It looks like most APIs are valid for nonfederated principal (role)s are not valid for federated ones. This is reminiscent of EXTERNAL catalogs, but maybe more extreme

Copy link
Contributor Author

Choose a reason for hiding this comment

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

External catalog has a separate type because it has an extra field. Federated identity types don't have any additional fields at this point, so I'd prefer not to add an extra type. If extra fields end up getting added, then it would make sense, but for now deserializing wouldn't even be able to distinguish what type it should deserialized to.

@@ -1089,6 +1089,10 @@ components:
clientId:
type: string
description: The output-only OAuth clientId associated with this principal if applicable
federated:
Copy link
Contributor

Choose a reason for hiding this comment

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

This part LGTM, but it should go to a ML vote

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to ML discussion.

What's the use case for exposing federated principal in the Polaris Management API? Acting as a proxy in front of an IdP sounds questionable to me.

Copy link
Contributor Author

@collado-mike collado-mike Apr 15, 2025

Choose a reason for hiding this comment

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

The main case is to support adding federated roles so that privileges can be defined. Per the original design doc, federated identities are created on the fly when a user logs in, but if we don't allow creation of federated roles, we can't define any privileges for those users until they've logged in. That makes things hard for the admins, IMO.

For Principals, I don't think federated principals should be created via the API, for the same reasoning you suggest, but we should be able to return federated principals and report that they are federated. To make that clearer, maybe I can update the spec to return one of two types, but to only allow creation of one type.

@@ -771,6 +771,9 @@ public PrincipalWithCredentials createPrincipal(PolarisEntity entity) {
PolarisAuthorizableOperation op = PolarisAuthorizableOperation.CREATE_PRINCIPAL;
authorizeBasicRootOperationOrThrow(op);

if (PolarisEntity.isFederated(entity)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we expose "federated" as a property settable via java API only to add validation that it cannot be set?

Also, REST API (which is the only caller of this method) does not allow setting the "federated" property, as far as I can tell, because it does not set internal properties on the PrincipalEntity 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

to clarify: My thinking was that the fact that make those entities "not creatable" via the Admin API is not the presence of a specific property (i.e. "federated") but the fact that they are managed in a different way (via IdP integrations, I believe).

Can this be made more explicit in code? I do not have a solid suggestion, but I wonder if some sort of a "manager" class might be suitable here. That "manager" would then make the decision about allowing or not allowing changes via Admin API. WDYT?

@@ -1089,6 +1089,10 @@ components:
clientId:
type: string
description: The output-only OAuth clientId associated with this principal if applicable
federated:
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to ML discussion.

What's the use case for exposing federated principal in the Polaris Management API? Acting as a proxy in front of an IdP sounds questionable to me.

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.

3 participants