This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.0 by this push:
     new d93e92b6e00 [secrity](auth) Add x509 cert based auth framework 
(#60098) (#60469)
d93e92b6e00 is described below

commit d93e92b6e001ad6d694420b4e5ae4c4d9ec70f94
Author: Siyang Tang <[email protected]>
AuthorDate: Wed Feb 4 16:13:58 2026 +0800

    [secrity](auth) Add x509 cert based auth framework (#60098) (#60469)
    
    ### What problem does this PR solve?
    
    Issue Number: close #xxx
    
    Related PR: #xxx
    
    Problem Summary:
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test <!-- At least one of them must be included. -->
        - [ ] Regression test
        - [ ] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason <!-- Add your reason?  -->
    
    - Behavior changed:
        - [ ] No.
        - [ ] Yes. <!-- Explain the behavior change -->
    
    - Does this need documentation?
        - [ ] No.
    - [ ] Yes. <!-- Add document PR link here. eg:
    https://github.com/apache/doris-website/pull/1214 -->
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label <!-- Add branch pick label that this PR
    should merge into -->
---
 .../antlr4/org/apache/doris/nereids/DorisLexer.g4  |   6 +
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 |  25 ++-
 .../org/apache/doris/alter/AlterUserOpType.java    |   3 +-
 .../java/org/apache/doris/analysis/TlsOptions.java | 242 +++++++++++++++++++++
 .../org/apache/doris/analysis/UserIdentity.java    |  81 +++++++
 .../org/apache/doris/common/proc/AuthProcDir.java  |   7 +-
 .../org/apache/doris/mysql/privilege/Auth.java     |  19 ++
 .../apache/doris/mysql/privilege/UserManager.java  |   6 +
 .../doris/nereids/parser/LogicalPlanBuilder.java   |  38 +++-
 .../plans/commands/ShowCreateUserCommand.java      |   5 +
 .../trees/plans/commands/info/AlterUserInfo.java   |  22 +-
 .../trees/plans/commands/info/CreateUserInfo.java  |  19 +-
 .../doris/mysql/privilege/CloudAuthTest.java       |  20 +-
 .../doris/nereids/parser/NereidsParserTest.java    |  14 ++
 gensrc/thrift/FrontendService.thrift               |  17 +-
 .../suites/account_p0/test_revoke_role.groovy      |   5 +-
 .../suites/manager/test_manager_interface_3.groovy |  30 +--
 17 files changed, 517 insertions(+), 42 deletions(-)

diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
index 3b4087404b0..f1d6e7c8679 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
@@ -124,6 +124,7 @@ CAST: 'CAST';
 CATALOG: 'CATALOG';
 CATALOGS: 'CATALOGS';
 CHAIN: 'CHAIN';
+CIPHER: 'CIPHER';
 CHAR: 'CHAR' | 'CHARACTER';
 CHARSET: 'CHARSET';
 CHAR_FILTER: 'CHAR_FILTER';
@@ -306,6 +307,7 @@ IS_NOT_NULL_PRED: 'IS_NOT_NULL_PRED';
 IS_NULL_PRED: 'IS_NULL_PRED';
 ISNULL: 'ISNULL';
 ISOLATION: 'ISOLATION';
+ISSUER: 'ISSUER';
 JOB: 'JOB';
 JOBS: 'JOBS';
 JOIN: 'JOIN';
@@ -375,6 +377,7 @@ NEXT: 'NEXT';
 NGRAM_BF: 'NGRAM_BF';
 ANN: 'ANN';
 NO: 'NO';
+NONE: 'NONE';
 NO_USE_MV: 'NO_USE_MV';
 NON_NULLABLE: 'NON_NULLABLE';
 NORMALIZER: 'NORMALIZER';
@@ -465,6 +468,7 @@ RESTRICTIVE: 'RESTRICTIVE';
 RESUME: 'RESUME';
 RETAIN: 'RETAIN';
 RETENTION: 'RETENTION';
+REQUIRE: 'REQUIRE';
 RETURNS: 'RETURNS';
 REVOKE: 'REVOKE';
 REWRITTEN: 'REWRITTEN';
@@ -481,6 +485,7 @@ ROW: 'ROW';
 ROWS: 'ROWS';
 S3: 'S3';
 SAMPLE: 'SAMPLE';
+SAN: 'SAN';
 SCHEDULE: 'SCHEDULE';
 SCHEDULER: 'SCHEDULER';
 SCHEMA: 'SCHEMA';
@@ -519,6 +524,7 @@ STREAM: 'STREAM';
 STREAMING: 'STREAMING';
 STRING: 'STRING';
 STRUCT: 'STRUCT';
+SUBJECT: 'SUBJECT';
 SUBSTR: 'SUBSTR';
 SUBSTRING: 'SUBSTRING';
 SUM: 'SUM';
diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
index 7b2d0af6fc5..abef3617806 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
@@ -244,7 +244,7 @@ supportedCreateStatement
             AS expression                                                      
     #createAliasFunction
     | CREATE USER (IF NOT EXISTS)? grantUserIdentify
             (SUPERUSER | DEFAULT ROLE role=STRING_LITERAL)?
-            passwordOption commentSpec?                                        
     #createUser
+            passwordOption requireClause? commentSpec?                         
     #createUser
     | CREATE (DATABASE | SCHEMA) (IF NOT EXISTS)? name=multipartIdentifier
               properties=propertyClause?                                       
     #createDatabase
     | CREATE (READ ONLY)? REPOSITORY name=identifier WITH storageBackend       
     #createRepository
@@ -322,7 +322,7 @@ supportedAlterStatement
     | ALTER COLOCATE GROUP name=multipartIdentifier
         SET LEFT_PAREN propertyItemList RIGHT_PAREN                            
             #alterColocateGroup
     | ALTER USER (IF EXISTS)? grantUserIdentify
-        passwordOption (COMMENT STRING_LITERAL)?                               
             #alterUser
+        passwordOption requireClause? commentSpec?                             
             #alterUser
     ;
 
 supportedDropStatement
@@ -912,6 +912,17 @@ passwordOption
         (ACCOUNT_LOCK | ACCOUNT_UNLOCK)?
     ;
 
+requireClause
+    : REQUIRE (NONE | tlsOption (AND? tlsOption)*)
+    ;
+
+tlsOption
+    : SAN STRING_LITERAL
+    | ISSUER STRING_LITERAL
+    | CIPHER STRING_LITERAL
+    | SUBJECT STRING_LITERAL
+    ;
+
 functionArguments
     : DOTDOTDOT
     | dataTypeList
@@ -1957,10 +1968,15 @@ nonReserved
     | CACHE
     | CACHED
     | CALL
+    | CANCEL
+    | CASE
+    | CAST
     | CATALOG
     | CATALOGS
     | CHAIN
+    | CIPHER
     | CHAR
+
     | CHARSET
     | CHECK
     | CLUSTER
@@ -2080,6 +2096,7 @@ nonReserved
     | IS_NULL_PRED
     | ISNULL
     | ISOLATION
+    | ISSUER
     | JOB
     | JOBS
     | JSON
@@ -2131,6 +2148,7 @@ nonReserved
     | NEXT
     | NGRAM_BF
     | NO
+    | NONE
     | NON_NULLABLE
     | NORMALIZER
     | NULLS
@@ -2193,6 +2211,7 @@ nonReserved
     | RESUME
     | RETAIN
     | RETENTION
+    | REQUIRE
     | RETURNS
     | REWRITTEN
     | RIGHT_BRACE
@@ -2204,6 +2223,7 @@ nonReserved
     | ROUTINE
     | S3
     | SAMPLE
+    | SAN
     | SCHEDULE
     | SCHEDULER
     | SCHEMA
@@ -2232,6 +2252,7 @@ nonReserved
     | STREAMING
     | STRING
     | STRUCT
+    | SUBJECT
     | SUBSTR
     | SUBSTRING
     | SUM
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/alter/AlterUserOpType.java 
b/fe/fe-core/src/main/java/org/apache/doris/alter/AlterUserOpType.java
index 4b694c38ef9..44977b4b316 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/alter/AlterUserOpType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/alter/AlterUserOpType.java
@@ -24,5 +24,6 @@ public enum AlterUserOpType {
     SET_PASSWORD_POLICY,
     LOCK_ACCOUNT,
     UNLOCK_ACCOUNT,
-    MODIFY_COMMENT
+    MODIFY_COMMENT,
+    SET_TLS_REQUIRE
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/TlsOptions.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/TlsOptions.java
new file mode 100644
index 00000000000..4f4f87273a6
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/TlsOptions.java
@@ -0,0 +1,242 @@
+// 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.analysis;
+
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.Pair;
+
+import java.util.List;
+
+/**
+ * TLS certificate requirement options for CREATE/ALTER USER.
+ * Supports REQUIRE NONE, REQUIRE SAN 'xxx', and future options (ISSUER, 
CIPHER, SUBJECT).
+ *
+ * <p>Current phase implementation:
+ * <ul>
+ *   <li>Only REQUIRE NONE and REQUIRE SAN 'xxx' are fully supported</li>
+ *   <li>Multi-option combinations (SAN AND CIPHER AND ...) are parsed but 
rejected in analyze()</li>
+ *   <li>SAN matching is simple string comparison</li>
+ * </ul>
+ *
+ * <p>Semantics:
+ * <ul>
+ *   <li>REQUIRE NONE clears all TLS requirements</li>
+ *   <li>No REQUIRE clause means keep existing settings unchanged</li>
+ *   <li>Unset fields use null (not empty string)</li>
+ * </ul>
+ */
+public class TlsOptions {
+
+    private boolean hasRequireClause;
+    private boolean requireNone;
+    private String san;
+    private String issuer;
+    private String subject;
+    private String cipher;
+
+    private TlsOptions(boolean hasRequireClause, boolean requireNone,
+            String san, String issuer, String subject, String cipher) {
+        this.hasRequireClause = hasRequireClause;
+        this.requireNone = requireNone;
+        this.san = san;
+        this.issuer = issuer;
+        this.subject = subject;
+        this.cipher = cipher;
+    }
+
+    /**
+     * Creates TlsOptions indicating no REQUIRE clause was specified.
+     * This means existing TLS settings should remain unchanged.
+     */
+    public static TlsOptions notSpecified() {
+        return new TlsOptions(false, false, null, null, null, null);
+    }
+
+    /**
+     * Creates TlsOptions for REQUIRE NONE.
+     * This clears all TLS requirements for the user.
+     */
+    public static TlsOptions requireNone() {
+        return new TlsOptions(true, true, null, null, null, null);
+    }
+
+    /**
+     * Creates TlsOptions from parsed list of option pairs.
+     * This factory method converts the parser output (List of Pairs) into 
explicit fields.
+     *
+     * @param options List of (option_name, value) pairs from parser, e.g. 
[("SAN", "IP:1.2.3.4")]
+     * @return TlsOptions with explicit fields populated
+     */
+    public static TlsOptions of(List<Pair<String, String>> options) {
+        if (options == null || options.isEmpty()) {
+            return requireNone();
+        }
+
+        String san = null;
+        String issuer = null;
+        String subject = null;
+        String cipher = null;
+
+        for (Pair<String, String> opt : options) {
+            if (opt == null || opt.first == null) {
+                continue;
+            }
+            String key = opt.first.toUpperCase();
+            String value = opt.second;
+            switch (key) {
+                case "SAN":
+                    san = value;
+                    break;
+                case "ISSUER":
+                    issuer = value;
+                    break;
+                case "SUBJECT":
+                    subject = value;
+                    break;
+                case "CIPHER":
+                    cipher = value;
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        return new TlsOptions(true, false, san, issuer, subject, cipher);
+    }
+
+
+    /**
+     * Validates the TLS options.
+     * Current phase: only SAN is supported; multi-option combinations are 
rejected.
+     *
+     * @throws AnalysisException if validation fails
+     */
+    public void analyze() throws AnalysisException {
+        if (!hasRequireClause || requireNone) {
+            return;
+        }
+
+        int optionCount = 0;
+        if (san != null) {
+            optionCount++;
+        }
+        if (issuer != null) {
+            optionCount++;
+        }
+        if (subject != null) {
+            optionCount++;
+        }
+        if (cipher != null) {
+            optionCount++;
+        }
+
+        if (optionCount == 0) {
+            throw new AnalysisException("REQUIRE clause must specify at least 
one TLS option or NONE");
+        }
+
+        if (issuer != null || subject != null || cipher != null) {
+            throw new AnalysisException("Only REQUIRE SAN is supported in 
current version. "
+                    + "ISSUER, SUBJECT, and CIPHER are not yet implemented.");
+        }
+
+        if (optionCount > 1) {
+            throw new AnalysisException("Multiple TLS options (e.g., SAN AND 
CIPHER) "
+                    + "are not supported in current version");
+        }
+
+        if (san != null && san.isEmpty()) {
+            throw new AnalysisException("SAN value cannot be empty");
+        }
+    }
+
+    public boolean hasRequireClause() {
+        return hasRequireClause;
+    }
+
+    public boolean isRequireNone() {
+        return requireNone;
+    }
+
+    public String getSan() {
+        return san;
+    }
+
+    public String getIssuer() {
+        return issuer;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public String getCipher() {
+        return cipher;
+    }
+
+    /**
+     * Returns SQL fragment for the REQUIRE clause.
+     * Empty string means no REQUIRE clause was specified.
+     */
+    public String toSql() {
+        if (!hasRequireClause) {
+            return "";
+        }
+        if (requireNone) {
+            return "REQUIRE NONE";
+        }
+
+        StringBuilder sb = new StringBuilder("REQUIRE ");
+        boolean first = true;
+
+        if (san != null) {
+            sb.append("SAN '").append(san).append("'");
+            first = false;
+        }
+        if (issuer != null) {
+            if (!first) {
+                sb.append(" AND ");
+            }
+            sb.append("ISSUER '").append(issuer).append("'");
+            first = false;
+        }
+        if (subject != null) {
+            if (!first) {
+                sb.append(" AND ");
+            }
+            sb.append("SUBJECT '").append(subject).append("'");
+            first = false;
+        }
+        if (cipher != null) {
+            if (!first) {
+                sb.append(" AND ");
+            }
+            sb.append("CIPHER '").append(cipher).append("'");
+        }
+
+        if (first) {
+            return "REQUIRE NONE";
+        }
+
+        return sb.toString();
+    }
+
+    @Override
+    public String toString() {
+        return toSql();
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/UserIdentity.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/UserIdentity.java
index 309e5d82ba7..54b59778321 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/UserIdentity.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/UserIdentity.java
@@ -56,6 +56,16 @@ public class UserIdentity implements Writable, 
GsonPostProcessable {
     @SerializedName(value = "isDomain")
     private boolean isDomain;
 
+    // TLS certificate authentication fields
+    @SerializedName(value = "san")
+    private String san;
+    @SerializedName(value = "issuer")
+    private String issuer;
+    @SerializedName(value = "cipher")
+    private String cipher;
+    @SerializedName(value = "subject")
+    private String subject;
+
     private boolean isAnalyzed = false;
 
     public static final UserIdentity ROOT;
@@ -122,6 +132,77 @@ public class UserIdentity implements Writable, 
GsonPostProcessable {
         return isDomain;
     }
 
+    // TLS certificate authentication getters and setters
+    public String getSan() {
+        return san;
+    }
+
+    public void setSan(String san) {
+        this.san = san;
+    }
+
+    public String getIssuer() {
+        return issuer;
+    }
+
+    public void setIssuer(String issuer) {
+        this.issuer = issuer;
+    }
+
+    public String getCipher() {
+        return cipher;
+    }
+
+    public void setCipher(String cipher) {
+        this.cipher = cipher;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+
+    /**
+     * Checks if this user has any TLS certificate requirements.
+     */
+    public boolean hasTlsRequirements() {
+        return san != null || issuer != null || cipher != null || subject != 
null;
+    }
+
+    /**
+     * Clears all TLS certificate requirements.
+     * Used when REQUIRE NONE is specified.
+     */
+    public void clearTlsRequirements() {
+        this.san = null;
+        this.issuer = null;
+        this.cipher = null;
+        this.subject = null;
+    }
+
+    /**
+     * Applies TLS options from a TlsOptions object.
+     * If tlsOptions is null or has no REQUIRE clause, no changes are made.
+     * If REQUIRE NONE is specified, all TLS requirements are cleared.
+     * Otherwise, the TLS fields are set from the TlsOptions.
+     */
+    public void applyTlsOptions(TlsOptions tlsOptions) {
+        if (tlsOptions == null || !tlsOptions.hasRequireClause()) {
+            return;
+        }
+        if (tlsOptions.isRequireNone()) {
+            clearTlsRequirements();
+        } else {
+            this.san = tlsOptions.getSan();
+            this.issuer = tlsOptions.getIssuer();
+            this.cipher = tlsOptions.getCipher();
+            this.subject = tlsOptions.getSubject();
+        }
+    }
+
     public void setIsAnalyzed() {
         this.isAnalyzed = true;
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java 
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java
index 0fa5b5d5b41..0e7f40453d2 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java
@@ -31,9 +31,10 @@ import com.google.common.collect.ImmutableList;
  */
 public class AuthProcDir implements ProcDirInterface {
     public static final ImmutableList<String> TITLE_NAMES = new 
ImmutableList.Builder<String>()
-            
.add("UserIdentity").add("Comment").add("Password").add("Roles").add("GlobalPrivs").add("CatalogPrivs")
-            
.add("DatabasePrivs").add("TablePrivs").add("ColPrivs").add("ResourcePrivs").add("CloudClusterPrivs")
-            
.add("CloudStagePrivs").add("StorageVaultPrivs").add("WorkloadGroupPrivs").add("ComputeGroupPrivs")
+            
.add("UserIdentity").add("Comment").add("Password").add("RequireSan").add("Roles")
+            
.add("GlobalPrivs").add("CatalogPrivs").add("DatabasePrivs").add("TablePrivs").add("ColPrivs")
+            
.add("ResourcePrivs").add("CloudClusterPrivs").add("CloudStagePrivs")
+            
.add("StorageVaultPrivs").add("WorkloadGroupPrivs").add("ComputeGroupPrivs")
             .build();
 
     private Auth auth;
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 dbfaaa15c2d..bf1dc7b1963 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
@@ -1338,12 +1338,17 @@ public class Auth implements Writable {
         List<String> userAuthInfo = Lists.newArrayList();
         // ================= UserIdentity =======================
         userAuthInfo.add(userIdent.toString());
+        String requireSan = Strings.isNullOrEmpty(userIdent.getSan())
+                ? FeConstants.null_string
+                : userIdent.getSan();
         if (isLdapAuthEnabled() && 
ldapManager.doesUserExist(userIdent.getQualifiedUser())) {
             // ============== Comment ==============
             userAuthInfo.add(FeConstants.null_string);
             LdapUserInfo ldapUserInfo = 
ldapManager.getUserInfo(userIdent.getQualifiedUser());
             // ============== Password ==============
             userAuthInfo.add(ldapUserInfo.isSetPasswd() ? "Yes" : "No");
+            // ============== RequireSan ==============
+            userAuthInfo.add(requireSan);
             // ============== Roles ==============
             
userAuthInfo.add(Joiner.on(",").join(getRoleNamesByUserWithLdap(userIdent,
                     
ConnectContext.get().getSessionVariable().showUserDefaultRole)));
@@ -1353,11 +1358,14 @@ public class Auth implements Writable {
                 userAuthInfo.add(FeConstants.null_string);
                 userAuthInfo.add(FeConstants.null_string);
                 userAuthInfo.add(FeConstants.null_string);
+                userAuthInfo.add(FeConstants.null_string);
             } else {
                 // ============== Comment ==============
                 userAuthInfo.add(user.getComment());
                 // ============== Password ==============
                 userAuthInfo.add(user.hasPassword() ? "Yes" : "No");
+                // ============== RequireSan ==============
+                userAuthInfo.add(requireSan);
                 // ============== Roles ==============
                 userAuthInfo.add(Joiner.on(",").join(userRoleManager
                         .getRolesByUser(userIdent, 
ConnectContext.get().getSessionVariable().showUserDefaultRole)));
@@ -1894,6 +1902,9 @@ public class Auth implements Writable {
                 case MODIFY_COMMENT:
                     modifyComment(userIdent, comment);
                     break;
+                case SET_TLS_REQUIRE:
+                    updateUserTlsRequirements(userIdent);
+                    break;
                 default:
                     throw new DdlException("Unknown alter user operation type: 
" + opType.name());
             }
@@ -1921,6 +1932,14 @@ public class Auth implements Writable {
         userRoleManager.addUserRole(userIdent, 
roleManager.getUserDefaultRoleName(userIdent));
     }
 
+    private void updateUserTlsRequirements(UserIdentity userIdent) throws 
DdlException {
+        User user = userManager.getUserByUserIdentity(userIdent);
+        if (user == null) {
+            throw new DdlException("user: " + userIdent + " does not exist");
+        }
+        user.setUserIdentity(userIdent);
+    }
+
     private void modifyComment(UserIdentity userIdent, String comment) throws 
DdlException {
         User user = userManager.getUserByUserIdentity(userIdent);
         if (user == null) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java
index d2ff874f640..a059101d665 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java
@@ -245,6 +245,7 @@ public class UserManager implements Writable, 
GsonPostProcessable {
             userByUserIdentity.setPassword(pwd);
             userByUserIdentity.setComment(comment);
             userByUserIdentity.setSetByDomainResolver(setByResolver);
+            userByUserIdentity.setUserIdentity(userIdent);
             return userByUserIdentity;
         }
 
@@ -362,6 +363,11 @@ public class UserManager implements Writable, 
GsonPostProcessable {
                     // password, this "domain" user ident will be returned as 
"current user".
                     for (String newIP : entry.getValue()) {
                         UserIdentity userIdent = 
UserIdentity.createAnalyzedUserIdentWithIp(userEntry.getKey(), newIP);
+                        UserIdentity domainUserIdent = 
domainUser.getUserIdentity();
+                        userIdent.setSan(domainUserIdent.getSan());
+                        userIdent.setIssuer(domainUserIdent.getIssuer());
+                        userIdent.setCipher(domainUserIdent.getCipher());
+                        userIdent.setSubject(domainUserIdent.getSubject());
                         byte[] password = 
domainUser.getPassword().getPassword();
                         Preconditions.checkNotNull(password, entry.getKey());
                         try {
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 ab53622370d..055482cadbd 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
@@ -43,6 +43,7 @@ import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.TableScanParams;
 import org.apache.doris.analysis.TableSnapshot;
 import org.apache.doris.analysis.TableValuedFunctionRef;
+import org.apache.doris.analysis.TlsOptions;
 import org.apache.doris.analysis.UserDesc;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.analysis.WorkloadGroupPattern;
@@ -1356,6 +1357,34 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         return 
LogicalPlanBuilderAssistant.escapeBackSlash(commentSpec.substring(1, 
commentSpec.length() - 1));
     }
 
+    /**
+     * Parse REQUIRE clause into TLS options.
+     */
+    public TlsOptions visitRequireClause(DorisParser.RequireClauseContext ctx) 
{
+        if (ctx == null) {
+            return TlsOptions.notSpecified();
+        }
+        if (ctx.NONE() != null) {
+            return TlsOptions.requireNone();
+        }
+        if (ctx.tlsOption() == null || ctx.tlsOption().isEmpty()) {
+            return TlsOptions.requireNone();
+        }
+        List<Pair<String, String>> options = new ArrayList<>();
+        for (DorisParser.TlsOptionContext option : ctx.tlsOption()) {
+            if (option.SAN() != null) {
+                options.add(Pair.of("SAN", 
stripQuotes(option.STRING_LITERAL().getText())));
+            } else if (option.ISSUER() != null) {
+                options.add(Pair.of("ISSUER", 
stripQuotes(option.STRING_LITERAL().getText())));
+            } else if (option.CIPHER() != null) {
+                options.add(Pair.of("CIPHER", 
stripQuotes(option.STRING_LITERAL().getText())));
+            } else if (option.SUBJECT() != null) {
+                options.add(Pair.of("SUBJECT", 
stripQuotes(option.STRING_LITERAL().getText())));
+            }
+        }
+        return TlsOptions.of(options);
+    }
+
     /**
      * This function may be used in some task like InsertTask, 
RefreshDictionary, etc. the target could be many type of
      * tables.
@@ -8055,8 +8084,9 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         boolean ifExist = ctx.EXISTS() != null;
         UserDesc userDesc = visitGrantUserIdentify(ctx.grantUserIdentify());
         PasswordOptions passwordOptions = 
visitPasswordOption(ctx.passwordOption());
-        String comment = ctx.STRING_LITERAL() != null ? 
stripQuotes(ctx.STRING_LITERAL().getText()) : null;
-        AlterUserInfo alterUserInfo = new AlterUserInfo(ifExist, userDesc, 
passwordOptions, comment);
+        String comment = ctx.commentSpec() == null ? null : 
visitCommentSpec(ctx.commentSpec());
+        TlsOptions tlsOptions = visitRequireClause(ctx.requireClause());
+        AlterUserInfo alterUserInfo = new AlterUserInfo(ifExist, userDesc, 
passwordOptions, comment, tlsOptions);
         return new AlterUserCommand(alterUserInfo);
     }
 
@@ -8831,6 +8861,7 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         String comment = visitCommentSpec(ctx.commentSpec());
         PasswordOptions passwordOptions = 
visitPasswordOption(ctx.passwordOption());
         UserDesc userDesc = (UserDesc) ctx.grantUserIdentify().accept(this);
+        TlsOptions tlsOptions = visitRequireClause(ctx.requireClause());
 
         String role = null;
         if (ctx.role != null) {
@@ -8841,7 +8872,8 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
                 userDesc,
                 role,
                 passwordOptions,
-                comment);
+                comment,
+                tlsOptions);
 
         return new CreateUserCommand(userInfo);
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCreateUserCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCreateUserCommand.java
index f542b500d4d..096facbdf63 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCreateUserCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCreateUserCommand.java
@@ -98,6 +98,11 @@ public class ShowCreateUserCommand extends ShowCommand {
         // user password
         sb.append(" IDENTIFIED BY *** ");
 
+        // tls requirements
+        if (user.getSan() != null) {
+            sb.append(" REQUIRE SAN '").append(user.getSan()).append("'");
+        }
+
         // password policy
         if (Env.getCurrentEnv().getAuth().getPasswdPolicyManager() != null) {
             // policies are : <expirePolicy, historyPolicy, failedLoginPolicy>
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 40c1ef7d781..bf0a1aa060b 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
@@ -19,6 +19,7 @@ package org.apache.doris.nereids.trees.plans.commands.info;
 
 import org.apache.doris.alter.AlterUserOpType;
 import org.apache.doris.analysis.PasswordOptions;
+import org.apache.doris.analysis.TlsOptions;
 import org.apache.doris.analysis.UserDesc;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.Env;
@@ -45,13 +46,20 @@ public class AlterUserInfo {
     private UserDesc userDesc;
     private PasswordOptions passwordOptions;
     private String comment;
+    private TlsOptions tlsOptions;
     private Set<AlterUserOpType> ops = Sets.newHashSet();
 
-    public AlterUserInfo(boolean ifExist, UserDesc userDesc, PasswordOptions 
passwordOptions, String comment) {
+    public AlterUserInfo(boolean ifExist, UserDesc userDesc, PasswordOptions 
passwordOptions, String comment,
+            TlsOptions tlsOptions) {
         this.ifExist = ifExist;
         this.userDesc = userDesc;
         this.passwordOptions = passwordOptions;
         this.comment = comment;
+        this.tlsOptions = tlsOptions == null ? TlsOptions.notSpecified() : 
tlsOptions;
+    }
+
+    public AlterUserInfo(boolean ifExist, UserDesc userDesc, PasswordOptions 
passwordOptions, String comment) {
+        this(ifExist, userDesc, passwordOptions, comment, 
TlsOptions.notSpecified());
     }
 
     public boolean isIfExist() {
@@ -74,7 +82,7 @@ public class AlterUserInfo {
     }
 
     public AlterUserOpType getOpType() {
-        Preconditions.checkState(ops.size() == 1);
+        Preconditions.checkState(ops.size() == 1, "AlterUserInfo should only 
carry one operation");
         return ops.iterator().next();
     }
 
@@ -82,6 +90,10 @@ public class AlterUserInfo {
         return comment;
     }
 
+    public TlsOptions getTlsOptions() {
+        return tlsOptions;
+    }
+
     /**
      * validate
      */
@@ -94,6 +106,12 @@ public class AlterUserInfo {
         }
 
         // may be set comment to "", so not use `Strings.isNullOrEmpty`
+        if (tlsOptions.hasRequireClause()) {
+            ops.add(AlterUserOpType.SET_TLS_REQUIRE);
+            tlsOptions.analyze();
+            userDesc.getUserIdent().applyTlsOptions(tlsOptions);
+        }
+
         if (comment != null) {
             ops.add(AlterUserOpType.MODIFY_COMMENT);
         }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateUserInfo.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateUserInfo.java
index daaf816ffd1..e1ecd55281e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateUserInfo.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateUserInfo.java
@@ -19,6 +19,7 @@ package org.apache.doris.nereids.trees.plans.commands.info;
 
 import org.apache.doris.analysis.PassVar;
 import org.apache.doris.analysis.PasswordOptions;
+import org.apache.doris.analysis.TlsOptions;
 import org.apache.doris.analysis.UserDesc;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.Env;
@@ -46,6 +47,7 @@ public class CreateUserInfo {
     private String role;
     private PasswordOptions passwordOptions;
     private String comment;
+    private TlsOptions tlsOptions;
     private UserIdentity userIdent;
     private PassVar passVar;
     private String userId;
@@ -54,20 +56,21 @@ public class CreateUserInfo {
      * CreateUserInfo
      */
     public CreateUserInfo(UserDesc userDesc) {
-        this(false, userDesc, null, null, "");
+        this(false, userDesc, null, null, "", TlsOptions.notSpecified());
     }
 
     /**
      * CreateUserInfo
      */
     public CreateUserInfo(boolean ifNotExist, UserDesc userDesc,
-            String role, PasswordOptions passwordOptions, String comment) {
+            String role, PasswordOptions passwordOptions, String comment, 
TlsOptions tlsOptions) {
         this.ifNotExist = ifNotExist;
         this.userIdent = userDesc.getUserIdent();
         this.passVar = userDesc.getPassVar();
         this.role = role;
         this.passwordOptions = passwordOptions;
         this.comment = comment;
+        this.tlsOptions = tlsOptions == null ? TlsOptions.notSpecified() : 
tlsOptions;
 
         String uId = Env.getCurrentEnv().getAuth().getUserId(ClusterNamespace
                 .getNameFromFullName(this.userIdent.getUser()));
@@ -85,6 +88,11 @@ public class CreateUserInfo {
         }
     }
 
+    public CreateUserInfo(boolean ifNotExist, UserDesc userDesc,
+            String role, PasswordOptions passwordOptions, String comment) {
+        this(ifNotExist, userDesc, role, passwordOptions, comment, 
TlsOptions.notSpecified());
+    }
+
     /**
      * validate
      */
@@ -107,6 +115,9 @@ public class CreateUserInfo {
         }
         passwordOptions.analyze();
 
+        tlsOptions.analyze();
+        userIdent.applyTlsOptions(tlsOptions);
+
         if 
(!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), 
PrivPredicate.GRANT)) {
             
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, 
"GRANT");
         }
@@ -128,6 +139,10 @@ public class CreateUserInfo {
         return comment;
     }
 
+    public TlsOptions getTlsOptions() {
+        return tlsOptions;
+    }
+
     public UserIdentity getUserIdent() {
         return userIdent;
     }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/CloudAuthTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/CloudAuthTest.java
index 0807158ccdb..5047357b4a6 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/CloudAuthTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/CloudAuthTest.java
@@ -387,9 +387,9 @@ public class CloudAuthTest extends TestWithFeService {
         ShowGrantsCommand sg = new ShowGrantsCommand(new 
UserIdentity("testUser", "%"), false);
         ShowResultSet showResultSet = sg.doRun(connectContext, null);
         // cluster field
-        Assert.assertEquals("vcg: Cluster_usage_priv", 
showResultSet.getResultRows().get(0).get(10));
+        Assert.assertEquals("vcg: Cluster_usage_priv", 
showResultSet.getResultRows().get(0).get(11));
         // compute group field
-        Assert.assertEquals("vcg: Cluster_usage_priv", 
showResultSet.getResultRows().get(0).get(14));
+        Assert.assertEquals("vcg: Cluster_usage_priv", 
showResultSet.getResultRows().get(0).get(15));
 
         // -------------------- case 2 -------------------------
         // grant usage_priv on cluster 'cg1' to 'testUser'@'%'
@@ -406,10 +406,10 @@ public class CloudAuthTest extends TestWithFeService {
         showResultSet = sg.doRun(connectContext, null);
         // cluster field
         Assert.assertEquals("cg1: Cluster_usage_priv; vcg: Cluster_usage_priv",
-                showResultSet.getResultRows().get(0).get(10));
+                showResultSet.getResultRows().get(0).get(11));
         // compute group field
         Assert.assertEquals("cg1: Cluster_usage_priv; vcg: Cluster_usage_priv",
-                showResultSet.getResultRows().get(0).get(14));
+                showResultSet.getResultRows().get(0).get(15));
 
         // revoke cg1 from test user
         String revokeCgSql1 = "revoke usage_priv on cluster 'cg1' from 
'testUser'@'%'";
@@ -423,10 +423,10 @@ public class CloudAuthTest extends TestWithFeService {
         showResultSet = sg.doRun(connectContext, null);
         // cluster field
         Assert.assertEquals("vcg: Cluster_usage_priv",
-                showResultSet.getResultRows().get(0).get(10));
+                showResultSet.getResultRows().get(0).get(11));
         // compute group field
         Assert.assertEquals("vcg: Cluster_usage_priv",
-                showResultSet.getResultRows().get(0).get(14));
+                showResultSet.getResultRows().get(0).get(15));
 
         // grant cg2 to user
         String grantVcgUser3 = "grant usage_priv on cluster 'cg2' to 
'testUser'@'%'";
@@ -454,10 +454,10 @@ public class CloudAuthTest extends TestWithFeService {
         showResultSet = sg.doRun(connectContext, null);
         // cluster field
         Assert.assertEquals("cg2: Cluster_usage_priv",
-                showResultSet.getResultRows().get(0).get(10));
+                showResultSet.getResultRows().get(0).get(11));
         // compute group field
         Assert.assertEquals("cg2: Cluster_usage_priv",
-                showResultSet.getResultRows().get(0).get(14));
+                showResultSet.getResultRows().get(0).get(15));
         // revoke cg2 from user
 
         String revokeCgSql3 = "revoke usage_priv on cluster 'cg2' from 
'testUser'";
@@ -481,9 +481,9 @@ public class CloudAuthTest extends TestWithFeService {
                 PrivPredicate.USAGE, ResourceTypeEnum.CLUSTER));
         showResultSet = sg.doRun(connectContext, null);
         // cluster field
-        Assert.assertEquals("\\N", 
showResultSet.getResultRows().get(0).get(10));
+        Assert.assertEquals("\\N", 
showResultSet.getResultRows().get(0).get(11));
         // compute group field
-        Assert.assertEquals("\\N", 
showResultSet.getResultRows().get(0).get(14));
+        Assert.assertEquals("\\N", 
showResultSet.getResultRows().get(0).get(15));
 
         // drop user
         String dropUserSql5 = "DROP USER testUser";
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
index 5a47ff15c08..24c421532f3 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
@@ -778,6 +778,20 @@ public class NereidsParserTest extends ParserTestBase {
         nereidsParser.parseSingle(sql);
     }
 
+    @Test
+    public void testCreateUserWithRequire() {
+        NereidsParser parser = new NereidsParser();
+        parser.parseSingle("create user u1 require none");
+        parser.parseSingle("create user u2 identified by 'pwd' require san 
'DNS:a.example'");
+    }
+
+    @Test
+    public void testAlterUserWithRequire() {
+        NereidsParser parser = new NereidsParser();
+        parser.parseSingle("alter user u1 require san 
'URI:spiffe://example.org/service'");
+        parser.parseSingle("alter user if exists u2 identified by 'pwd' 
require none");
+    }
+
     @Test
     public void testCreateRepository() {
         NereidsParser nereidsParser = new NereidsParser();
diff --git a/gensrc/thrift/FrontendService.thrift 
b/gensrc/thrift/FrontendService.thrift
index e1f9de1f4bb..75dabfb28cc 100644
--- a/gensrc/thrift/FrontendService.thrift
+++ b/gensrc/thrift/FrontendService.thrift
@@ -433,6 +433,17 @@ struct TMasterOpResult {
     11: optional i64 affectedRows;
 }
 
+// Certificate-based authentication info forwarded from BE to FE
+struct TCertBasedAuth {
+    1: optional string cert_pem            // Client certificate in PEM format 
(for debugging)
+    2: optional string subject             // Certificate Subject DN
+    3: optional string san                 // Subject Alternative Names 
(formatted string)
+    4: optional string issuer              // Issuer DN
+    5: optional string cipher              // SSL cipher suite used
+    6: optional string validity_not_before // Certificate validity start time
+    7: optional string validity_not_after  // Certificate validity end time
+}
+
 struct TLoadTxnBeginRequest {
     1: optional string cluster
     2: required string user
@@ -450,6 +461,7 @@ struct TLoadTxnBeginRequest {
     13: optional string auth_code_uuid // deprecated, use token instead
     14: optional i64 table_id
     15: optional i64 backend_id
+    16: optional TCertBasedAuth cert_based_auth
 }
 
 struct TLoadTxnBeginResult {
@@ -561,6 +573,7 @@ struct TStreamLoadPutRequest {
     57: optional Types.TUniqueKeyUpdateMode unique_key_update_mode
     58: optional Descriptors.TPartialUpdateNewRowPolicy 
partial_update_new_key_policy
     59: optional bool empty_field_as_null
+    60: optional TCertBasedAuth cert_based_auth
 
     // For cloud
     1000: optional string cloud_cluster
@@ -641,7 +654,8 @@ struct TLoadTxnCommitRequest {
     17: optional string auth_code_uuid // deprecated, use token instead
     18: optional bool groupCommit
     19: optional i64 receiveBytes
-    20: optional i64 backendId 
+    20: optional i64 backendId
+    21: optional TCertBasedAuth cert_based_auth
 }
 
 struct TLoadTxnCommitResult {
@@ -684,6 +698,7 @@ struct TLoadTxn2PCRequest {
     9: optional string token
     10: optional i64 thrift_rpc_timeout_ms
     11: optional string label
+    12: optional TCertBasedAuth cert_based_auth
 
     // For cloud
     1000: optional string auth_code_uuid // deprecated, use token instead
diff --git a/regression-test/suites/account_p0/test_revoke_role.groovy 
b/regression-test/suites/account_p0/test_revoke_role.groovy
index 5aa9f77206a..80146c74a19 100644
--- a/regression-test/suites/account_p0/test_revoke_role.groovy
+++ b/regression-test/suites/account_p0/test_revoke_role.groovy
@@ -35,15 +35,14 @@ suite("test_revoke_role", "account") {
 
     def result = sql """ SHOW GRANTS FOR ${user} """
     assertEquals(result.size(), 1)
-    assertTrue(result[0][6].contains("internal.${dbName}: Select_priv"))
+    assertTrue(result[0][7].contains("internal.${dbName}: Select_priv"))
 
     sql """REVOKE '${role}' from ${user}"""
     result = sql """ SHOW GRANTS FOR ${user} """
     assertEquals(result.size(), 1)
-    assertFalse(result[0][6].contains("internal.${dbName}: Select_priv"))
+    assertFalse(result[0][7].contains("internal.${dbName}: Select_priv"))
 
     sql """DROP USER ${user}"""
     sql """DROP ROLE ${role}"""
     sql """DROP DATABASE ${dbName}"""
 }
-
diff --git a/regression-test/suites/manager/test_manager_interface_3.groovy 
b/regression-test/suites/manager/test_manager_interface_3.groovy
index 23d63c6b68a..922497ceaf0 100644
--- a/regression-test/suites/manager/test_manager_interface_3.groovy
+++ b/regression-test/suites/manager/test_manager_interface_3.groovy
@@ -185,27 +185,27 @@ suite('test_manager_interface_3',"p0") {
         for(int i = 0;i < result.size(); i++ ) {
             
             // Roles: test_manager_role_grant_role1 
-            if ( result[i][3] == "${role1}") {
+            if ( result[i][4] == "${role1}") {
                 //UserIdentity: 
                 logger.info("result[${i}][0] = ${result[i][0]}" )
                 if (result[i][0].contains("test_manager_role_grant_user1")){
                     //DatabasePrivs 
-                    assertTrue(result[i][6] == "internal.information_schema: 
Select_priv; internal.mysql: Select_priv; internal.test_manager_role_grant_db: 
Select_priv,Create_priv,Drop_priv")
+                    assertTrue(result[i][7] == "internal.information_schema: 
Select_priv; internal.mysql: Select_priv; internal.test_manager_role_grant_db: 
Select_priv,Create_priv,Drop_priv")
                     x ++ 
                 }else if 
(result[i][0].contains("test_manager_role_grant_user2")) {
-                    assertTrue(result[i][6] == "internal.information_schema: 
Select_priv; internal.mysql: Select_priv; internal.test_manager_role_grant_db: 
Select_priv,Load_priv,Drop_priv")
+                    assertTrue(result[i][7] == "internal.information_schema: 
Select_priv; internal.mysql: Select_priv; internal.test_manager_role_grant_db: 
Select_priv,Load_priv,Drop_priv")
                     x ++ 
 
                 }else {
                     assertTrue(false." only ${user1} and ${user2}, no 
${result[i][0]}")
                 }
             }
-            else if ( result[i][3] =="admin"){
+            else if ( result[i][4] =="admin"){
                 if (result[i][0] == """'admin'@'%'"""){
                     x++
                 }
             
-            } else if (result[i][3] =="operator") {
+            } else if (result[i][4] =="operator") {
                 if (result[i][0] =="""'root'@'%'""" ){
                     x++
                 }
@@ -240,27 +240,27 @@ suite('test_manager_interface_3',"p0") {
         for(int i = 0;i < result.size(); i++ ) {
             
             // Roles: test_manager_role_grant_role1 
-            if ( result[i][3] == "${role1}") {
+            if ( result[i][4] == "${role1}") {
                 //UserIdentity: 
                 logger.info("result[${i}][0] = ${result[i][0]}" )
                 if (result[i][0].contains("test_manager_role_grant_user1")){
                     //DatabasePrivs 
-                    assertTrue(result[i][6] == "internal.information_schema: 
Select_priv; internal.mysql: Select_priv; internal.test_manager_role_grant_db: 
Select_priv,Drop_priv")
+                    assertTrue(result[i][7] == "internal.information_schema: 
Select_priv; internal.mysql: Select_priv; internal.test_manager_role_grant_db: 
Select_priv,Drop_priv")
                     x ++ 
                 }else if 
(result[i][0].contains("test_manager_role_grant_user2")) {
-                    
assertTrue(result[i][6].contains("internal.information_schema: Select_priv; 
internal.mysql: Select_priv; internal.test_manager_role_grant_db: 
Select_priv,Drop_priv"))
+                    
assertTrue(result[i][7].contains("internal.information_schema: Select_priv; 
internal.mysql: Select_priv; internal.test_manager_role_grant_db: 
Select_priv,Drop_priv"))
                     x ++ 
 
                 }else {
                     assertTrue(false." only ${user1} and ${user2}, no 
${result[i][0]}")
                 }
             }
-            else if ( result[i][3] =="admin"){
+            else if ( result[i][4] =="admin"){
                 if (result[i][0] == """'admin'@'%'"""){
                     x++
                 }
             
-            } else if (result[i][3] =="operator") {
+            } else if (result[i][4] =="operator") {
                 if (result[i][0] =="""'root'@'%'""" ){
                     x++
                 }
@@ -272,10 +272,10 @@ suite('test_manager_interface_3',"p0") {
         result = sql  """show  grants """        
         x = 0 
         for(int i = 0;i < result.size(); i++ ) {
-            if (result[i][3] =="operator") {
+            if (result[i][4] =="operator") {
                 if (result[i][0] =="""'root'@'%'""" ){
-                    if (result[i][6] == "internal.information_schema: 
Select_priv; internal.mysql: Select_priv"){
-                        assertTrue(result[i][4]=="Node_priv,Admin_priv")
+                    if (result[i][7] == "internal.information_schema: 
Select_priv; internal.mysql: Select_priv"){
+                        assertTrue(result[i][5]=="Node_priv,Admin_priv")
                         x++
                     
                     }
@@ -445,11 +445,11 @@ suite('test_manager_interface_3',"p0") {
         x = 0 
         for(int i = 0;i < result.size(); i++ ) {
             
-            if ( result[i][3] == "${role}") {
+            if ( result[i][4] == "${role}") {
                 //UserIdentity: 
                 if (result[i][0].contains(user)){
                     //DatabasePrivs 
-                    assertTrue(result[i][9] == "test_manager_resource_case: 
Usage_priv")
+                    assertTrue(result[i][10] == "test_manager_resource_case: 
Usage_priv")
                     x ++ 
                 }
             }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to