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 bd6fecbc979e5cb56cd72324cb452e8b1bbb52f3 Author: Calvin Kirs <[email protected]> AuthorDate: Tue Sep 29 23:35:06 2026 +0800 branch-4.1: [fix](auth) Treat only root@'%' as root in SET PASSWORD and ALTER USER (#68634) https://github.com/apache/doris/pull/68559 --- .../trees/plans/commands/info/AlterUserInfo.java | 6 +- .../trees/plans/commands/info/SetPassVarOp.java | 3 +- .../doris/mysql/privilege/SystemRootUserTest.java | 108 +++++++++++++++++++++ .../suites/account_p0/test_system_user.groovy | 31 ++++++ 4 files changed, 141 insertions(+), 7 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/AlterUserInfo.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/AlterUserInfo.java index bf0a1aa060b..34544f5fe53 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/AlterUserInfo.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/AlterUserInfo.java @@ -23,12 +23,10 @@ import org.apache.doris.analysis.TlsOptions; import org.apache.doris.analysis.UserDesc; import org.apache.doris.analysis.UserIdentity; import org.apache.doris.catalog.Env; -import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; import org.apache.doris.common.UserException; -import org.apache.doris.mysql.privilege.Auth; import org.apache.doris.mysql.privilege.PasswordPolicy; import org.apache.doris.mysql.privilege.PrivPredicate; import org.apache.doris.qe.ConnectContext; @@ -132,9 +130,7 @@ public class AlterUserInfo { + "actual number of type is " + ops.size()); } - if (userDesc.getUserIdent().getQualifiedUser().equals(Auth.ROOT_USER) - && !ClusterNamespace.getNameFromFullName(ConnectContext.get().getQualifiedUser()) - .equals(Auth.ROOT_USER)) { + if (userDesc.getUserIdent().isRootUser() && !ConnectContext.get().getCurrentUserIdentity().isRootUser()) { throw new AnalysisException("Only root user can modify root user"); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/SetPassVarOp.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/SetPassVarOp.java index f142b9679a2..0453ffeb52c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/SetPassVarOp.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/SetPassVarOp.java @@ -25,7 +25,6 @@ import org.apache.doris.common.AnalysisException; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; import org.apache.doris.common.UserException; -import org.apache.doris.mysql.privilege.Auth; import org.apache.doris.mysql.privilege.PrivPredicate; import org.apache.doris.qe.ConnectContext; @@ -69,7 +68,7 @@ public class SetPassVarOp extends SetVarOp { } // 2. No user can set password for root expect for root user itself - if (userIdent.getQualifiedUser().equals(Auth.ROOT_USER)) { + if (userIdent.isRootUser()) { throw new AnalysisException("Can not set password for root user, except root itself"); } diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/SystemRootUserTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/SystemRootUserTest.java new file mode 100644 index 00000000000..d60df7107d2 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/SystemRootUserTest.java @@ -0,0 +1,108 @@ +// 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.doris.mysql.privilege; + +import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.catalog.Env; +import org.apache.doris.nereids.parser.NereidsParser; +import org.apache.doris.nereids.trees.plans.commands.Command; +import org.apache.doris.utframe.TestWithFeService; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Only the full identity root@'%' is the system root user. An account created as root@'<host>' is an + * ordinary account: a user with GRANT privilege manages it like any other account, and it has no more + * say over root@'%' than any other non-root user. + */ +public class SystemRootUserTest extends TestWithFeService { + + private static final String HOST = "8.8.20.39"; + + @Override + protected void runBeforeAll() throws Exception { + run("CREATE USER 'root'@'" + HOST + "' IDENTIFIED BY 'Pwd_a1'"); + run("GRANT GRANT_PRIV ON *.*.* TO 'root'@'" + HOST + "'"); + run("CREATE USER 'grant_admin'@'%'"); + run("GRANT GRANT_PRIV ON *.*.* TO 'grant_admin'@'%'"); + } + + @Override + protected void runBeforeEach() throws Exception { + useUser(Auth.ROOT_USER); + } + + private void run(String sql) throws Exception { + ((Command) new NereidsParser().parseSingle(sql)).run(connectContext, null); + } + + private void assertRejected(String sql, String expectedMessage) { + Exception e = Assertions.assertThrows(Exception.class, () -> run(sql)); + Assertions.assertTrue(e.getMessage().contains(expectedMessage), e.getMessage()); + } + + private void assertPassword(UserIdentity user, String password) { + Assertions.assertDoesNotThrow( + () -> Env.getCurrentEnv().getAuth().checkPlainPasswordForUserIdentity(user, password, null)); + } + + private UserIdentity rootAtHost() { + UserIdentity user = new UserIdentity(Auth.ROOT_USER, HOST); + user.setIsAnalyzed(); + return user; + } + + @Test + public void testRootCanSetPasswordForRootAtSpecificHost() throws Exception { + run("SET PASSWORD FOR 'root'@'" + HOST + "' = PASSWORD('Pwd_f6')"); + assertPassword(rootAtHost(), "Pwd_f6"); + } + + @Test + public void testGrantUserCanSetPasswordForRootAtSpecificHost() throws Exception { + useUser("grant_admin"); + run("SET PASSWORD FOR 'root'@'" + HOST + "' = PASSWORD('Pwd_b2')"); + assertPassword(rootAtHost(), "Pwd_b2"); + } + + @Test + public void testGrantUserCanAlterRootAtSpecificHost() throws Exception { + useUser("grant_admin"); + run("ALTER USER 'root'@'" + HOST + "' IDENTIFIED BY 'Pwd_c3'"); + assertPassword(rootAtHost(), "Pwd_c3"); + } + + @Test + public void testGrantUserStillCannotModifySystemRoot() throws Exception { + useUser("grant_admin"); + assertRejected("SET PASSWORD FOR 'root'@'%' = PASSWORD('Pwd_d4')", + "Can not set password for root user, except root itself"); + assertRejected("ALTER USER 'root'@'%' IDENTIFIED BY 'Pwd_d4'", "Only root user can modify root user"); + assertPassword(UserIdentity.ROOT, ""); + } + + @Test + public void testRootAtSpecificHostCannotModifySystemRoot() throws Exception { + useUser(Auth.ROOT_USER, HOST); + assertRejected("SET PASSWORD FOR 'root'@'%' = PASSWORD('Pwd_e5')", + "Can not set password for root user, except root itself"); + assertRejected("ALTER USER 'root'@'%' IDENTIFIED BY 'Pwd_e5'", "Only root user can modify root user"); + assertPassword(UserIdentity.ROOT, ""); + } +} diff --git a/regression-test/suites/account_p0/test_system_user.groovy b/regression-test/suites/account_p0/test_system_user.groovy index d7b3ad213fb..829115680b4 100644 --- a/regression-test/suites/account_p0/test_system_user.groovy +++ b/regression-test/suites/account_p0/test_system_user.groovy @@ -100,4 +100,35 @@ suite("test_system_user","p0,auth") { sql """ drop user `admin`@'8.8.8.8'; """ + + // only root@'%' is the system root: root@'<host>' is managed like any other account, and a GRANT + // user that may manage it still can not modify root@'%' + String grantUser = "test_system_user_grant" + String pwd = 'C123_567p' + try_sql("DROP USER 'root'@'8.8.20.39'") + try_sql("DROP USER '${grantUser}'") + sql """CREATE USER 'root'@'8.8.20.39' IDENTIFIED BY 'Pwd_a1'""" + sql """SET PASSWORD FOR 'root'@'8.8.20.39' = PASSWORD('Pwd_b2')""" + sql """ALTER USER 'root'@'8.8.20.39' ACCOUNT_UNLOCK""" + sql """CREATE USER '${grantUser}' IDENTIFIED BY '${pwd}'""" + sql """GRANT GRANT_PRIV ON *.*.* TO ${grantUser}""" + if (isCloudMode()) { + def clusters = sql "SHOW CLUSTERS" + assertTrue(!clusters.isEmpty()) + sql """GRANT USAGE_PRIV ON CLUSTER `${clusters[0][0]}` TO ${grantUser}""" + } + def tokens = context.config.jdbcUrl.split('/') + def url = tokens[0] + "//" + tokens[2] + "/" + "information_schema" + "?" + connect(grantUser, "${pwd}", url) { + sql """SET PASSWORD FOR 'root'@'8.8.20.39' = PASSWORD('Pwd_c3')""" + sql """ALTER USER 'root'@'8.8.20.39' IDENTIFIED BY 'Pwd_d4'""" + test { + sql """SET PASSWORD FOR 'root'@'%' = PASSWORD('Pwd_e5')""" + exception "Can not set password for root user" + } + test { + sql """ALTER USER 'root'@'%' IDENTIFIED BY 'Pwd_e5'""" + exception "Only root user can modify root user" + } + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
