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 b69fc36110d branch-4.1: [fix](auth) Treat only root@'%' as root in SET
PASSWORD and ALTER USER (#68634)
b69fc36110d is described below
commit b69fc36110d7c383e09d5b158df67c6e0494a69f
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]