Skip to content

Commit 96f1459

Browse files
authored
Refactor Authenticator and PolarisPrincipal (#2307)
The main goal of this change is to facilitate future integration of federated principals: - `AuthenticatedPolarisPrincipal` becomes an interface `PolarisPrincipal`, as the original class leaks implementation details (references to `PrincipalEntity` and thus to the storage layer). The new interface does not reference the storage layer. This is one step further towards easy pluggability of authentication in Polaris. - The `Authenticator.authenticate()` method does not return an `Optional` anymore, as this was ambiguous (returning `Optional.empty()` vs throwing `NotAuthorizedException`). - Also the `Authenticator` interface is not generic anymore. This was an artifact of times when there were two kinds of `Authenticators` in Polaris (one for internal auth, the other for external) and is not necessary anymore.
1 parent 5a7686b commit 96f1459

File tree

62 files changed

+460
-426
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+460
-426
lines changed

polaris-core/src/main/java/org/apache/polaris/core/auth/AuthenticatedPolarisPrincipal.java

Lines changed: 0 additions & 63 deletions
This file was deleted.

polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@
2929
public interface PolarisAuthorizer {
3030

3131
void authorizeOrThrow(
32-
@Nonnull AuthenticatedPolarisPrincipal authenticatedPrincipal,
32+
@Nonnull PolarisPrincipal polarisPrincipal,
3333
@Nonnull Set<PolarisBaseEntity> activatedEntities,
3434
@Nonnull PolarisAuthorizableOperation authzOp,
3535
@Nullable PolarisResolvedPathWrapper target,
3636
@Nullable PolarisResolvedPathWrapper secondary);
3737

3838
void authorizeOrThrow(
39-
@Nonnull AuthenticatedPolarisPrincipal authenticatedPrincipal,
39+
@Nonnull PolarisPrincipal polarisPrincipal,
4040
@Nonnull Set<PolarisBaseEntity> activatedEntities,
4141
@Nonnull PolarisAuthorizableOperation authzOp,
4242
@Nullable List<PolarisResolvedPathWrapper> targets,

polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -558,13 +558,13 @@ public boolean matchesOrIsSubsumedBy(
558558

559559
@Override
560560
public void authorizeOrThrow(
561-
@Nonnull AuthenticatedPolarisPrincipal authenticatedPrincipal,
561+
@Nonnull PolarisPrincipal polarisPrincipal,
562562
@Nonnull Set<PolarisBaseEntity> activatedEntities,
563563
@Nonnull PolarisAuthorizableOperation authzOp,
564564
@Nullable PolarisResolvedPathWrapper target,
565565
@Nullable PolarisResolvedPathWrapper secondary) {
566566
authorizeOrThrow(
567-
authenticatedPrincipal,
567+
polarisPrincipal,
568568
activatedEntities,
569569
authzOp,
570570
target == null ? null : List.of(target),
@@ -573,7 +573,7 @@ public void authorizeOrThrow(
573573

574574
@Override
575575
public void authorizeOrThrow(
576-
@Nonnull AuthenticatedPolarisPrincipal authenticatedPrincipal,
576+
@Nonnull PolarisPrincipal polarisPrincipal,
577577
@Nonnull Set<PolarisBaseEntity> activatedEntities,
578578
@Nonnull PolarisAuthorizableOperation authzOp,
579579
@Nullable List<PolarisResolvedPathWrapper> targets,
@@ -582,20 +582,18 @@ public void authorizeOrThrow(
582582
realmConfig.getConfig(
583583
FeatureConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING);
584584
if (enforceCredentialRotationRequiredState
585-
&& authenticatedPrincipal
586-
.getPrincipalEntity()
587-
.getInternalPropertiesAsMap()
585+
&& polarisPrincipal
586+
.getProperties()
588587
.containsKey(PolarisEntityConstants.PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_STATE)
589588
&& authzOp != PolarisAuthorizableOperation.ROTATE_CREDENTIALS) {
590589
throw new ForbiddenException(
591590
"Principal '%s' is not authorized for op %s due to PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_STATE",
592-
authenticatedPrincipal.getName(), authzOp);
593-
} else if (!isAuthorized(
594-
authenticatedPrincipal, activatedEntities, authzOp, targets, secondaries)) {
591+
polarisPrincipal.getName(), authzOp);
592+
} else if (!isAuthorized(polarisPrincipal, activatedEntities, authzOp, targets, secondaries)) {
595593
throw new ForbiddenException(
596594
"Principal '%s' with activated PrincipalRoles '%s' and activated grants via '%s' is not authorized for op %s",
597-
authenticatedPrincipal.getName(),
598-
authenticatedPrincipal.getActivatedPrincipalRoleNames(),
595+
polarisPrincipal.getName(),
596+
polarisPrincipal.getRoles(),
599597
activatedEntities.stream().map(PolarisEntityCore::getName).collect(Collectors.toSet()),
600598
authzOp);
601599
}
@@ -607,21 +605,21 @@ public void authorizeOrThrow(
607605
* the operation.
608606
*/
609607
public boolean isAuthorized(
610-
@Nonnull AuthenticatedPolarisPrincipal authenticatedPolarisPrincipal,
608+
@Nonnull PolarisPrincipal polarisPrincipal,
611609
@Nonnull Set<PolarisBaseEntity> activatedEntities,
612610
@Nonnull PolarisAuthorizableOperation authzOp,
613611
@Nullable PolarisResolvedPathWrapper target,
614612
@Nullable PolarisResolvedPathWrapper secondary) {
615613
return isAuthorized(
616-
authenticatedPolarisPrincipal,
614+
polarisPrincipal,
617615
activatedEntities,
618616
authzOp,
619617
target == null ? null : List.of(target),
620618
secondary == null ? null : List.of(secondary));
621619
}
622620

623621
public boolean isAuthorized(
624-
@Nonnull AuthenticatedPolarisPrincipal authenticatedPolarisPrincipal,
622+
@Nonnull PolarisPrincipal polarisPrincipal,
625623
@Nonnull Set<PolarisBaseEntity> activatedEntities,
626624
@Nonnull PolarisAuthorizableOperation authzOp,
627625
@Nullable List<PolarisResolvedPathWrapper> targets,
@@ -636,8 +634,7 @@ public boolean isAuthorized(
636634
authzOp,
637635
privilegeOnTarget);
638636
for (PolarisResolvedPathWrapper target : targets) {
639-
if (!hasTransitivePrivilege(
640-
authenticatedPolarisPrincipal, entityIdSet, privilegeOnTarget, target)) {
637+
if (!hasTransitivePrivilege(polarisPrincipal, entityIdSet, privilegeOnTarget, target)) {
641638
// TODO: Collect missing privileges to report all at the end and/or return to code
642639
// that throws NotAuthorizedException for more useful messages.
643640
return false;
@@ -652,7 +649,7 @@ public boolean isAuthorized(
652649
privilegeOnSecondary);
653650
for (PolarisResolvedPathWrapper secondary : secondaries) {
654651
if (!hasTransitivePrivilege(
655-
authenticatedPolarisPrincipal, entityIdSet, privilegeOnSecondary, secondary)) {
652+
polarisPrincipal, entityIdSet, privilegeOnSecondary, secondary)) {
656653
return false;
657654
}
658655
}
@@ -670,7 +667,7 @@ public boolean isAuthorized(
670667
* errors/exceptions.
671668
*/
672669
public boolean hasTransitivePrivilege(
673-
@Nonnull AuthenticatedPolarisPrincipal authenticatedPolarisPrincipal,
670+
@Nonnull PolarisPrincipal polarisPrincipal,
674671
Set<Long> activatedGranteeIds,
675672
PolarisPrivilege desiredPrivilege,
676673
PolarisResolvedPathWrapper resolvedPath) {
@@ -693,7 +690,7 @@ public boolean hasTransitivePrivilege(
693690
desiredPrivilege,
694691
grantRecord,
695692
resolvedSecurableEntity,
696-
authenticatedPolarisPrincipal.getName(),
693+
polarisPrincipal.getName(),
697694
activatedGranteeIds);
698695
return true;
699696
}
@@ -704,7 +701,7 @@ public boolean hasTransitivePrivilege(
704701
LOGGER.debug(
705702
"Failed to satisfy privilege {} for principalName {} on resolvedPath {}",
706703
desiredPrivilege,
707-
authenticatedPolarisPrincipal.getName(),
704+
polarisPrincipal.getName(),
708705
resolvedPath);
709706
return false;
710707
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.core.auth;
20+
21+
import java.security.Principal;
22+
import java.util.Map;
23+
import java.util.Set;
24+
import org.apache.polaris.core.entity.PrincipalEntity;
25+
import org.apache.polaris.immutables.PolarisImmutable;
26+
27+
/** Represents a {@link Principal} in the Polaris system. */
28+
@PolarisImmutable
29+
public interface PolarisPrincipal extends Principal {
30+
31+
/**
32+
* Creates a new instance of {@link PolarisPrincipal} from the given {@link PrincipalEntity} and
33+
* roles.
34+
*
35+
* <p>The created principal will have the same ID and name as the {@link PrincipalEntity}, and its
36+
* properties will be derived from the internal properties of the entity.
37+
*
38+
* @param principalEntity the principal entity representing the user or service
39+
* @param roles the set of roles associated with the principal
40+
*/
41+
static PolarisPrincipal of(PrincipalEntity principalEntity, Set<String> roles) {
42+
return of(
43+
principalEntity.getId(),
44+
principalEntity.getName(),
45+
principalEntity.getInternalPropertiesAsMap(),
46+
roles);
47+
}
48+
49+
/**
50+
* Creates a new instance of {@link PolarisPrincipal} with the specified ID, name, roles, and
51+
* properties.
52+
*
53+
* @param id the unique identifier of the principal
54+
* @param name the name of the principal
55+
* @param properties additional properties associated with the principal
56+
* @param roles the set of roles associated with the principal
57+
*/
58+
static PolarisPrincipal of(
59+
long id, String name, Map<String, String> properties, Set<String> roles) {
60+
return ImmutablePolarisPrincipal.builder()
61+
.id(id)
62+
.name(name)
63+
.properties(properties)
64+
.roles(roles)
65+
.build();
66+
}
67+
68+
/**
69+
* Returns the unique identifier of the principal.
70+
*
71+
* <p>This identifier is used to uniquely identify the principal within a Polaris realm.
72+
*/
73+
long getId();
74+
75+
/**
76+
* Returns the set of activated principal role names. Activated role names are the roles that were
77+
* explicitly requested by the client when authenticating, through JWT claims or other means. It
78+
* may be a subset of the roles that the principal has in the system.
79+
*/
80+
Set<String> getRoles();
81+
82+
/**
83+
* Returns the properties of this principal.
84+
*
85+
* <p>Properties are key-value pairs that provide additional information about the principal, such
86+
* as permissions, preferences, or other metadata.
87+
*/
88+
Map<String, String> getProperties();
89+
}

polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import java.util.Map;
3030
import java.util.Set;
3131
import org.apache.polaris.core.PolarisDiagnostics;
32-
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
32+
import org.apache.polaris.core.auth.PolarisPrincipal;
3333
import org.apache.polaris.core.context.CallContext;
3434
import org.apache.polaris.core.entity.PolarisBaseEntity;
3535
import org.apache.polaris.core.entity.PolarisEntityConstants;
@@ -83,7 +83,7 @@ public PolarisResolutionManifest(
8383
this.diagnostics.checkNotNull(securityContext, "null_security_context_for_resolution_manifest");
8484
this.securityContext = securityContext;
8585
diagnostics.check(
86-
securityContext.getUserPrincipal() instanceof AuthenticatedPolarisPrincipal,
86+
securityContext.getUserPrincipal() instanceof PolarisPrincipal,
8787
"invalid_principal_type_for_resolution_manifest",
8888
"principal={}",
8989
securityContext.getUserPrincipal());

polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolver.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import java.util.stream.Collectors;
3333
import org.apache.polaris.core.PolarisCallContext;
3434
import org.apache.polaris.core.PolarisDiagnostics;
35-
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
35+
import org.apache.polaris.core.auth.PolarisPrincipal;
3636
import org.apache.polaris.core.entity.CatalogEntity;
3737
import org.apache.polaris.core.entity.PolarisBaseEntity;
3838
import org.apache.polaris.core.entity.PolarisChangeTrackingVersions;
@@ -68,7 +68,7 @@ public class Resolver {
6868
@Nullable private final EntityCache cache;
6969

7070
// the id of the principal making the call or 0 if unknown
71-
private final @Nonnull AuthenticatedPolarisPrincipal polarisPrincipal;
71+
private final @Nonnull PolarisPrincipal polarisPrincipal;
7272
private final @Nonnull SecurityContext securityContext;
7373

7474
// reference catalog name for name resolution
@@ -116,7 +116,7 @@ public class Resolver {
116116
*
117117
* @param polarisCallContext the polaris call context
118118
* @param polarisMetaStoreManager meta store manager
119-
* @param securityContext The {@link AuthenticatedPolarisPrincipal} for the current request
119+
* @param securityContext The {@link SecurityContext} for the current request
120120
* @param cache shared entity cache
121121
* @param referenceCatalogName if not null, specifies the name of the reference catalog. The
122122
* reference catalog is the catalog used to resolve catalog roles and catalog path. Also, if a
@@ -147,12 +147,12 @@ public Resolver(
147147
this.diagnostics.checkNotNull(
148148
securityContext.getUserPrincipal(), "principal_must_be_specified");
149149
this.diagnostics.check(
150-
securityContext.getUserPrincipal() instanceof AuthenticatedPolarisPrincipal,
150+
securityContext.getUserPrincipal() instanceof PolarisPrincipal,
151151
"unexpected_principal_type",
152152
"class={}",
153153
securityContext.getUserPrincipal().getClass().getName());
154154

155-
this.polarisPrincipal = (AuthenticatedPolarisPrincipal) securityContext.getUserPrincipal();
155+
this.polarisPrincipal = (PolarisPrincipal) securityContext.getUserPrincipal();
156156
// paths to resolve
157157
this.pathsToResolve = new ArrayList<>();
158158
this.resolvedPaths = new ArrayList<>();
@@ -758,7 +758,7 @@ private ResolverStatus resolveCallerPrincipalAndPrincipalRoles(
758758
toValidate,
759759
PolarisEntityType.PRINCIPAL,
760760
PolarisEntityConstants.getNullId(),
761-
polarisPrincipal.getPrincipalEntity().getId());
761+
polarisPrincipal.getId());
762762

763763
// if the principal was not found, we can end right there
764764
if (this.resolvedCallerPrincipal == null
@@ -768,10 +768,9 @@ private ResolverStatus resolveCallerPrincipalAndPrincipalRoles(
768768

769769
// activate all principal roles specified in the authenticated principal
770770
resolvedCallerPrincipalRoles =
771-
this.polarisPrincipal.getActivatedPrincipalRoleNames().isEmpty()
771+
this.polarisPrincipal.getRoles().isEmpty()
772772
? resolveAllPrincipalRoles(toValidate, resolvedCallerPrincipal)
773-
: resolvePrincipalRolesByName(
774-
toValidate, this.polarisPrincipal.getActivatedPrincipalRoleNames());
773+
: resolvePrincipalRolesByName(toValidate, this.polarisPrincipal.getRoles());
775774

776775
// total success
777776
return new ResolverStatus(ResolverStatus.StatusEnum.SUCCESS);

polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/BaseResolverTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import java.util.Set;
3232
import java.util.stream.Collectors;
3333
import org.apache.polaris.core.PolarisCallContext;
34-
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
34+
import org.apache.polaris.core.auth.PolarisPrincipal;
3535
import org.apache.polaris.core.entity.PolarisBaseEntity;
3636
import org.apache.polaris.core.entity.PolarisEntityCore;
3737
import org.apache.polaris.core.entity.PolarisEntitySubType;
@@ -481,8 +481,8 @@ private Resolver allocateResolver(
481481
.filter(Optional::isPresent)
482482
.map(Optional::get)
483483
.collect(Collectors.toList()));
484-
AuthenticatedPolarisPrincipal authenticatedPrincipal =
485-
new AuthenticatedPolarisPrincipal(
484+
PolarisPrincipal authenticatedPrincipal =
485+
PolarisPrincipal.of(
486486
PrincipalEntity.of(P1), Optional.ofNullable(principalRolesScope).orElse(Set.of()));
487487
return new Resolver(
488488
callCtx(),

0 commit comments

Comments
 (0)