This is an automated email from the ASF dual-hosted git repository. yiguolei pushed a commit to branch branch-4.2 in repository https://gitbox.apache.org/repos/asf/doris.git
commit 2f9f1e253fa924815caf2642a372c66dfdd2ab7e Author: Calvin Kirs <[email protected]> AuthorDate: Tue Sep 29 23:35:34 2026 +0800 branch-4.1: [fix](auth) Check column privileges held by grantor in GRANT/REVOKE (#68629) https://github.com/apache/doris/pull/68555 --- .../plans/commands/GrantTablePrivilegeCommand.java | 25 ++++- .../commands/GrantTablePrivilegeCommandTest.java | 87 ++++++++++++++++ .../data/account_p0/test_grant_col_priv.out | 7 ++ .../suites/account_p0/test_grant_col_priv.groovy | 109 +++++++++++++++++++++ 4 files changed, 226 insertions(+), 2 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommand.java index 16169ef8326..0672cf57be5 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommand.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommand.java @@ -27,6 +27,7 @@ import org.apache.doris.common.Config; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; import org.apache.doris.common.FeNameFormat; +import org.apache.doris.common.UserException; import org.apache.doris.mysql.privilege.AccessControllerManager; import org.apache.doris.mysql.privilege.Auth; import org.apache.doris.mysql.privilege.ColPrivilegeKey; @@ -133,6 +134,7 @@ public class GrantTablePrivilegeCommand extends Command implements ForwardWithSy * 3. Only the user with NODE_PRIV can grant NODE_PRIV to other user * 4. Check that the current user has both grant_priv and the permissions to be assigned to others * 5. col priv must assign to specific table + * 6. Check that the current user has the col privs to be assigned to others, on the table or on each col */ public static void checkTablePrivileges(Collection<Privilege> privileges, TablePattern tblPattern, Map<ColPrivilegeKey, Set<String>> colPrivileges) throws AnalysisException { @@ -153,8 +155,8 @@ public class GrantTablePrivilegeCommand extends Command implements ForwardWithSy // Rule 4 PrivPredicate predicate = getPrivPredicate(privileges); AccessControllerManager accessManager = Env.getCurrentEnv().getAccessManager(); - if (!accessManager.checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN) - && !checkTablePriv(ConnectContext.get(), predicate, tblPattern)) { + boolean isAdmin = accessManager.checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN); + if (!isAdmin && !checkTablePriv(ConnectContext.get(), predicate, tblPattern)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ALL_ACCESS_DENIED_ERROR, predicate.getPrivs().toPrivilegeList()); } @@ -163,6 +165,25 @@ public class GrantTablePrivilegeCommand extends Command implements ForwardWithSy if (!MapUtils.isEmpty(colPrivileges) && "*".equals(tblPattern.getTbl())) { throw new AnalysisException("Col auth must specify specific table"); } + + // Rule 6 + if (!isAdmin) { + checkColPrivs(ConnectContext.get(), colPrivileges); + } + } + + private static void checkColPrivs(ConnectContext ctx, Map<ColPrivilegeKey, Set<String>> colPrivileges) + throws AnalysisException { + AccessControllerManager accessManager = Env.getCurrentEnv().getAccessManager(); + for (Map.Entry<ColPrivilegeKey, Set<String>> entry : colPrivileges.entrySet()) { + ColPrivilegeKey key = entry.getKey(); + PrivPredicate wanted = PrivPredicate.of(PrivBitSet.of(key.getPrivilege()), CompoundPredicate.Operator.OR); + try { + accessManager.checkColumnsPriv(ctx, key.getCtl(), key.getDb(), key.getTbl(), entry.getValue(), wanted); + } catch (UserException e) { + throw new AnalysisException(e.getMessage(), e); + } + } } private static PrivPredicate getPrivPredicate(Collection<Privilege> privileges) { diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommandTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommandTest.java index bd022f4e2af..65f80e75732 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommandTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommandTest.java @@ -18,8 +18,10 @@ package org.apache.doris.nereids.trees.plans.commands; import org.apache.doris.analysis.TablePattern; +import org.apache.doris.analysis.UserIdentity; import org.apache.doris.catalog.AccessPrivilege; import org.apache.doris.catalog.AccessPrivilegeWithCols; +import org.apache.doris.common.AnalysisException; import org.apache.doris.common.DdlException; import org.apache.doris.nereids.parser.NereidsParser; import org.apache.doris.nereids.trees.plans.logical.LogicalPlan; @@ -104,4 +106,89 @@ public class GrantTablePrivilegeCommandTest extends TestWithFeService { Assertions.assertTrue(plan instanceof GrantTablePrivilegeCommand); Assertions.assertThrows(DdlException.class, () -> ((GrantTablePrivilegeCommand) plan).run(connectContext, null)); } + + @Test + public void testGrantColPrivWithOnlyGrantPriv() throws Exception { + addUser("col_grantor1", true); + addUser("col_target1", true); + grantPriv("GRANT GRANT_PRIV ON test.test_table TO 'col_grantor1'"); + try { + useUser("col_grantor1"); + Assertions.assertThrows(AnalysisException.class, + () -> runCommand("GRANT SELECT_PRIV(k1) ON test.test_table TO 'col_target1'")); + Assertions.assertThrows(AnalysisException.class, + () -> runCommand("GRANT SELECT_PRIV(k1, k2) ON test.test_table TO 'col_grantor1'")); + Assertions.assertThrows(AnalysisException.class, + () -> runCommand("REVOKE SELECT_PRIV(k1) ON test.test_table FROM 'col_target1'")); + } finally { + connectContext.setCurrentUserIdentity(UserIdentity.ROOT); + } + } + + @Test + public void testGrantColPrivWithColSelectPriv() throws Exception { + addUser("col_grantor2", true); + addUser("col_target2", true); + grantPriv("GRANT GRANT_PRIV, SELECT_PRIV(k1) ON test.test_table TO 'col_grantor2'"); + try { + useUser("col_grantor2"); + Assertions.assertDoesNotThrow( + () -> runCommand("GRANT SELECT_PRIV(k1) ON test.test_table TO 'col_target2'")); + AnalysisException e = Assertions.assertThrows(AnalysisException.class, + () -> runCommand("GRANT SELECT_PRIV(k2) ON test.test_table TO 'col_target2'")); + Assertions.assertTrue(e.getMessage().contains("k2"), e.getMessage()); + Assertions.assertThrows(AnalysisException.class, + () -> runCommand("GRANT SELECT_PRIV(k1, k2) ON test.test_table TO 'col_target2'")); + Assertions.assertThrows(AnalysisException.class, + () -> runCommand("REVOKE SELECT_PRIV(k2) ON test.test_table FROM 'col_target2'")); + Assertions.assertDoesNotThrow( + () -> runCommand("REVOKE SELECT_PRIV(k1) ON test.test_table FROM 'col_target2'")); + } finally { + connectContext.setCurrentUserIdentity(UserIdentity.ROOT); + } + } + + @Test + public void testGrantColPrivWithTableSelectPriv() throws Exception { + addUser("col_grantor3", true); + addUser("col_target3", true); + grantPriv("GRANT GRANT_PRIV, SELECT_PRIV ON test.test_table TO 'col_grantor3'"); + try { + useUser("col_grantor3"); + Assertions.assertDoesNotThrow( + () -> runCommand("GRANT SELECT_PRIV(k1, k2) ON test.test_table TO 'col_target3'")); + Assertions.assertDoesNotThrow( + () -> runCommand("REVOKE SELECT_PRIV(k2) ON test.test_table FROM 'col_target3'")); + } finally { + connectContext.setCurrentUserIdentity(UserIdentity.ROOT); + } + } + + @Test + public void testGrantColPrivWithDbGrantPriv() throws Exception { + addUser("col_grantor4", true); + addUser("col_target4", true); + grantPriv("GRANT GRANT_PRIV ON test.* TO 'col_grantor4'"); + try { + useUser("col_grantor4"); + Assertions.assertThrows(AnalysisException.class, + () -> runCommand("GRANT SELECT_PRIV(k1) ON test.test_table TO 'col_target4'")); + } finally { + connectContext.setCurrentUserIdentity(UserIdentity.ROOT); + } + } + + @Test + public void testGrantColPrivByAdmin() throws Exception { + addUser("col_target5", true); + Assertions.assertDoesNotThrow( + () -> runCommand("GRANT SELECT_PRIV(k1) ON test.test_table TO 'col_target5'")); + Assertions.assertDoesNotThrow( + () -> runCommand("REVOKE SELECT_PRIV(k1) ON test.test_table FROM 'col_target5'")); + } + + private void runCommand(String sql) throws Exception { + LogicalPlan plan = new NereidsParser().parseSingle(sql); + ((Command) plan).run(connectContext, null); + } } diff --git a/regression-test/data/account_p0/test_grant_col_priv.out b/regression-test/data/account_p0/test_grant_col_priv.out new file mode 100644 index 00000000000..e26ff33084a --- /dev/null +++ b/regression-test/data/account_p0/test_grant_col_priv.out @@ -0,0 +1,7 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !select_c1 -- +1 + +-- !select_c1_c2 -- +1 2 + diff --git a/regression-test/suites/account_p0/test_grant_col_priv.groovy b/regression-test/suites/account_p0/test_grant_col_priv.groovy new file mode 100644 index 00000000000..fb19a7a7ca8 --- /dev/null +++ b/regression-test/suites/account_p0/test_grant_col_priv.groovy @@ -0,0 +1,109 @@ +// 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. + +suite("test_grant_col_priv") { + String pwd = 'C123_567p' + + try_sql("drop user test_grant_col_priv_grantor") + try_sql("drop user test_grant_col_priv_target") + sql """drop database if exists test_grant_col_priv_db""" + + sql """create database test_grant_col_priv_db""" + sql """ + create table test_grant_col_priv_db.test_grant_col_priv_tbl (c1 int, c2 int) + duplicate key(c1) + distributed by hash(c1) buckets 1 + properties ("replication_num" = "1") + """ + sql """insert into test_grant_col_priv_db.test_grant_col_priv_tbl values (1, 2)""" + + sql """create user 'test_grant_col_priv_grantor' identified by '${pwd}'""" + sql """create user 'test_grant_col_priv_target' identified by '${pwd}'""" + //cloud-mode + if (isCloudMode()) { + def clusters = sql " SHOW CLUSTERS; " + assertTrue(!clusters.isEmpty()) + def validCluster = clusters[0][0] + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO test_grant_col_priv_grantor""" + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO test_grant_col_priv_target""" + } + // for login + sql """grant select_priv on regression_test to test_grant_col_priv_grantor""" + sql """grant select_priv on regression_test to test_grant_col_priv_target""" + + // have grant_priv only, can not grant col select_priv + sql """grant grant_priv on test_grant_col_priv_db.test_grant_col_priv_tbl to test_grant_col_priv_grantor""" + connect('test_grant_col_priv_grantor', "${pwd}", context.config.jdbcUrl) { + test { + sql """grant select_priv(c1) on test_grant_col_priv_db.test_grant_col_priv_tbl to test_grant_col_priv_target""" + exception "denied" + } + test { + sql """grant select_priv(c1, c2) on test_grant_col_priv_db.test_grant_col_priv_tbl to test_grant_col_priv_grantor""" + exception "denied" + } + } + connect('test_grant_col_priv_target', "${pwd}", context.config.jdbcUrl) { + test { + sql """select c1 from test_grant_col_priv_db.test_grant_col_priv_tbl""" + exception "denied" + } + } + + // have grant_priv and select_priv on c1, can grant/revoke select_priv on c1 but not on c2 + sql """grant select_priv(c1) on test_grant_col_priv_db.test_grant_col_priv_tbl to test_grant_col_priv_grantor""" + connect('test_grant_col_priv_grantor', "${pwd}", context.config.jdbcUrl) { + sql """grant select_priv(c1) on test_grant_col_priv_db.test_grant_col_priv_tbl to test_grant_col_priv_target""" + test { + sql """grant select_priv(c2) on test_grant_col_priv_db.test_grant_col_priv_tbl to test_grant_col_priv_target""" + exception "denied" + } + test { + sql """grant select_priv(c1, c2) on test_grant_col_priv_db.test_grant_col_priv_tbl to test_grant_col_priv_target""" + exception "denied" + } + test { + sql """revoke select_priv(c2) on test_grant_col_priv_db.test_grant_col_priv_tbl from test_grant_col_priv_target""" + exception "denied" + } + } + connect('test_grant_col_priv_target', "${pwd}", context.config.jdbcUrl) { + order_qt_select_c1 """select c1 from test_grant_col_priv_db.test_grant_col_priv_tbl""" + test { + sql """select c2 from test_grant_col_priv_db.test_grant_col_priv_tbl""" + exception "denied" + } + } + connect('test_grant_col_priv_grantor', "${pwd}", context.config.jdbcUrl) { + sql """revoke select_priv(c1) on test_grant_col_priv_db.test_grant_col_priv_tbl from test_grant_col_priv_target""" + } + connect('test_grant_col_priv_target', "${pwd}", context.config.jdbcUrl) { + test { + sql """select c1 from test_grant_col_priv_db.test_grant_col_priv_tbl""" + exception "denied" + } + } + + // have grant_priv and select_priv on the table, can grant select_priv on any col + sql """grant select_priv on test_grant_col_priv_db.test_grant_col_priv_tbl to test_grant_col_priv_grantor""" + connect('test_grant_col_priv_grantor', "${pwd}", context.config.jdbcUrl) { + sql """grant select_priv(c1, c2) on test_grant_col_priv_db.test_grant_col_priv_tbl to test_grant_col_priv_target""" + } + connect('test_grant_col_priv_target', "${pwd}", context.config.jdbcUrl) { + order_qt_select_c1_c2 """select c1, c2 from test_grant_col_priv_db.test_grant_col_priv_tbl""" + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
