This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new 08fc786e280 branch-4.1: [fix](auth) Reject privilege object names with
more than three parts in GRANT/REVOKE #68660 (#68682)
08fc786e280 is described below
commit 08fc786e280fb439a4a198d1fe41ae2761059e71
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Sep 30 22:04:20 2026 +0800
branch-4.1: [fix](auth) Reject privilege object names with more than three
parts in GRANT/REVOKE #68660 (#68682)
Cherry-picked from #68660
Co-authored-by: Calvin Kirs <[email protected]>
---
.../org/apache/doris/mysql/privilege/Auth.java | 10 ++--
.../doris/nereids/parser/LogicalPlanBuilder.java | 68 +++++++---------------
.../commands/RevokeTablePrivilegeCommand.java | 13 ++---
.../commands/GrantTablePrivilegeCommandTest.java | 29 +++++++++
.../commands/RevokeTablePrivilegeCommandTest.java | 29 +++++++++
.../test_grant_revoke_object_name.groovy | 51 ++++++++++++++++
6 files changed, 139 insertions(+), 61 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
index ce3faecc1ca..f41d82013a8 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
@@ -912,12 +912,10 @@ public class Auth implements Writable {
// revoke table
public void revokeTablePrivilegeCommand(RevokeTablePrivilegeCommand
command) throws DdlException {
- if (command.getTablePattern() != null) {
- PrivBitSet privs = PrivBitSet.of(command.getPrivileges());
- revokeInternal(command.getUserIdentity().orElse(null),
command.getRole().orElse(null),
- command.getTablePattern(), privs,
command.getColPrivileges(),
- true /* err on non exist */, false /* is replay */);
- }
+ PrivBitSet privs = PrivBitSet.of(command.getPrivileges());
+ revokeInternal(command.getUserIdentity().orElse(null),
command.getRole().orElse(null),
+ command.getTablePattern(), privs, command.getColPrivileges(),
+ true /* err on non exist */, false /* is replay */);
}
public void replayRevoke(PrivInfo info) {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index ff460b3cb19..e0c4518bbc2 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -9145,35 +9145,28 @@ public class LogicalPlanBuilder extends
DorisParserBaseVisitor<Object> {
return new TransactionRollbackCommand();
}
+ /**
+ * The object of a table privilege is db, db.tbl or ctl.db.tbl;
TablePattern.analyze() checks its wildcards.
+ */
+ private TablePattern
parsePrivilegeTablePattern(DorisParser.MultipartIdentifierOrAsteriskContext
ctx) {
+ List<String> parts = visitMultipartIdentifierOrAsterisk(ctx);
+ switch (parts.size()) {
+ case 1:
+ return new TablePattern(parts.get(0), "");
+ case 2:
+ return new TablePattern(parts.get(0), parts.get(1));
+ case 3:
+ return new TablePattern(parts.get(0), parts.get(1),
parts.get(2));
+ default:
+ throw new ParseException("Privilege object name should be db,
db.tbl or ctl.db.tbl, but got: "
+ + String.join(".", parts), ctx);
+ }
+ }
+
@Override
public LogicalPlan
visitGrantTablePrivilege(DorisParser.GrantTablePrivilegeContext ctx) {
List<AccessPrivilegeWithCols> accessPrivilegeWithCols =
visitPrivilegeList(ctx.privilegeList());
-
- List<String> parts =
visitMultipartIdentifierOrAsterisk(ctx.multipartIdentifierOrAsterisk());
- int size = parts.size();
-
- if (size < 1) {
- throw new AnalysisException("grant table privilege statement
missing parameters");
- }
-
- TablePattern tablePattern = null;
- if (size == 1) {
- String db = parts.get(size - 1);
- tablePattern = new TablePattern(db, "");
- }
-
- if (size == 2) {
- String db = parts.get(size - 2);
- String tbl = parts.get(size - 1);
- tablePattern = new TablePattern(db, tbl);
- }
-
- if (size == 3) {
- String ctl = parts.get(size - 3);
- String db = parts.get(size - 2);
- String tbl = parts.get(size - 1);
- tablePattern = new TablePattern(ctl, db, tbl);
- }
+ TablePattern tablePattern =
parsePrivilegeTablePattern(ctx.multipartIdentifierOrAsterisk());
Optional<UserIdentity> userIdentity = Optional.empty();
Optional<String> role = Optional.empty();
@@ -9319,28 +9312,7 @@ public class LogicalPlanBuilder extends
DorisParserBaseVisitor<Object> {
@Override
public LogicalPlan
visitRevokeTablePrivilege(DorisParser.RevokeTablePrivilegeContext ctx) {
List<AccessPrivilegeWithCols> accessPrivilegeWithCols =
visitPrivilegeList(ctx.privilegeList());
-
- List<String> parts =
visitMultipartIdentifierOrAsterisk(ctx.multipartIdentifierOrAsterisk());
- int size = parts.size();
-
- TablePattern tablePattern = null;
- if (size == 1) {
- String db = parts.get(size - 1);
- tablePattern = new TablePattern(db, "");
- }
-
- if (size == 2) {
- String db = parts.get(size - 2);
- String tbl = parts.get(size - 1);
- tablePattern = new TablePattern(db, tbl);
- }
-
- if (size == 3) {
- String ctl = parts.get(size - 3);
- String db = parts.get(size - 2);
- String tbl = parts.get(size - 1);
- tablePattern = new TablePattern(ctl, db, tbl);
- }
+ TablePattern tablePattern =
parsePrivilegeTablePattern(ctx.multipartIdentifierOrAsterisk());
Optional<UserIdentity> userIdentity = Optional.empty();
Optional<String> role = Optional.empty();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/RevokeTablePrivilegeCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/RevokeTablePrivilegeCommand.java
index 1748d6bd57b..5213df7cf01 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/RevokeTablePrivilegeCommand.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/RevokeTablePrivilegeCommand.java
@@ -38,6 +38,7 @@ import org.apache.commons.collections4.MapUtils;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -59,10 +60,10 @@ public class RevokeTablePrivilegeCommand extends Command
implements ForwardWithS
public RevokeTablePrivilegeCommand(List<AccessPrivilegeWithCols>
accessPrivileges, TablePattern tablePattern,
Optional<UserIdentity> userIdentity, Optional<String> role) {
super(PlanType.REVOKE_TABLE_PRIVILEGE_COMMAND);
- this.accessPrivileges = accessPrivileges;
- this.tablePattern = tablePattern;
- this.userIdentity = userIdentity;
- this.role = role;
+ this.accessPrivileges = Objects.requireNonNull(accessPrivileges,
"accessPrivileges is null");
+ this.tablePattern = Objects.requireNonNull(tablePattern, "tablePattern
is null");
+ this.userIdentity = Objects.requireNonNull(userIdentity, "userIdentity
is null");
+ this.role = Objects.requireNonNull(role, "role is null");
}
@Override
@@ -100,9 +101,7 @@ public class RevokeTablePrivilegeCommand extends Command
implements ForwardWithS
}
// Revoke operation obey the same rule as Grant operation. reuse the
same method
- if (tablePattern != null) {
- GrantTablePrivilegeCommand.checkTablePrivileges(privileges,
tablePattern, colPrivileges);
- }
+ GrantTablePrivilegeCommand.checkTablePrivileges(privileges,
tablePattern, colPrivileges);
}
@Override
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 65f80e75732..9683ed5b161 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
@@ -23,6 +23,7 @@ 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.exceptions.ParseException;
import org.apache.doris.nereids.parser.NereidsParser;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
import org.apache.doris.utframe.TestWithFeService;
@@ -191,4 +192,32 @@ public class GrantTablePrivilegeCommandTest extends
TestWithFeService {
LogicalPlan plan = new NereidsParser().parseSingle(sql);
((Command) plan).run(connectContext, null);
}
+
+ @Test
+ public void testObjectName() {
+ NereidsParser nereidsParser = new NereidsParser();
+ String[][] cases = {
+ {"GRANT SELECT_PRIV ON test TO 'jack'", "test.*"},
+ {"GRANT SELECT_PRIV ON test.test_table TO 'jack'",
"test.test_table"},
+ {"GRANT SELECT_PRIV ON internal.test.test_table TO 'jack'",
"internal.test.test_table"},
+ };
+ for (String[] c : cases) {
+ LogicalPlan plan = nereidsParser.parseSingle(c[0]);
+ Assertions.assertTrue(plan instanceof GrantTablePrivilegeCommand,
c[0]);
+ Assertions.assertEquals(c[1], ((GrantTablePrivilegeCommand)
plan).getTablePattern().toString(), c[0]);
+ }
+ }
+
+ @Test
+ public void testObjectNameWithTooManyParts() {
+ NereidsParser nereidsParser = new NereidsParser();
+ for (String name : new String[] {"a.b.c.d", "*.*.*.*", "a.b.c.d.e"}) {
+ String sql = "GRANT SELECT_PRIV ON " + name + " TO 'jack'";
+ ParseException exception =
Assertions.assertThrows(ParseException.class,
+ () -> nereidsParser.parseSingle(sql), sql);
+ Assertions.assertTrue(exception.getMessage().contains(
+ "Privilege object name should be db, db.tbl or ctl.db.tbl,
but got: " + name),
+ exception.getMessage());
+ }
+ }
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/RevokeTablePrivilegeCommandTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/RevokeTablePrivilegeCommandTest.java
index 9c04d120eb1..06738995913 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/RevokeTablePrivilegeCommandTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/RevokeTablePrivilegeCommandTest.java
@@ -20,6 +20,7 @@ package org.apache.doris.nereids.trees.plans.commands;
import org.apache.doris.analysis.TablePattern;
import org.apache.doris.catalog.AccessPrivilege;
import org.apache.doris.catalog.AccessPrivilegeWithCols;
+import org.apache.doris.nereids.exceptions.ParseException;
import org.apache.doris.nereids.parser.NereidsParser;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
import org.apache.doris.utframe.TestWithFeService;
@@ -95,4 +96,32 @@ public class RevokeTablePrivilegeCommandTest extends
TestWithFeService {
Assertions.assertTrue(revokeplan2 instanceof
RevokeTablePrivilegeCommand);
Assertions.assertDoesNotThrow(() -> ((RevokeTablePrivilegeCommand)
revokeplan2).run(connectContext, null));
}
+
+ @Test
+ public void testObjectName() {
+ NereidsParser nereidsParser = new NereidsParser();
+ String[][] cases = {
+ {"REVOKE SELECT_PRIV ON test FROM 'jack'", "test.*"},
+ {"REVOKE SELECT_PRIV ON test.test_table FROM 'jack'",
"test.test_table"},
+ {"REVOKE SELECT_PRIV ON internal.test.test_table FROM 'jack'",
"internal.test.test_table"},
+ };
+ for (String[] c : cases) {
+ LogicalPlan plan = nereidsParser.parseSingle(c[0]);
+ Assertions.assertTrue(plan instanceof RevokeTablePrivilegeCommand,
c[0]);
+ Assertions.assertEquals(c[1], ((RevokeTablePrivilegeCommand)
plan).getTablePattern().toString(), c[0]);
+ }
+ }
+
+ @Test
+ public void testObjectNameWithTooManyParts() {
+ NereidsParser nereidsParser = new NereidsParser();
+ for (String name : new String[] {"a.b.c.d", "*.*.*.*", "a.b.c.d.e"}) {
+ String sql = "REVOKE SELECT_PRIV ON " + name + " FROM 'jack'";
+ ParseException exception =
Assertions.assertThrows(ParseException.class,
+ () -> nereidsParser.parseSingle(sql), sql);
+ Assertions.assertTrue(exception.getMessage().contains(
+ "Privilege object name should be db, db.tbl or ctl.db.tbl,
but got: " + name),
+ exception.getMessage());
+ }
+ }
}
diff --git
a/regression-test/suites/account_p0/test_grant_revoke_object_name.groovy
b/regression-test/suites/account_p0/test_grant_revoke_object_name.groovy
new file mode 100644
index 00000000000..6801a383dd0
--- /dev/null
+++ b/regression-test/suites/account_p0/test_grant_revoke_object_name.groovy
@@ -0,0 +1,51 @@
+// 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_revoke_object_name") {
+ sql """DROP USER IF EXISTS 'test_grant_revoke_object_name_user'"""
+ sql """DROP ROLE IF EXISTS test_grant_revoke_object_name_role"""
+ sql """DROP DATABASE IF EXISTS test_grant_revoke_object_name_db"""
+ sql """CREATE DATABASE test_grant_revoke_object_name_db"""
+ sql """CREATE USER 'test_grant_revoke_object_name_user'"""
+ sql """CREATE ROLE test_grant_revoke_object_name_role"""
+
+ // db, db.tbl and ctl.db.tbl are the only object name shapes a table
privilege takes
+ sql """GRANT SELECT_PRIV ON test_grant_revoke_object_name_db TO
'test_grant_revoke_object_name_user'"""
+ sql """REVOKE SELECT_PRIV ON test_grant_revoke_object_name_db FROM
'test_grant_revoke_object_name_user'"""
+ sql """GRANT SELECT_PRIV ON test_grant_revoke_object_name_db.* TO
'test_grant_revoke_object_name_user'"""
+ sql """REVOKE SELECT_PRIV ON test_grant_revoke_object_name_db.* FROM
'test_grant_revoke_object_name_user'"""
+ sql """GRANT SELECT_PRIV ON internal.test_grant_revoke_object_name_db.* TO
'test_grant_revoke_object_name_user'"""
+ sql """REVOKE SELECT_PRIV ON internal.test_grant_revoke_object_name_db.*
FROM 'test_grant_revoke_object_name_user'"""
+
+ // four or more parts are rejected while parsing, for both a user and a
role
+ test {
+ sql """GRANT SELECT_PRIV ON a.b.c.d TO
'test_grant_revoke_object_name_user'"""
+ exception "Privilege object name should be db, db.tbl or ctl.db.tbl,
but got: a.b.c.d"
+ }
+ test {
+ sql """REVOKE SELECT_PRIV ON a.b.c.d FROM
'test_grant_revoke_object_name_user'"""
+ exception "Privilege object name should be db, db.tbl or ctl.db.tbl,
but got: a.b.c.d"
+ }
+ test {
+ sql """GRANT SELECT_PRIV ON
internal.test_grant_revoke_object_name_db.t.c TO ROLE
test_grant_revoke_object_name_role"""
+ exception "Privilege object name should be db, db.tbl or ctl.db.tbl,
but got: internal.test_grant_revoke_object_name_db.t.c"
+ }
+ test {
+ sql """REVOKE SELECT_PRIV ON *.*.*.* FROM ROLE
test_grant_revoke_object_name_role"""
+ exception "Privilege object name should be db, db.tbl or ctl.db.tbl,
but got: *.*.*.*"
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]