gh-yzou commented on code in PR #1264:
URL: https://github.com/apache/polaris/pull/1264#discussion_r2021604610


##########
polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisPrivilege.java:
##########
@@ -162,7 +208,7 @@ public enum PolarisPrivilege {
   private final PolarisEntityType securableType;
 
   // the subtype of the securable for this privilege
-  private final PolarisEntitySubType securableSubType;
+  private final List<PolarisEntitySubType> securableSubTypes;

Review Comment:
   I double checked how authorizeOrThrow is implemented 
https://github.com/apache/polaris/blob/main/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java#L510,
 it seems that is just checking if the user role have the corresponding 
privilege attached for a given op, it doesn't really check if the target 
matches the privilege definition, so the definition seems really just an 
definition, and not used anywhere



##########
service/common/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandlerWrapper.java:
##########
@@ -0,0 +1,122 @@
+/*
+ * 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.service.catalog.generic;
+
+import jakarta.ws.rs.core.SecurityContext;
+import java.util.Map;
+import java.util.TreeSet;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
+import org.apache.polaris.core.auth.PolarisAuthorizer;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.entity.PolarisEntitySubType;
+import org.apache.polaris.core.entity.table.GenericTableEntity;
+import org.apache.polaris.core.persistence.PolarisEntityManager;
+import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
+import org.apache.polaris.service.catalog.common.CatalogHandlerWrapper;
+import org.apache.polaris.service.types.GenericTable;
+import org.apache.polaris.service.types.ListGenericTablesResponse;
+import org.apache.polaris.service.types.LoadGenericTableResponse;
+
+public class GenericTableCatalogHandlerWrapper extends CatalogHandlerWrapper {
+
+  private PolarisMetaStoreManager metaStoreManager;
+
+  private GenericTableCatalog genericTableCatalog;
+
+  public GenericTableCatalogHandlerWrapper(
+      CallContext callContext,
+      PolarisEntityManager entityManager,
+      PolarisMetaStoreManager metaStoreManager,
+      SecurityContext securityContext,
+      String catalogName,
+      PolarisAuthorizer authorizer) {
+    super(callContext, entityManager, securityContext, catalogName, 
authorizer);
+    this.metaStoreManager = metaStoreManager;
+  }
+
+  public void enforceGenericTablesEnabledOrThrow() {
+    boolean enabled =
+        callContext
+            .getPolarisCallContext()
+            .getConfigurationStore()
+            .getConfiguration(
+                callContext.getPolarisCallContext(), 
FeatureConfiguration.ENABLE_GENERIC_TABLES);
+    if (!enabled) {
+      throw new UnsupportedOperationException("Generic table support is not 
enabled");
+    }
+  }
+
+  @Override
+  protected void initializeCatalog() {
+    enforceGenericTablesEnabledOrThrow();
+    this.genericTableCatalog =
+        new GenericTableCatalog(metaStoreManager, callContext, 
this.resolutionManifest);
+  }
+
+  public ListGenericTablesResponse listGenericTables(Namespace parent) {
+    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LIST_TABLES;
+    authorizeBasicNamespaceOperationOrThrow(op, parent);
+
+    return ListGenericTablesResponse.builder()
+        .setIdentifiers(new 
TreeSet<>(genericTableCatalog.listGenericTables(parent)))
+        .build();
+  }
+
+  public LoadGenericTableResponse createGenericTable(
+      TableIdentifier identifier, String format, String doc, Map<String, 
String> properties) {
+    PolarisAuthorizableOperation op = 
PolarisAuthorizableOperation.CREATE_TABLE_DIRECT;

Review Comment:
   nit: I want to double check if we will also need to support the 
CREATE_TABLE_DIRECT_WITH_WRITE_DELEGATION mode ? if yes, we can do it in the 
follow up PR, it doesn't need to block the current PR. 
   However, i see it is called WRITE_DELEGATION, I assume it is only needed by 
Iceberg since iceberg have the metadata write, want to double check this.



##########
service/common/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandlerWrapper.java:
##########
@@ -0,0 +1,122 @@
+/*
+ * 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.service.catalog.generic;
+
+import jakarta.ws.rs.core.SecurityContext;
+import java.util.Map;
+import java.util.TreeSet;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
+import org.apache.polaris.core.auth.PolarisAuthorizer;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.entity.PolarisEntitySubType;
+import org.apache.polaris.core.entity.table.GenericTableEntity;
+import org.apache.polaris.core.persistence.PolarisEntityManager;
+import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
+import org.apache.polaris.service.catalog.common.CatalogHandlerWrapper;
+import org.apache.polaris.service.types.GenericTable;
+import org.apache.polaris.service.types.ListGenericTablesResponse;
+import org.apache.polaris.service.types.LoadGenericTableResponse;
+
+public class GenericTableCatalogHandlerWrapper extends CatalogHandlerWrapper {
+
+  private PolarisMetaStoreManager metaStoreManager;
+
+  private GenericTableCatalog genericTableCatalog;
+
+  public GenericTableCatalogHandlerWrapper(
+      CallContext callContext,
+      PolarisEntityManager entityManager,
+      PolarisMetaStoreManager metaStoreManager,
+      SecurityContext securityContext,
+      String catalogName,
+      PolarisAuthorizer authorizer) {
+    super(callContext, entityManager, securityContext, catalogName, 
authorizer);
+    this.metaStoreManager = metaStoreManager;
+  }
+
+  public void enforceGenericTablesEnabledOrThrow() {
+    boolean enabled =
+        callContext
+            .getPolarisCallContext()

Review Comment:
   nit: this seems can be done with the following call directly
   ```
   FeatureConfiguration.loadConfig(FeatureConfiguration.ENABLE_GENERIC_TABLES);
   ```



##########
service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandlerWrapper.java:
##########
@@ -0,0 +1,354 @@
+/*
+ * 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.service.catalog.common;
+
+import jakarta.ws.rs.core.SecurityContext;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.exceptions.AlreadyExistsException;
+import org.apache.iceberg.exceptions.NoSuchNamespaceException;
+import org.apache.iceberg.exceptions.NoSuchTableException;
+import org.apache.iceberg.exceptions.NoSuchViewException;
+import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
+import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
+import org.apache.polaris.core.auth.PolarisAuthorizer;
+import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
+import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.entity.PolarisEntitySubType;
+import org.apache.polaris.core.entity.PolarisEntityType;
+import org.apache.polaris.core.persistence.PolarisEntityManager;
+import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
+import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
+import org.apache.polaris.core.persistence.resolver.ResolverPath;
+import org.apache.polaris.core.persistence.resolver.ResolverStatus;
+
+/**
+ * An ABC for catalog wrappers which provides authorize methods that should be 
called before a
+ * request is actually forwarded to a catalog. Child types must implement 
`initializeCatalog` which
+ * will be called after a successful authorization.
+ */
+public abstract class CatalogHandlerWrapper {
+
+  // Initialized in the authorize methods.
+  protected PolarisResolutionManifest resolutionManifest = null;
+
+  protected final CallContext callContext;
+  protected final PolarisEntityManager entityManager;
+  protected final String catalogName;
+  protected final AuthenticatedPolarisPrincipal authenticatedPrincipal;
+  protected final SecurityContext securityContext;
+  protected final PolarisAuthorizer authorizer;
+
+  public CatalogHandlerWrapper(
+      CallContext callContext,
+      PolarisEntityManager entityManager,
+      SecurityContext securityContext,
+      String catalogName,
+      PolarisAuthorizer authorizer) {
+    this.callContext = callContext;
+    this.entityManager = entityManager;
+    this.catalogName = catalogName;
+    PolarisDiagnostics diagServices = 
callContext.getPolarisCallContext().getDiagServices();
+    diagServices.checkNotNull(securityContext, "null_security_context");
+    diagServices.checkNotNull(securityContext.getUserPrincipal(), 
"null_user_principal");
+    diagServices.check(
+        securityContext.getUserPrincipal() instanceof 
AuthenticatedPolarisPrincipal,
+        "invalid_principal_type",
+        "Principal must be an AuthenticatedPolarisPrincipal");
+    this.securityContext = securityContext;
+    this.authenticatedPrincipal =
+        (AuthenticatedPolarisPrincipal) securityContext.getUserPrincipal();
+    this.authorizer = authorizer;
+  }
+
+  /** Initialize the catalog once authorized. Called after all `authorize...` 
methods. */
+  protected abstract void initializeCatalog();
+
+  protected void authorizeBasicNamespaceOperationOrThrow(
+      PolarisAuthorizableOperation op, Namespace namespace) {
+    authorizeBasicNamespaceOperationOrThrow(op, namespace, null, null);
+  }
+
+  protected void authorizeBasicNamespaceOperationOrThrow(
+      PolarisAuthorizableOperation op,
+      Namespace namespace,
+      List<Namespace> extraPassthroughNamespaces,
+      List<TableIdentifier> extraPassthroughTableLikes) {
+    resolutionManifest =
+        entityManager.prepareResolutionManifest(callContext, securityContext, 
catalogName);
+    resolutionManifest.addPath(
+        new ResolverPath(Arrays.asList(namespace.levels()), 
PolarisEntityType.NAMESPACE),
+        namespace);
+
+    if (extraPassthroughNamespaces != null) {
+      for (Namespace ns : extraPassthroughNamespaces) {
+        resolutionManifest.addPassthroughPath(
+            new ResolverPath(
+                Arrays.asList(ns.levels()), PolarisEntityType.NAMESPACE, true 
/* optional */),
+            ns);
+      }
+    }
+    if (extraPassthroughTableLikes != null) {
+      for (TableIdentifier id : extraPassthroughTableLikes) {
+        resolutionManifest.addPassthroughPath(
+            new ResolverPath(
+                PolarisCatalogHelpers.tableIdentifierToList(id),
+                PolarisEntityType.TABLE_LIKE,
+                true /* optional */),
+            id);
+      }
+    }
+    resolutionManifest.resolveAll();
+    PolarisResolvedPathWrapper target = 
resolutionManifest.getResolvedPath(namespace, true);
+    if (target == null) {
+      throw new NoSuchNamespaceException("Namespace does not exist: %s", 
namespace);
+    }
+    authorizer.authorizeOrThrow(
+        authenticatedPrincipal,
+        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
+        op,
+        target,
+        null /* secondary */);
+
+    initializeCatalog();
+  }
+
+  protected void authorizeCreateNamespaceUnderNamespaceOperationOrThrow(
+      PolarisAuthorizableOperation op, Namespace namespace) {
+    resolutionManifest =
+        entityManager.prepareResolutionManifest(callContext, securityContext, 
catalogName);
+
+    Namespace parentNamespace = 
PolarisCatalogHelpers.getParentNamespace(namespace);
+    resolutionManifest.addPath(
+        new ResolverPath(Arrays.asList(parentNamespace.levels()), 
PolarisEntityType.NAMESPACE),
+        parentNamespace);
+
+    // When creating an entity under a namespace, the authz target is the 
parentNamespace, but we
+    // must also add the actual path that will be created as an "optional" 
passthrough resolution
+    // path to indicate that the underlying catalog is "allowed" to check the 
creation path for
+    // a conflicting entity.
+    resolutionManifest.addPassthroughPath(
+        new ResolverPath(
+            Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE, 
true /* optional */),
+        namespace);
+    resolutionManifest.resolveAll();
+    PolarisResolvedPathWrapper target = 
resolutionManifest.getResolvedPath(parentNamespace, true);
+    if (target == null) {
+      throw new NoSuchNamespaceException("Namespace does not exist: %s", 
parentNamespace);
+    }
+    authorizer.authorizeOrThrow(
+        authenticatedPrincipal,
+        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
+        op,
+        target,
+        null /* secondary */);
+
+    initializeCatalog();
+  }
+
+  protected void authorizeCreateTableLikeUnderNamespaceOperationOrThrow(
+      PolarisAuthorizableOperation op, TableIdentifier identifier) {
+    Namespace namespace = identifier.namespace();
+
+    resolutionManifest =
+        entityManager.prepareResolutionManifest(callContext, securityContext, 
catalogName);
+    resolutionManifest.addPath(
+        new ResolverPath(Arrays.asList(namespace.levels()), 
PolarisEntityType.NAMESPACE),
+        namespace);
+
+    // When creating an entity under a namespace, the authz target is the 
namespace, but we must
+    // also
+    // add the actual path that will be created as an "optional" passthrough 
resolution path to
+    // indicate that the underlying catalog is "allowed" to check the creation 
path for a
+    // conflicting
+    // entity.
+    resolutionManifest.addPassthroughPath(
+        new ResolverPath(
+            PolarisCatalogHelpers.tableIdentifierToList(identifier),
+            PolarisEntityType.TABLE_LIKE,
+            true /* optional */),
+        identifier);
+    resolutionManifest.resolveAll();
+    PolarisResolvedPathWrapper target = 
resolutionManifest.getResolvedPath(namespace, true);
+    if (target == null) {
+      throw new NoSuchNamespaceException("Namespace does not exist: %s", 
namespace);
+    }
+    authorizer.authorizeOrThrow(
+        authenticatedPrincipal,
+        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
+        op,
+        target,
+        null /* secondary */);
+
+    initializeCatalog();
+  }
+
+  protected void authorizeBasicTableLikeOperationOrThrow(
+      PolarisAuthorizableOperation op, PolarisEntitySubType subType, 
TableIdentifier identifier) {
+    resolutionManifest =
+        entityManager.prepareResolutionManifest(callContext, securityContext, 
catalogName);
+
+    // The underlying Catalog is also allowed to fetch "fresh" versions of the 
target entity.
+    resolutionManifest.addPassthroughPath(
+        new ResolverPath(
+            PolarisCatalogHelpers.tableIdentifierToList(identifier),
+            PolarisEntityType.TABLE_LIKE,
+            true /* optional */),
+        identifier);
+    resolutionManifest.resolveAll();
+    PolarisResolvedPathWrapper target =
+        resolutionManifest.getResolvedPath(identifier, 
PolarisEntityType.TABLE_LIKE, subType, true);
+    if (target == null) {
+      if (subType == PolarisEntitySubType.ICEBERG_TABLE
+          || subType == PolarisEntitySubType.GENERIC_TABLE) {
+        throw new NoSuchTableException("Table does not exist: %s", identifier);
+      } else {
+        throw new NoSuchViewException("View does not exist: %s", identifier);
+      }
+    }
+    authorizer.authorizeOrThrow(
+        authenticatedPrincipal,
+        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
+        op,
+        target,
+        null /* secondary */);
+
+    initializeCatalog();
+  }
+
+  protected void authorizeCollectionOfTableLikeOperationOrThrow(
+      PolarisAuthorizableOperation op,
+      final PolarisEntitySubType subType,
+      List<TableIdentifier> ids) {
+    resolutionManifest =
+        entityManager.prepareResolutionManifest(callContext, securityContext, 
catalogName);
+    ids.forEach(
+        identifier ->
+            resolutionManifest.addPassthroughPath(
+                new ResolverPath(
+                    PolarisCatalogHelpers.tableIdentifierToList(identifier),
+                    PolarisEntityType.TABLE_LIKE),
+                identifier));
+
+    ResolverStatus status = resolutionManifest.resolveAll();
+
+    // If one of the paths failed to resolve, throw exception based on the one 
that
+    // we first failed to resolve.
+    if (status.getStatus() == 
ResolverStatus.StatusEnum.PATH_COULD_NOT_BE_FULLY_RESOLVED) {
+      TableIdentifier identifier =
+          PolarisCatalogHelpers.listToTableIdentifier(
+              status.getFailedToResolvePath().getEntityNames());
+      if (subType == PolarisEntitySubType.ICEBERG_TABLE) {
+        throw new NoSuchTableException("Table does not exist: %s", identifier);
+      } else {
+        throw new NoSuchViewException("View does not exist: %s", identifier);
+      }
+    }
+
+    List<PolarisResolvedPathWrapper> targets =
+        ids.stream()
+            .map(
+                identifier ->
+                    Optional.ofNullable(
+                            resolutionManifest.getResolvedPath(
+                                identifier, PolarisEntityType.TABLE_LIKE, 
subType, true))
+                        .orElseThrow(
+                            () ->
+                                subType == PolarisEntitySubType.ICEBERG_TABLE
+                                    ? new NoSuchTableException(
+                                        "Table does not exist: %s", identifier)
+                                    : new NoSuchViewException(
+                                        "View does not exist: %s", 
identifier)))
+            .toList();
+    authorizer.authorizeOrThrow(
+        authenticatedPrincipal,
+        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
+        op,
+        targets,
+        null /* secondaries */);
+
+    initializeCatalog();
+  }
+
+  protected void authorizeRenameTableLikeOperationOrThrow(
+      PolarisAuthorizableOperation op,
+      PolarisEntitySubType subType,
+      TableIdentifier src,
+      TableIdentifier dst) {
+    resolutionManifest =
+        entityManager.prepareResolutionManifest(callContext, securityContext, 
catalogName);
+    // Add src, dstParent, and dst(optional)
+    resolutionManifest.addPath(
+        new ResolverPath(
+            PolarisCatalogHelpers.tableIdentifierToList(src), 
PolarisEntityType.TABLE_LIKE),
+        src);
+    resolutionManifest.addPath(
+        new ResolverPath(Arrays.asList(dst.namespace().levels()), 
PolarisEntityType.NAMESPACE),
+        dst.namespace());
+    resolutionManifest.addPath(
+        new ResolverPath(
+            PolarisCatalogHelpers.tableIdentifierToList(dst),
+            PolarisEntityType.TABLE_LIKE,
+            true /* optional */),
+        dst);
+    ResolverStatus status = resolutionManifest.resolveAll();
+    if (status.getStatus() == 
ResolverStatus.StatusEnum.PATH_COULD_NOT_BE_FULLY_RESOLVED
+        && status.getFailedToResolvePath().getLastEntityType() == 
PolarisEntityType.NAMESPACE) {
+      throw new NoSuchNamespaceException("Namespace does not exist: %s", 
dst.namespace());
+    } else if (resolutionManifest.getResolvedPath(src, 
PolarisEntityType.TABLE_LIKE, subType)
+        == null) {
+      if (subType == PolarisEntitySubType.ICEBERG_TABLE) {
+        throw new NoSuchTableException("Table does not exist: %s", src);
+      } else {
+        throw new NoSuchViewException("View does not exist: %s", src);
+      }
+    }
+
+    // Normally, since we added the dst as an optional path, we'd expect it to 
only get resolved
+    // up to its parent namespace, and for there to be no TABLE_LIKE already 
in the dst in which
+    // case the leafSubType will be NULL_SUBTYPE.
+    // If there is a conflicting TABLE or VIEW, this leafSubType will indicate 
that conflicting
+    // type.
+    // TODO: Possibly modify the exception thrown depending on whether the 
caller has privileges
+    // on the parent namespace.
+    PolarisEntitySubType dstLeafSubType = 
resolutionManifest.getLeafSubType(dst);
+    if (dstLeafSubType == PolarisEntitySubType.ICEBERG_TABLE) {
+      throw new AlreadyExistsException("Cannot rename %s to %s. Table already 
exists", src, dst);
+    } else if (dstLeafSubType == PolarisEntitySubType.ICEBERG_VIEW) {

Review Comment:
   sorry, i don't mean do it in this pr, but do it at sometime



-- 
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: issues-unsubscr...@polaris.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to