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]