From abad5e7b91c83473ec8758cbe584a455df7e5db0 Mon Sep 17 00:00:00 2001 From: Honah J Date: Fri, 11 Apr 2025 17:57:04 -0500 Subject: [PATCH 1/5] Add PolicyCatalogHandler and tests --- .../auth/PolarisAuthorizableOperation.java | 64 +--- .../core/auth/PolarisAuthorizerImpl.java | 100 ++---- .../polaris/core/entity/PolarisPrivilege.java | 6 + .../quarkus/admin/PolarisAuthzTestBase.java | 14 + .../PolicyCatalogHandlerAuthzTest.java | 306 ++++++++++++++++++ .../catalog/common/CatalogHandler.java | 6 +- .../service/catalog/policy/PolicyCatalog.java | 4 +- .../catalog/policy/PolicyCatalogHandler.java | 181 +++++++++++ 8 files changed, 551 insertions(+), 130 deletions(-) create mode 100644 quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/PolicyCatalogHandlerAuthzTest.java create mode 100644 service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java diff --git a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizableOperation.java b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizableOperation.java index 9b285c1bb0..292798cd4f 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizableOperation.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizableOperation.java @@ -18,63 +18,8 @@ */ package org.apache.polaris.core.auth; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_FOR_GRANTEE; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_WRITE_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_WRITE_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_WRITE_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_MANAGE_GRANTS_FOR_GRANTEE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_RESET_CREDENTIALS; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_MANAGE_GRANTS_FOR_GRANTEE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_WRITE_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROTATE_CREDENTIALS; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_WRITE_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.SERVICE_MANAGE_ACCESS; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_READ_DATA; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_WRITE_DATA; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_WRITE_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.*; +import static org.apache.polaris.core.entity.PolarisPrivilege.POLICY_WRITE; import java.util.EnumSet; import org.apache.polaris.core.entity.PolarisPrivilege; @@ -182,6 +127,11 @@ public enum PolarisAuthorizableOperation { REVOKE_VIEW_GRANT_FROM_CATALOG_ROLE( VIEW_MANAGE_GRANTS_ON_SECURABLE, CATALOG_ROLE_MANAGE_GRANTS_FOR_GRANTEE), LIST_GRANTS_ON_VIEW(VIEW_LIST_GRANTS), + CREATE_POLICY(POLICY_CREATE), + LOAD_POLICY(POLICY_READ), + DROP_POLICY(POLICY_DROP), + UPDATE_POLICY(POLICY_WRITE), + LIST_POLICY(POLICY_LIST), ; private final EnumSet privilegesOnTarget; diff --git a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java index 564be49dac..a059b3418f 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java @@ -18,75 +18,7 @@ */ package org.apache.polaris.core.auth; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_FULL_METADATA; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_MANAGE_ACCESS; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_MANAGE_CONTENT; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_MANAGE_METADATA; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_FULL_METADATA; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_FOR_GRANTEE; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_USAGE; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_WRITE_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_WRITE_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_FULL_METADATA; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_WRITE_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_FULL_METADATA; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_MANAGE_GRANTS_FOR_GRANTEE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_RESET_CREDENTIALS; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_FULL_METADATA; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_MANAGE_GRANTS_FOR_GRANTEE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_USAGE; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_WRITE_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROTATE_CREDENTIALS; -import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_WRITE_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.SERVICE_MANAGE_ACCESS; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_FULL_METADATA; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_READ_DATA; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_WRITE_DATA; -import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_WRITE_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_CREATE; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_DROP; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_FULL_METADATA; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_LIST; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_LIST_GRANTS; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_MANAGE_GRANTS_ON_SECURABLE; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_READ_PROPERTIES; -import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.*; import com.google.common.base.Preconditions; import com.google.common.collect.HashMultimap; @@ -457,6 +389,36 @@ public class PolarisAuthorizerImpl implements PolarisAuthorizer { SUPER_PRIVILEGES.putAll( CATALOG_ROLE_MANAGE_GRANTS_FOR_GRANTEE, List.of(CATALOG_ROLE_MANAGE_GRANTS_FOR_GRANTEE, CATALOG_MANAGE_ACCESS)); + + // Policy + SUPER_PRIVILEGES.putAll( + POLICY_CREATE, + List.of( + POLICY_CREATE, POLICY_FULL_METADATA, CATALOG_MANAGE_METADATA, CATALOG_MANAGE_CONTENT)); + SUPER_PRIVILEGES.putAll( + POLICY_WRITE, + List.of( + POLICY_WRITE, POLICY_FULL_METADATA, CATALOG_MANAGE_METADATA, CATALOG_MANAGE_CONTENT)); + SUPER_PRIVILEGES.putAll( + POLICY_DROP, + List.of( + POLICY_DROP, POLICY_FULL_METADATA, CATALOG_MANAGE_METADATA, CATALOG_MANAGE_CONTENT)); + SUPER_PRIVILEGES.putAll( + POLICY_READ, + List.of( + POLICY_READ, + POLICY_WRITE, + POLICY_FULL_METADATA, + CATALOG_MANAGE_METADATA, + CATALOG_MANAGE_CONTENT)); + SUPER_PRIVILEGES.putAll( + POLICY_LIST, + List.of( + POLICY_LIST, + POLICY_CREATE, + POLICY_FULL_METADATA, + CATALOG_MANAGE_METADATA, + CATALOG_MANAGE_CONTENT)); } private final PolarisConfigurationStore featureConfig; diff --git a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisPrivilege.java b/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisPrivilege.java index 6a2464a069..03585790b9 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisPrivilege.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisPrivilege.java @@ -136,6 +136,12 @@ public enum PolarisPrivilege { CATALOG_ROLE_FULL_METADATA(67, PolarisEntityType.CATALOG_ROLE), CATALOG_ROLE_MANAGE_GRANTS_ON_SECURABLE(68, PolarisEntityType.CATALOG_ROLE), CATALOG_ROLE_MANAGE_GRANTS_FOR_GRANTEE(69, PolarisEntityType.CATALOG_ROLE), + POLICY_CREATE(70, PolarisEntityType.NAMESPACE), + POLICY_READ(71, PolarisEntityType.POLICY), + POLICY_DROP(72, PolarisEntityType.POLICY), + POLICY_WRITE(73, PolarisEntityType.POLICY), + POLICY_LIST(74, PolarisEntityType.NAMESPACE), + POLICY_FULL_METADATA(75, PolarisEntityType.POLICY), ; /** diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java index 4b0037eed0..3a7af2531b 100644 --- a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java @@ -77,17 +77,20 @@ import org.apache.polaris.core.persistence.transactional.TransactionalPersistence; import org.apache.polaris.core.secrets.UserSecretsManager; import org.apache.polaris.core.secrets.UserSecretsManagerFactory; +import org.apache.polaris.core.policy.PredefinedPolicyTypes; import org.apache.polaris.service.admin.PolarisAdminService; import org.apache.polaris.service.catalog.PolarisPassthroughResolutionView; import org.apache.polaris.service.catalog.generic.GenericTableCatalog; import org.apache.polaris.service.catalog.iceberg.IcebergCatalog; import org.apache.polaris.service.catalog.io.FileIOFactory; +import org.apache.polaris.service.catalog.policy.PolicyCatalog; import org.apache.polaris.service.config.DefaultConfigurationStore; import org.apache.polaris.service.config.RealmEntityManagerFactory; import org.apache.polaris.service.context.CallContextCatalogFactory; import org.apache.polaris.service.context.PolarisCallContextCatalogFactory; import org.apache.polaris.service.storage.PolarisStorageIntegrationProviderImpl; import org.apache.polaris.service.task.TaskExecutor; +import org.apache.polaris.service.types.PolicyIdentifier; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -139,6 +142,9 @@ public Map getConfigOverrides() { protected static final TableIdentifier TABLE_NS1_1_GENERIC = TableIdentifier.of(NS1, "layer1_table_generic"); + // A policy directly under ns1 + protected static final PolicyIdentifier POLICY_NS1_1 = new PolicyIdentifier(NS1, "layer1_policy"); + // Two tables under ns1a protected static final TableIdentifier TABLE_NS1A_1 = TableIdentifier.of(NS1A, "table1"); protected static final TableIdentifier TABLE_NS1A_2 = TableIdentifier.of(NS1A, "table2"); @@ -185,6 +191,7 @@ public Map getConfigOverrides() { protected IcebergCatalog baseCatalog; protected GenericTableCatalog genericTableCatalog; + protected PolicyCatalog policyCatalog; protected PolarisAdminService adminService; protected PolarisEntityManager entityManager; protected PolarisMetaStoreManager metaStoreManager; @@ -321,6 +328,12 @@ public void before(TestInfo testInfo) { genericTableCatalog.createGenericTable(TABLE_NS1_1_GENERIC, "format", "doc", Map.of()); + policyCatalog.createPolicy( + POLICY_NS1_1, + PredefinedPolicyTypes.DATA_COMPACTION.getName(), + "test_policy", + "{\"enable\": false}"); + baseCatalog .buildView(VIEW_NS1_1) .withSchema(SCHEMA) @@ -463,6 +476,7 @@ private void initBaseCatalog() { CatalogProperties.FILE_IO_IMPL, "org.apache.iceberg.inmemory.InMemoryFileIO")); this.genericTableCatalog = new GenericTableCatalog(metaStoreManager, callContext, passthroughView); + this.policyCatalog = new PolicyCatalog(metaStoreManager, callContext, passthroughView); } @Alternative diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/PolicyCatalogHandlerAuthzTest.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/PolicyCatalogHandlerAuthzTest.java new file mode 100644 index 0000000000..464f7ff3b4 --- /dev/null +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/PolicyCatalogHandlerAuthzTest.java @@ -0,0 +1,306 @@ +/* + * 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.quarkus.catalog; + +import io.quarkus.test.junit.QuarkusTest; +import java.util.List; +import java.util.Set; +import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal; +import org.apache.polaris.core.entity.PolarisPrivilege; +import org.apache.polaris.core.policy.PredefinedPolicyTypes; +import org.apache.polaris.service.catalog.policy.PolicyCatalogHandler; +import org.apache.polaris.service.quarkus.admin.PolarisAuthzTestBase; +import org.apache.polaris.service.types.CreatePolicyRequest; +import org.apache.polaris.service.types.PolicyIdentifier; +import org.apache.polaris.service.types.UpdatePolicyRequest; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +@QuarkusTest +public class PolicyCatalogHandlerAuthzTest extends PolarisAuthzTestBase { + private PolicyCatalogHandler newWrapper() { + return newWrapper(Set.of()); + } + + private PolicyCatalogHandler newWrapper(Set activatedPrincipalRoles) { + return newWrapper(activatedPrincipalRoles, CATALOG_NAME); + } + + private PolicyCatalogHandler newWrapper(Set activatedPrincipalRoles, String catalogName) { + final AuthenticatedPolarisPrincipal authenticatedPrincipal = + new AuthenticatedPolarisPrincipal(principalEntity, activatedPrincipalRoles); + return new PolicyCatalogHandler( + callContext, + entityManager, + metaStoreManager, + securityContext(authenticatedPrincipal, activatedPrincipalRoles), + catalogName, + polarisAuthorizer); + } + + /** + * Tests each "sufficient" privilege individually using CATALOG_ROLE1 by granting at the + * CATALOG_NAME level, revoking after each test, and also ensuring that the request fails after + * revocation. + * + * @param sufficientPrivileges List of privileges that should be sufficient each in isolation for + * {@code action} to succeed. + * @param action The operation being tested; could also be multiple operations that should all + * succeed with the sufficient privilege + * @param cleanupAction If non-null, additional action to run to "undo" a previous success action + * in case the action has side effects. Called before revoking the sufficient privilege; + * either the cleanup privileges must be latent, or the cleanup action could be run with + * PRINCIPAL_ROLE2 while runnint {@code action} with PRINCIPAL_ROLE1. + */ + private void doTestSufficientPrivileges( + List sufficientPrivileges, Runnable action, Runnable cleanupAction) { + doTestSufficientPrivilegeSets( + sufficientPrivileges.stream().map(priv -> Set.of(priv)).toList(), + action, + cleanupAction, + PRINCIPAL_NAME); + } + + /** + * @param sufficientPrivileges each set of concurrent privileges expected to be sufficient + * together. + * @param action + * @param cleanupAction + * @param principalName + */ + private void doTestSufficientPrivilegeSets( + List> sufficientPrivileges, + Runnable action, + Runnable cleanupAction, + String principalName) { + doTestSufficientPrivilegeSets( + sufficientPrivileges, action, cleanupAction, principalName, CATALOG_NAME); + } + + /** + * @param sufficientPrivileges each set of concurrent privileges expected to be sufficient + * together. + * @param action + * @param cleanupAction + * @param principalName + * @param catalogName + */ + private void doTestSufficientPrivilegeSets( + List> sufficientPrivileges, + Runnable action, + Runnable cleanupAction, + String principalName, + String catalogName) { + doTestSufficientPrivilegeSets( + sufficientPrivileges, + action, + cleanupAction, + principalName, + (privilege) -> + adminService.grantPrivilegeOnCatalogToRole(catalogName, CATALOG_ROLE1, privilege), + (privilege) -> + adminService.revokePrivilegeOnCatalogFromRole(catalogName, CATALOG_ROLE1, privilege)); + } + + private void doTestInsufficientPrivileges( + List insufficientPrivileges, Runnable action) { + doTestInsufficientPrivileges(insufficientPrivileges, PRINCIPAL_NAME, action); + } + + /** + * Tests each "insufficient" privilege individually using CATALOG_ROLE1 by granting at the + * CATALOG_NAME level, ensuring the action fails, then revoking after each test case. + */ + private void doTestInsufficientPrivileges( + List insufficientPrivileges, String principalName, Runnable action) { + doTestInsufficientPrivileges( + insufficientPrivileges, + principalName, + action, + (privilege) -> + adminService.grantPrivilegeOnCatalogToRole(CATALOG_NAME, CATALOG_ROLE1, privilege), + (privilege) -> + adminService.revokePrivilegeOnCatalogFromRole(CATALOG_NAME, CATALOG_ROLE1, privilege)); + } + + @Test + public void testListPoliciesAllSufficientPrivileges() { + doTestSufficientPrivileges( + List.of( + PolarisPrivilege.POLICY_LIST, + PolarisPrivilege.POLICY_CREATE, + PolarisPrivilege.POLICY_FULL_METADATA, + PolarisPrivilege.CATALOG_MANAGE_CONTENT), + () -> newWrapper().listPolicies(NS1, null), + null /* cleanupAction */); + } + + @Test + public void testListPoliciesInsufficientPrivileges() { + doTestInsufficientPrivileges( + List.of( + PolarisPrivilege.NAMESPACE_FULL_METADATA, + PolarisPrivilege.POLICY_READ, + PolarisPrivilege.POLICY_DROP, + PolarisPrivilege.POLICY_WRITE), + () -> newWrapper().listPolicies(NS1, null)); + } + + @Test + public void testCreatePolicyAllSufficientPrivileges() { + Assertions.assertThat( + adminService.grantPrivilegeOnCatalogToRole( + CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DROP)) + .isTrue(); + + final PolicyIdentifier newPolicy = new PolicyIdentifier(NS2, "newPolicy"); + final CreatePolicyRequest createPolicyRequest = + CreatePolicyRequest.builder() + .setName(newPolicy.getName()) + .setType(PredefinedPolicyTypes.DATA_COMPACTION.getName()) + .setContent("{\"enable\": false}") + .build(); + + doTestSufficientPrivileges( + List.of( + PolarisPrivilege.POLICY_CREATE, + PolarisPrivilege.CATALOG_MANAGE_CONTENT, + PolarisPrivilege.POLICY_FULL_METADATA), + () -> newWrapper(Set.of(PRINCIPAL_ROLE1)).createPolicy(NS2, createPolicyRequest), + () -> newWrapper(Set.of(PRINCIPAL_ROLE2)).dropPolicy(newPolicy, true)); + } + + @Test + public void testCreatePolicyInsufficientPrivileges() { + final PolicyIdentifier newPolicy = new PolicyIdentifier(NS2, "newPolicy"); + final CreatePolicyRequest createPolicyRequest = + CreatePolicyRequest.builder() + .setName(newPolicy.getName()) + .setType(PredefinedPolicyTypes.DATA_COMPACTION.getName()) + .setContent("{\"enable\": false}") + .build(); + + doTestInsufficientPrivileges( + List.of( + PolarisPrivilege.POLICY_READ, + PolarisPrivilege.POLICY_LIST, + PolarisPrivilege.POLICY_DROP, + PolarisPrivilege.POLICY_LIST, + PolarisPrivilege.POLICY_WRITE), + () -> newWrapper().createPolicy(NS2, createPolicyRequest)); + } + + @Test + public void testLoadPolicyAllSufficientPrivileges() { + doTestSufficientPrivileges( + List.of( + PolarisPrivilege.POLICY_READ, + PolarisPrivilege.POLICY_WRITE, + PolarisPrivilege.POLICY_FULL_METADATA, + PolarisPrivilege.CATALOG_MANAGE_CONTENT), + () -> newWrapper().loadPolicy(POLICY_NS1_1), + null /* cleanupAction */); + } + + @Test + public void testLoadPolicyInsufficientPrivileges() { + doTestInsufficientPrivileges( + List.of( + PolarisPrivilege.POLICY_LIST, + PolarisPrivilege.POLICY_DROP, + PolarisPrivilege.POLICY_CREATE), + () -> newWrapper().loadPolicy(POLICY_NS1_1)); + } + + @Test + public void testUpdatePolicyAllSufficientPrivileges() { + doTestSufficientPrivileges( + List.of( + PolarisPrivilege.POLICY_WRITE, + PolarisPrivilege.POLICY_FULL_METADATA, + PolarisPrivilege.CATALOG_MANAGE_CONTENT), + () -> + newWrapper() + .updatePolicy( + POLICY_NS1_1, + UpdatePolicyRequest.builder() + .setCurrentPolicyVersion(0) + .setDescription("test_policy") + .setContent("{\"enable\": false}") + .build()), + null /* cleanupAction */); + } + + @Test + public void testUpdatePolicyInsufficientPrivileges() { + doTestInsufficientPrivileges( + List.of( + PolarisPrivilege.POLICY_LIST, + PolarisPrivilege.POLICY_DROP, + PolarisPrivilege.POLICY_CREATE, + PolarisPrivilege.POLICY_READ), + () -> + newWrapper() + .updatePolicy( + POLICY_NS1_1, + UpdatePolicyRequest.builder() + .setCurrentPolicyVersion(0) + .setDescription("test_policy") + .setContent("{\"enable\": false}") + .build())); + } + + @Test + public void testDropPolicyAllSufficientPrivileges() { + Assertions.assertThat( + adminService.grantPrivilegeOnCatalogToRole( + CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_CREATE)) + .isTrue(); + + final CreatePolicyRequest createPolicyRequest = + CreatePolicyRequest.builder() + .setName(POLICY_NS1_1.getName()) + .setType(PredefinedPolicyTypes.DATA_COMPACTION.getName()) + .setDescription("test_policy") + .setContent("{\"enable\": false}") + .build(); + + doTestSufficientPrivileges( + List.of( + PolarisPrivilege.POLICY_DROP, + PolarisPrivilege.POLICY_FULL_METADATA, + PolarisPrivilege.CATALOG_MANAGE_CONTENT), + () -> newWrapper(Set.of(PRINCIPAL_ROLE1)).dropPolicy(POLICY_NS1_1, true), + () -> + newWrapper(Set.of(PRINCIPAL_ROLE2)) + .createPolicy( + POLICY_NS1_1.getNamespace(), createPolicyRequest) /* cleanupAction */); + } + + @Test + public void testDropPolicyInsufficientPrivileges() { + doTestInsufficientPrivileges( + List.of( + PolarisPrivilege.POLICY_READ, + PolarisPrivilege.POLICY_LIST, + PolarisPrivilege.POLICY_CREATE, + PolarisPrivilege.POLICY_WRITE), + () -> newWrapper().dropPolicy(POLICY_NS1_1, true)); + } +} diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java b/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java index 3a902c8edb..3de4321d4f 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java @@ -54,9 +54,9 @@ public abstract class CatalogHandler { // Initialized in the authorize methods. protected PolarisResolutionManifest resolutionManifest = null; - private final PolarisEntityManager entityManager; - private final String catalogName; - private final PolarisAuthorizer authorizer; + protected final PolarisEntityManager entityManager; + protected final String catalogName; + protected final PolarisAuthorizer authorizer; protected final CallContext callContext; protected final AuthenticatedPolarisPrincipal authenticatedPrincipal; diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalog.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalog.java index 718b4ef9e0..195d0e128d 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalog.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalog.java @@ -27,6 +27,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import org.apache.iceberg.catalog.Namespace; import org.apache.iceberg.catalog.TableIdentifier; @@ -213,7 +214,8 @@ public Policy updatePolicy( currentPolicyVersion, policyVersion)); } - if (newDescription.equals(policy.getDescription()) && newContent.equals(policy.getContent())) { + if (Objects.equals(newDescription, policy.getDescription()) + && Objects.equals(newContent, policy.getContent())) { // No need to update the policy if the new description and content are the same as the current return constructPolicy(policy); } diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java new file mode 100644 index 0000000000..3670e019d9 --- /dev/null +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java @@ -0,0 +1,181 @@ +/* + * 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.policy; + +import jakarta.ws.rs.core.SecurityContext; +import java.util.Arrays; +import java.util.HashSet; +import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.exceptions.NoSuchNamespaceException; +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.PolarisEntityType; +import org.apache.polaris.core.persistence.PolarisEntityManager; +import org.apache.polaris.core.persistence.PolarisMetaStoreManager; +import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper; +import org.apache.polaris.core.persistence.resolver.ResolverPath; +import org.apache.polaris.core.policy.PolicyType; +import org.apache.polaris.core.policy.exceptions.NoSuchPolicyException; +import org.apache.polaris.service.catalog.common.CatalogHandler; +import org.apache.polaris.service.types.CreatePolicyRequest; +import org.apache.polaris.service.types.ListPoliciesResponse; +import org.apache.polaris.service.types.LoadPolicyResponse; +import org.apache.polaris.service.types.PolicyIdentifier; +import org.apache.polaris.service.types.UpdatePolicyRequest; + +public class PolicyCatalogHandler extends CatalogHandler { + + private PolarisMetaStoreManager metaStoreManager; + + private PolicyCatalog policyCatalog; + + public PolicyCatalogHandler( + CallContext callContext, + PolarisEntityManager entityManager, + PolarisMetaStoreManager metaStoreManager, + SecurityContext securityContext, + String catalogName, + PolarisAuthorizer authorizer) { + super(callContext, entityManager, securityContext, catalogName, authorizer); + this.metaStoreManager = metaStoreManager; + } + + @Override + protected void initializeCatalog() { + this.policyCatalog = new PolicyCatalog(metaStoreManager, callContext, this.resolutionManifest); + } + + public ListPoliciesResponse listPolicies(Namespace parent, PolicyType policyType) { + PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LIST_POLICY; + authorizeBasicNamespaceOperationOrThrow(op, parent); + + return ListPoliciesResponse.builder() + .setIdentifiers(new HashSet<>(policyCatalog.listPolicies(parent, policyType))) + .build(); + } + + public LoadPolicyResponse createPolicy(Namespace namespace, CreatePolicyRequest request) { + PolarisAuthorizableOperation op = PolarisAuthorizableOperation.CREATE_POLICY; + PolicyIdentifier identifier = + PolicyIdentifier.builder().setNamespace(namespace).setName(request.getName()).build(); + + authorizeCreatePolicyUnderNamespaceOperationOrThrow(op, identifier); + + return LoadPolicyResponse.builder() + .setPolicy( + policyCatalog.createPolicy( + identifier, request.getType(), request.getDescription(), request.getContent())) + .build(); + } + + public LoadPolicyResponse loadPolicy(PolicyIdentifier identifier) { + PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LOAD_POLICY; + authorizeBasicPolicyOperationOrThrow(op, identifier); + + return LoadPolicyResponse.builder().setPolicy(policyCatalog.loadPolicy(identifier)).build(); + } + + public LoadPolicyResponse updatePolicy(PolicyIdentifier identifier, UpdatePolicyRequest request) { + PolarisAuthorizableOperation op = PolarisAuthorizableOperation.UPDATE_POLICY; + authorizeBasicPolicyOperationOrThrow(op, identifier); + + return LoadPolicyResponse.builder() + .setPolicy( + policyCatalog.updatePolicy( + identifier, + request.getDescription(), + request.getContent(), + request.getCurrentPolicyVersion())) + .build(); + } + + public boolean dropPolicy(PolicyIdentifier identifier, boolean detachAll) { + PolarisAuthorizableOperation op = PolarisAuthorizableOperation.DROP_POLICY; + authorizeBasicPolicyOperationOrThrow(op, identifier); + + return policyCatalog.dropPolicy(identifier, detachAll); + } + + private void authorizeCreatePolicyUnderNamespaceOperationOrThrow( + PolarisAuthorizableOperation op, PolicyIdentifier identifier) { + resolutionManifest = + entityManager.prepareResolutionManifest(callContext, securityContext, catalogName); + resolutionManifest.addPath( + new ResolverPath( + Arrays.asList(identifier.getNamespace().levels()), PolarisEntityType.NAMESPACE), + identifier.getNamespace()); + + // 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.identifierToList(identifier.getNamespace(), identifier.getName()), + PolarisEntityType.POLICY, + true /* optional */), + identifier); + resolutionManifest.resolveAll(); + PolarisResolvedPathWrapper target = + resolutionManifest.getResolvedPath(identifier.getNamespace(), true); + if (target == null) { + throw new NoSuchNamespaceException("Namespace does not exist: %s", identifier.getNamespace()); + } + + authorizer.authorizeOrThrow( + authenticatedPrincipal, + resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(), + op, + target, + null /* secondary */); + + initializeCatalog(); + } + + private void authorizeBasicPolicyOperationOrThrow( + PolarisAuthorizableOperation op, PolicyIdentifier identifier) { + resolutionManifest = + entityManager.prepareResolutionManifest(callContext, securityContext, catalogName); + resolutionManifest.addPassthroughPath( + new ResolverPath( + PolarisCatalogHelpers.identifierToList(identifier.getNamespace(), identifier.getName()), + PolarisEntityType.POLICY, + true /* optional */), + identifier); + resolutionManifest.resolveAll(); + + PolarisResolvedPathWrapper target = resolutionManifest.getResolvedPath(identifier, true); + if (target == null) { + throw new NoSuchPolicyException(String.format("Policy does not exist: %s", identifier)); + } + + authorizer.authorizeOrThrow( + authenticatedPrincipal, + resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(), + op, + target, + null /* secondary */); + + initializeCatalog(); + } +} From 70382c2c6edfdd0584f8b347847308fb7a0b5902 Mon Sep 17 00:00:00 2001 From: Honah J Date: Fri, 11 Apr 2025 18:18:53 -0500 Subject: [PATCH 2/5] Fix style --- .../auth/PolarisAuthorizableOperation.java | 62 ++++++++++++++- .../core/auth/PolarisAuthorizerImpl.java | 76 ++++++++++++++++++- 2 files changed, 136 insertions(+), 2 deletions(-) diff --git a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizableOperation.java b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizableOperation.java index 292798cd4f..0957a4e9db 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizableOperation.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizableOperation.java @@ -18,8 +18,68 @@ */ package org.apache.polaris.core.auth; -import static org.apache.polaris.core.entity.PolarisPrivilege.*; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_FOR_GRANTEE; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.POLICY_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.POLICY_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.POLICY_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.POLICY_READ; import static org.apache.polaris.core.entity.PolarisPrivilege.POLICY_WRITE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_MANAGE_GRANTS_FOR_GRANTEE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_RESET_CREDENTIALS; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_MANAGE_GRANTS_FOR_GRANTEE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROTATE_CREDENTIALS; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.SERVICE_MANAGE_ACCESS; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_READ_DATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_WRITE_DATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_WRITE_PROPERTIES; import java.util.EnumSet; import org.apache.polaris.core.entity.PolarisPrivilege; diff --git a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java index a059b3418f..3c13334d25 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java @@ -18,7 +18,81 @@ */ package org.apache.polaris.core.auth; -import static org.apache.polaris.core.entity.PolarisPrivilege.*; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_FULL_METADATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_MANAGE_ACCESS; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_MANAGE_CONTENT; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_MANAGE_METADATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_FULL_METADATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_FOR_GRANTEE; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_USAGE; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_ROLE_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.CATALOG_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_FULL_METADATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.NAMESPACE_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.POLICY_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.POLICY_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.POLICY_FULL_METADATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.POLICY_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.POLICY_READ; +import static org.apache.polaris.core.entity.PolarisPrivilege.POLICY_WRITE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_FULL_METADATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_MANAGE_GRANTS_FOR_GRANTEE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_RESET_CREDENTIALS; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_FULL_METADATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_MANAGE_GRANTS_FOR_GRANTEE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_USAGE; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROLE_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_ROTATE_CREDENTIALS; +import static org.apache.polaris.core.entity.PolarisPrivilege.PRINCIPAL_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.SERVICE_MANAGE_ACCESS; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_FULL_METADATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_READ_DATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_WRITE_DATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.TABLE_WRITE_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_CREATE; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_DROP; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_FULL_METADATA; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_LIST; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_LIST_GRANTS; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_MANAGE_GRANTS_ON_SECURABLE; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_READ_PROPERTIES; +import static org.apache.polaris.core.entity.PolarisPrivilege.VIEW_WRITE_PROPERTIES; import com.google.common.base.Preconditions; import com.google.common.collect.HashMultimap; From f0601f4143cd8bb60807b704aed69411c7b351a5 Mon Sep 17 00:00:00 2001 From: Honah J Date: Wed, 16 Apr 2025 16:16:34 -0700 Subject: [PATCH 3/5] Address review comments --- .../apache/polaris/core/auth/PolarisAuthorizerImpl.java | 4 +++- .../quarkus/catalog/PolicyCatalogHandlerAuthzTest.java | 8 +++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java index 3c13334d25..abeefa2b6a 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java @@ -464,7 +464,7 @@ public class PolarisAuthorizerImpl implements PolarisAuthorizer { CATALOG_ROLE_MANAGE_GRANTS_FOR_GRANTEE, List.of(CATALOG_ROLE_MANAGE_GRANTS_FOR_GRANTEE, CATALOG_MANAGE_ACCESS)); - // Policy + // Policy privileges SUPER_PRIVILEGES.putAll( POLICY_CREATE, List.of( @@ -490,6 +490,8 @@ public class PolarisAuthorizerImpl implements PolarisAuthorizer { List.of( POLICY_LIST, POLICY_CREATE, + POLICY_READ, + POLICY_WRITE, POLICY_FULL_METADATA, CATALOG_MANAGE_METADATA, CATALOG_MANAGE_CONTENT)); diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/PolicyCatalogHandlerAuthzTest.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/PolicyCatalogHandlerAuthzTest.java index 464f7ff3b4..74a54b807c 100644 --- a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/PolicyCatalogHandlerAuthzTest.java +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/PolicyCatalogHandlerAuthzTest.java @@ -145,6 +145,8 @@ public void testListPoliciesAllSufficientPrivileges() { List.of( PolarisPrivilege.POLICY_LIST, PolarisPrivilege.POLICY_CREATE, + PolarisPrivilege.POLICY_WRITE, + PolarisPrivilege.POLICY_READ, PolarisPrivilege.POLICY_FULL_METADATA, PolarisPrivilege.CATALOG_MANAGE_CONTENT), () -> newWrapper().listPolicies(NS1, null), @@ -154,11 +156,7 @@ public void testListPoliciesAllSufficientPrivileges() { @Test public void testListPoliciesInsufficientPrivileges() { doTestInsufficientPrivileges( - List.of( - PolarisPrivilege.NAMESPACE_FULL_METADATA, - PolarisPrivilege.POLICY_READ, - PolarisPrivilege.POLICY_DROP, - PolarisPrivilege.POLICY_WRITE), + List.of(PolarisPrivilege.NAMESPACE_FULL_METADATA, PolarisPrivilege.POLICY_DROP), () -> newWrapper().listPolicies(NS1, null)); } From 428391cf220f0a8a9713b6a18faae5ef51f179a2 Mon Sep 17 00:00:00 2001 From: Honah J Date: Wed, 16 Apr 2025 16:55:14 -0700 Subject: [PATCH 4/5] Address review comments 2 --- .../catalog/common/CatalogHandler.java | 18 +++++++- .../iceberg/IcebergCatalogHandler.java | 2 +- .../catalog/policy/PolicyCatalogHandler.java | 45 ++----------------- 3 files changed, 21 insertions(+), 44 deletions(-) diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java b/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java index 3de4321d4f..9c8f1913f3 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java @@ -43,6 +43,7 @@ import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest; import org.apache.polaris.core.persistence.resolver.ResolverPath; import org.apache.polaris.core.persistence.resolver.ResolverStatus; +import org.apache.polaris.service.types.PolicyIdentifier; /** * An ABC for catalog wrappers which provides authorize methods that should be called before a @@ -89,14 +90,15 @@ public CatalogHandler( protected void authorizeBasicNamespaceOperationOrThrow( PolarisAuthorizableOperation op, Namespace namespace) { - authorizeBasicNamespaceOperationOrThrow(op, namespace, null, null); + authorizeBasicNamespaceOperationOrThrow(op, namespace, null, null, null); } protected void authorizeBasicNamespaceOperationOrThrow( PolarisAuthorizableOperation op, Namespace namespace, List extraPassthroughNamespaces, - List extraPassthroughTableLikes) { + List extraPassthroughTableLikes, + List extraPassThroughPolicies) { resolutionManifest = entityManager.prepareResolutionManifest(callContext, securityContext, catalogName); resolutionManifest.addPath( @@ -121,6 +123,18 @@ protected void authorizeBasicNamespaceOperationOrThrow( id); } } + + if (extraPassThroughPolicies != null) { + for (PolicyIdentifier id : extraPassThroughPolicies) { + resolutionManifest.addPassthroughPath( + new ResolverPath( + PolarisCatalogHelpers.identifierToList(id.getNamespace(), id.getName()), + PolarisEntityType.POLICY, + true /* optional */), + id); + } + } + resolutionManifest.resolveAll(); PolarisResolvedPathWrapper target = resolutionManifest.getResolvedPath(namespace, true); if (target == null) { diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java b/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java index 716d68fdbc..c4105ddba8 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java @@ -506,7 +506,7 @@ public boolean sendNotification(TableIdentifier identifier, NotificationRequest extraPassthroughNamespaces.add(nsLevel); } authorizeBasicNamespaceOperationOrThrow( - op, Namespace.empty(), extraPassthroughNamespaces, extraPassthroughTableLikes); + op, Namespace.empty(), extraPassthroughNamespaces, extraPassthroughTableLikes, null); CatalogEntity catalog = CatalogEntity.of( diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java index 3670e019d9..1eceb362d2 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java @@ -19,10 +19,9 @@ package org.apache.polaris.service.catalog.policy; import jakarta.ws.rs.core.SecurityContext; -import java.util.Arrays; import java.util.HashSet; +import java.util.List; import org.apache.iceberg.catalog.Namespace; -import org.apache.iceberg.exceptions.NoSuchNamespaceException; import org.apache.polaris.core.auth.PolarisAuthorizableOperation; import org.apache.polaris.core.auth.PolarisAuthorizer; import org.apache.polaris.core.catalog.PolarisCatalogHelpers; @@ -77,7 +76,9 @@ public LoadPolicyResponse createPolicy(Namespace namespace, CreatePolicyRequest PolicyIdentifier identifier = PolicyIdentifier.builder().setNamespace(namespace).setName(request.getName()).build(); - authorizeCreatePolicyUnderNamespaceOperationOrThrow(op, identifier); + // authorize the creating policy under namespace operation + authorizeBasicNamespaceOperationOrThrow( + op, identifier.getNamespace(), null, null, List.of(identifier)); return LoadPolicyResponse.builder() .setPolicy( @@ -114,44 +115,6 @@ public boolean dropPolicy(PolicyIdentifier identifier, boolean detachAll) { return policyCatalog.dropPolicy(identifier, detachAll); } - private void authorizeCreatePolicyUnderNamespaceOperationOrThrow( - PolarisAuthorizableOperation op, PolicyIdentifier identifier) { - resolutionManifest = - entityManager.prepareResolutionManifest(callContext, securityContext, catalogName); - resolutionManifest.addPath( - new ResolverPath( - Arrays.asList(identifier.getNamespace().levels()), PolarisEntityType.NAMESPACE), - identifier.getNamespace()); - - // 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.identifierToList(identifier.getNamespace(), identifier.getName()), - PolarisEntityType.POLICY, - true /* optional */), - identifier); - resolutionManifest.resolveAll(); - PolarisResolvedPathWrapper target = - resolutionManifest.getResolvedPath(identifier.getNamespace(), true); - if (target == null) { - throw new NoSuchNamespaceException("Namespace does not exist: %s", identifier.getNamespace()); - } - - authorizer.authorizeOrThrow( - authenticatedPrincipal, - resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(), - op, - target, - null /* secondary */); - - initializeCatalog(); - } - private void authorizeBasicPolicyOperationOrThrow( PolarisAuthorizableOperation op, PolicyIdentifier identifier) { resolutionManifest = From 1c21810d97007f38d538e6d801879df7ff5a27b3 Mon Sep 17 00:00:00 2001 From: Honah J Date: Fri, 18 Apr 2025 11:05:35 -0700 Subject: [PATCH 5/5] fix nit --- .../polaris/service/quarkus/admin/PolarisAuthzTestBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java index 3a7af2531b..10972b602c 100644 --- a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java @@ -75,9 +75,9 @@ import org.apache.polaris.core.persistence.dao.entity.EntityResult; import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest; import org.apache.polaris.core.persistence.transactional.TransactionalPersistence; +import org.apache.polaris.core.policy.PredefinedPolicyTypes; import org.apache.polaris.core.secrets.UserSecretsManager; import org.apache.polaris.core.secrets.UserSecretsManagerFactory; -import org.apache.polaris.core.policy.PredefinedPolicyTypes; import org.apache.polaris.service.admin.PolarisAdminService; import org.apache.polaris.service.catalog.PolarisPassthroughResolutionView; import org.apache.polaris.service.catalog.generic.GenericTableCatalog;