adutra commented on code in PR #3681:
URL: https://github.com/apache/polaris/pull/3681#discussion_r2793117597
##########
runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java:
##########
@@ -170,6 +176,7 @@ public PolarisAdminService(
this.userSecretsManager = userSecretsManager;
this.serviceIdentityProvider = serviceIdentityProvider;
this.reservedProperties = reservedProperties;
+ this.authorizationState = authorizationState;
Review Comment:
Wondering: do we need to make this object a CDI request-scoped bean? I think
it could be instantiated by the handler manually, which would have the
advantage that we wouldn't need to expose setters in `AuthorizationState`.
That is, instead of:
```java
AuthorizationState authzContext = authorizationState();
authzContext.setResolutionManifest(resolutionManifest);
```
The handler would do:
```java
AuthorizationState authzContext = new
RequestAuthorizationState(resolutionManifest);
```
##########
polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationRequest.java:
##########
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.polaris.core.auth;
+
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import java.util.List;
+import java.util.Set;
+import org.apache.polaris.core.entity.PolarisEntityType;
+import org.apache.polaris.core.entity.PolarisPrivilege;
+
+/**
+ * Authorization request inputs for pre-authorization and core authorization.
+ *
+ * <p>This wrapper keeps authorization inputs together while preserving legacy
semantics.
+ */
+public final class AuthorizationRequest {
Review Comment:
nit: now that polaris-core is on Java 17, this class, as well as
`PolarisSecurable`, could be records. Alternatively, they could be annotated
with `@PolarisImmutable`.
##########
polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java:
##########
@@ -908,4 +913,73 @@ public boolean hasTransitivePrivilege(
resolvedPath);
return false;
}
+
+ @Override
+ public void preAuthorize(@Nonnull AuthorizationState ctx, @Nonnull
AuthorizationRequest request) {
+ PolarisResolutionManifest manifest = ctx.getResolutionManifest();
+ if (manifest.hasResolution()) {
+ return;
+ }
+
+ // Phase 2: preserve existing RBAC ordering by resolving the manifest
within preAuthorize.
+ manifest.resolveSelections(
+ EnumSet.of(
+ Resolvable.CALLER_PRINCIPAL,
+ Resolvable.CALLER_PRINCIPAL_ROLES,
+ Resolvable.CATALOG_ROLES,
+ Resolvable.REFERENCE_CATALOG,
+ Resolvable.REQUESTED_PATHS,
+ Resolvable.TOP_LEVEL_ENTITIES));
+ }
+
+ @Override
+ public void authorize(@Nonnull AuthorizationState ctx, @Nonnull
AuthorizationRequest request) {
+ PolarisResolutionManifest manifest = ctx.getResolutionManifest();
+ Set<PolarisBaseEntity> activatedEntities =
+ manifest == null ? Set.of() :
manifest.getAllActivatedCatalogRoleAndPrincipalRoles();
Review Comment:
nit: `manifest` cannot be null here.
##########
polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizer.java:
##########
@@ -27,14 +27,36 @@
/** Interface for invoking authorization checks. */
public interface PolarisAuthorizer {
+ /**
+ * Pre-authorization hook for resolving authorizer-specific inputs.
+ *
+ * <p>Implementations may resolve or validate any inputs needed to make an
authorization decision.
+ */
+ void preAuthorize(@Nonnull AuthorizationState ctx, @Nonnull
AuthorizationRequest request);
Review Comment:
Just to confirm: are the new methods still expected to throw on deny?
##########
polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizer.java:
##########
@@ -27,14 +27,36 @@
/** Interface for invoking authorization checks. */
public interface PolarisAuthorizer {
+ /**
+ * Pre-authorization hook for resolving authorizer-specific inputs.
+ *
+ * <p>Implementations may resolve or validate any inputs needed to make an
authorization decision.
+ */
+ void preAuthorize(@Nonnull AuthorizationState ctx, @Nonnull
AuthorizationRequest request);
+ /** Core authorization entry point for the new SPI. */
+ void authorize(@Nonnull AuthorizationState ctx, @Nonnull
AuthorizationRequest request);
+
+ /**
+ * Backwards-compatible external API that throws on deny for legacy call
sites.
+ *
+ * <p>Default implementation delegates to {@link
#authorize(AuthorizationState,
+ * AuthorizationRequest)}.
+ */
+ default void authorizeOrThrow(
Review Comment:
I'm confused: I don't see this method used anywhere, and (contrary to what
the javadocs say) it's a new method, not a legacy one.
##########
runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java:
##########
@@ -217,23 +258,23 @@ private static CatalogEntity getCatalogByName(
private static CatalogRoleEntity getCatalogRoleByName(
PolarisResolutionManifest resolutionManifest, String catalogRoleName) {
- return
Optional.ofNullable(resolutionManifest.getResolvedPath(catalogRoleName))
+ PolarisSecurable catalogRoleSecurable =
+ new PolarisSecurable(PolarisEntityType.CATALOG_ROLE,
List.of(catalogRoleName));
+ return
Optional.ofNullable(resolutionManifest.getResolvedPath(catalogRoleSecurable))
.map(PolarisResolvedPathWrapper::getRawLeafEntity)
.map(CatalogRoleEntity::of)
.orElseThrow(() -> new NotFoundException("CatalogRole %s not found",
catalogRoleName));
}
private void authorizeBasicRootOperationOrThrow(PolarisAuthorizableOperation
op) {
PolarisResolutionManifest resolutionManifest = newResolutionManifest(null);
- resolutionManifest.resolveAll();
- PolarisResolvedPathWrapper rootContainerWrapper =
- resolutionManifest.getResolvedRootContainerEntityAsPath();
- authorizer.authorizeOrThrow(
- polarisPrincipal,
- resolutionManifest.getAllActivatedPrincipalRoleEntities(),
- op,
- rootContainerWrapper,
- null /* secondary */);
+ AuthorizationState authzContext = authorizationState;
+ authzContext.setResolutionManifest(resolutionManifest);
+ authorizer.preAuthorize(authzContext, newAuthorizationRequest(op));
+ PolarisSecurable rootSecurable =
Review Comment:
nit: I think the root securable could be a public constant in
`PolarisSecurable`:
```java
public static final PolarisSecurable ROOT =
new PolarisSecurable(
PolarisEntityType.ROOT,
List.of(PolarisEntityConstants.getRootContainerName()));
```
##########
polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java:
##########
@@ -131,6 +147,29 @@ public void addPassthroughPath(ResolverPath path, Object
key) {
passthroughPaths.put(key, path);
}
+ /** Adds an alias key for a previously added path key. */
+ public void addPathAlias(Object existingKey, Object aliasKey) {
Review Comment:
This method looks a bit too low-level, could we avoid introducing it if we
change `addPath` to take several keys? E.g.
```java
public void addPath(ResolverPath path, Object... key) {
primaryResolver.addPath(path);
for (Object k : key) {
pathLookup.put(k, currentPathIndex);
}
addedPaths.add(path);
++currentPathIndex;
}
```
Then at call sites, instead of:
```java
resolutionManifest.addPath(
new ResolverPath(parentNamespaceSecurable.getNameParts(),
PolarisEntityType.NAMESPACE),
parentNamespaceSecurable);
resolutionManifest.addPathAlias(parentNamespaceSecurable,
parentNamespace);
```
We'd have:
```java
resolutionManifest.addPath(
new ResolverPath(parentNamespaceSecurable.getNameParts(),
PolarisEntityType.NAMESPACE),
parentNamespaceSecurable,
parentNamespace);
```
##########
polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java:
##########
@@ -179,12 +237,17 @@ public PolarisResolvedPathWrapper getResolvedPath(
*/
@Override
public PolarisResolvedPathWrapper getPassthroughResolvedPath(Object key) {
- diagnostics.check(
- passthroughPaths.containsKey(key),
- "invalid_key_for_passthrough_resolved_path",
- "key={} passthroughPaths={}",
- key,
- passthroughPaths);
+ if (!passthroughPaths.containsKey(key)) {
+ if (pathLookup.containsKey(key)) {
+ return getResolvedPath(key);
+ }
Review Comment:
This if block looks like a change in the logic, can you explain why it is
needed?
##########
polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolvable.java:
##########
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.polaris.core.persistence.resolver;
+
+/** Explicit resolution components for {@link
PolarisResolutionManifest#resolveSelections}. */
+public enum Resolvable {
Review Comment:
Would you mind adding some javadocs explaining the scope of each constant?
It might be just me 😅 but I'm not immediately seeing what `TOP_LEVEL_ENTITIES`
is referring to: according to our docs, only catalogs are top-level entities.
Polaris code also considers principals and roles as top-level entities for
authorization purposes. But these entities are all covered already by
`CALLER_PRINCIPAL`,
`CALLER_PRINCIPAL_ROLES` and `REFERENCE_CATALOG`. Is `TOP_LEVEL_ENTITIES`
reserved to "optional" top-level entities?
##########
runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogUtils.java:
##########
@@ -45,13 +47,19 @@ public class CatalogUtils {
*/
public static PolarisResolvedPathWrapper findResolvedStorageEntity(
PolarisResolutionManifestCatalogView resolvedEntityView, TableIdentifier
tableIdentifier) {
+ PolarisSecurable tableSecurable =
+ new PolarisSecurable(
+ PolarisEntityType.TABLE_LIKE,
+ PolarisCatalogHelpers.tableIdentifierToList(tableIdentifier));
PolarisResolvedPathWrapper resolvedTableEntities =
resolvedEntityView.getResolvedPath(
- tableIdentifier, PolarisEntityType.TABLE_LIKE,
PolarisEntitySubType.ICEBERG_TABLE);
+ tableSecurable, PolarisEntityType.TABLE_LIKE,
PolarisEntitySubType.ICEBERG_TABLE);
Review Comment:
Shouldn't the path have been registered also under the alias key
`tableIdentifier`?
I'm wondering if this change (and many other similar ones where a path is
looked up by a `PolarisSecurable` instead of some other key) is necessary.
Or is it because you plan to deprecate/remove lookup keys that are not
`PolarisSecurable` instances later on?
##########
runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java:
##########
@@ -1315,12 +1318,30 @@ public void close() throws Exception {
}
public ConfigResponse getConfig() {
- Resolver resolver = resolverFactory().createResolver(polarisPrincipal(),
catalogName());
- ResolverStatus resolverStatus = resolver.resolveAll();
- if (!resolverStatus.getStatus().equals(ResolverStatus.StatusEnum.SUCCESS))
{
+ // 'catalogName' is taken from the REST request's 'warehouse' query
parameter.
+ // 'warehouse' as an output will be treated by the client as a default
catalog
+ // storage base location.
+ // 'prefix' as an output is the REST subpath that routes to the catalog
+ // resource, which may be URL-escaped catalogName or potentially a
different
+ // unique identifier for the catalog being accessed.
+ if (catalogName() == null) {
+ throw new BadRequestException("Please specify a warehouse");
+ }
Review Comment:
This has been moved to `IcebergCatalogAdapter`. Here, `catalogName()` is
guaranteed to never be null.
```suggestion
```
##########
polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizer.java:
##########
@@ -27,14 +27,36 @@
/** Interface for invoking authorization checks. */
public interface PolarisAuthorizer {
+ /**
+ * Pre-authorization hook for resolving authorizer-specific inputs.
+ *
+ * <p>Implementations may resolve or validate any inputs needed to make an
authorization decision.
+ */
+ void preAuthorize(@Nonnull AuthorizationCallContext ctx, @Nonnull
AuthorizationRequest request);
+ /** Core authorization entry point for the new SPI. */
+ void authorize(@Nonnull AuthorizationCallContext ctx, @Nonnull
AuthorizationRequest request);
+
+ /**
+ * Backwards-compatible external API that throws on deny for legacy call
sites.
+ *
+ * <p>Default implementation delegates to {@link
#authorize(AuthorizationCallContext,
+ * AuthorizationRequest)}.
+ */
+ default void authorizeOrThrow(
+ @Nonnull AuthorizationCallContext ctx, @Nonnull AuthorizationRequest
request) {
+ authorize(ctx, request);
+ }
+
+ @Deprecated
void authorizeOrThrow(
@Nonnull PolarisPrincipal polarisPrincipal,
@Nonnull Set<PolarisBaseEntity> activatedEntities,
@Nonnull PolarisAuthorizableOperation authzOp,
@Nullable PolarisResolvedPathWrapper target,
@Nullable PolarisResolvedPathWrapper secondary);
+ @Deprecated
void authorizeOrThrow(
Review Comment:
I agree: this one seems used only in `OpaPolarisAuthorizerTest`.
##########
polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java:
##########
@@ -143,6 +182,25 @@ public ResolverStatus resolveAll() {
return primaryResolverStatus;
}
+ /**
+ * Resolves explicitly requested components.
+ *
+ * <p>Phase 1 behavior delegates to {@link #resolveAll()} to preserve
existing semantics until
+ * resolver selection is fully implemented.
+ */
+ public ResolverStatus resolveSelections(Set<Resolvable> selections) {
Review Comment:
nit: would `resolvePartially` better capture the opposition to `resolveAll`?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]