You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by GitBox <gi...@apache.org> on 2023/01/19 06:45:11 UTC

[GitHub] [doris] zddr opened a new pull request, #16091: [wip](auth)Implementing privilege management with rbac model

zddr opened a new pull request, #16091:
URL: https://github.com/apache/doris/pull/16091

   # Proposed changes
   
   Issue Number: close #xxx
   
   ## Problem summary
   
   Describe your changes.
   
   ## Checklist(Required)
   
   1. Does it affect the original behavior: 
       - [ ] Yes
       - [ ] No
       - [ ] I don't know
   2. Has unit tests been added:
       - [ ] Yes
       - [ ] No
       - [ ] No Need
   3. Has document been added or modified:
       - [ ] Yes
       - [ ] No
       - [ ] No Need
   4. Does it need to update dependencies:
       - [ ] Yes
       - [ ] No
   5. Are there any changes that cannot be rolled back:
       - [ ] Yes (If Yes, please explain WHY)
       - [ ] No
   
   ## Further comments
   
   If this is a relatively large or complex change, kick off the discussion at [dev@doris.apache.org](mailto:dev@doris.apache.org) by explaining why you chose the solution you did and what alternatives you considered, etc...
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] hello-stephen commented on pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #16091:
URL: https://github.com/apache/doris/pull/16091#issuecomment-1415037900

   TeamCity pipeline, clickbench performance test result:
    the sum of best hot time: 35.3 seconds
    load time: 475 seconds
    storage size: 17170642077 Bytes
    https://doris-community-test-1308700295.cos.ap-hongkong.myqcloud.com/tmp/20230203054345_clickbench_pr_89907.html


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] morningman commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "morningman (via GitHub)" <gi...@apache.org>.
morningman commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1098088468


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java:
##########
@@ -0,0 +1,584 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.ResourcePattern;
+import org.apache.doris.analysis.TablePattern;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.FeMetaVersion;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.privilege.Auth.PrivLevel;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+public class Role implements Writable {
+    private static final Logger LOG = LogManager.getLogger(Role.class);
+
+    // operator is responsible for operating cluster, such as add/drop node
+    public static String OPERATOR_ROLE = "operator";
+    // admin is like DBA, who has all privileges except for NODE privilege held by operator
+    public static String ADMIN_ROLE = "admin";
+
+    public static Role OPERATOR = new Role(OPERATOR_ROLE,
+            TablePattern.ALL, PrivBitSet.of(Privilege.NODE_PRIV, Privilege.ADMIN_PRIV),
+            ResourcePattern.ALL, PrivBitSet.of(Privilege.NODE_PRIV, Privilege.ADMIN_PRIV));
+    public static Role ADMIN = new Role(ADMIN_ROLE,
+            TablePattern.ALL, PrivBitSet.of(Privilege.ADMIN_PRIV),
+            ResourcePattern.ALL, PrivBitSet.of(Privilege.ADMIN_PRIV));
+
+    private String roleName;
+    private Map<TablePattern, PrivBitSet> tblPatternToPrivs = Maps.newConcurrentMap();

Review Comment:
   Add comment to explain which field will be persist



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java:
##########
@@ -72,37 +61,29 @@ public boolean hasPriv(String host, String user, PrivPredicate wanted) {
      * NOTICE, this method does not set password for the newly added entry if this is a user priv table, the caller
      * need to set password later.
      */
-    public PrivEntry addEntry(PrivEntry newEntry, boolean errOnExist, boolean errOnNonExist) throws DdlException {
+    public PrivEntry addEntry(PrivEntry newEntry,
+            boolean errOnExist, boolean errOnNonExist) throws DdlException {
         PrivEntry existingEntry = getExistingEntry(newEntry);
         if (existingEntry == null) {
             if (errOnNonExist) {
-                throw new DdlException("User " + newEntry.getUserIdent() + " does not exist");
+                throw new DdlException("entry does not exist");
             }
             entries.add(newEntry);
             Collections.sort(entries);
             LOG.info("add priv entry: {}", newEntry);
             return newEntry;
         } else {
             if (errOnExist) {
-                throw new DdlException("User already exist");
+                throw new DdlException("entry already exist");
             } else {
-                checkOperationAllowed(existingEntry, newEntry, "ADD ENTRY");
-                // if existing entry is set by domain resolver, just replace it with the new entry.
-                // if existing entry is not set by domain resolver, merge the 2 entries.
-                if (existingEntry.isSetByDomainResolver()) {
-                    existingEntry.setPrivSet(newEntry.getPrivSet());
-                    existingEntry.setSetByDomainResolver(newEntry.isSetByDomainResolver());
-                    LOG.debug("reset priv entry: {}", existingEntry);
-                } else if (!newEntry.isSetByDomainResolver()) {
-                    mergePriv(existingEntry, newEntry);
-                    existingEntry.setSetByDomainResolver(false);
-                    LOG.debug("merge priv entry: {}", existingEntry);
-                }
+                mergePriv(existingEntry, newEntry);
+                LOG.debug("merge priv entry: {}", existingEntry);

Review Comment:
   check this `checkOperationAllowed` logic



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java:
##########
@@ -0,0 +1,1627 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.AlterUserStmt;
+import org.apache.doris.analysis.AlterUserStmt.OpType;
+import org.apache.doris.analysis.CreateRoleStmt;
+import org.apache.doris.analysis.CreateUserStmt;
+import org.apache.doris.analysis.DropRoleStmt;
+import org.apache.doris.analysis.DropUserStmt;
+import org.apache.doris.analysis.GrantStmt;
+import org.apache.doris.analysis.PasswordOptions;
+import org.apache.doris.analysis.ResourcePattern;
+import org.apache.doris.analysis.RevokeStmt;
+import org.apache.doris.analysis.SetLdapPassVar;
+import org.apache.doris.analysis.SetPassVar;
+import org.apache.doris.analysis.SetUserPropertyStmt;
+import org.apache.doris.analysis.TableName;
+import org.apache.doris.analysis.TablePattern;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.AuthorizationInfo;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.cluster.ClusterNamespace;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.common.FeConstants;
+import org.apache.doris.common.FeMetaVersion;
+import org.apache.doris.common.LdapConfig;
+import org.apache.doris.common.Pair;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.UserException;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.datasource.InternalCatalog;
+import org.apache.doris.ldap.LdapManager;
+import org.apache.doris.ldap.LdapPrivsChecker;
+import org.apache.doris.load.DppConfig;
+import org.apache.doris.persist.AlterUserOperationLog;
+import org.apache.doris.persist.LdapInfo;
+import org.apache.doris.persist.PrivInfo;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.resource.Tag;
+import org.apache.doris.system.SystemInfoService;
+import org.apache.doris.thrift.TFetchResourceResult;
+import org.apache.doris.thrift.TPrivilegeStatus;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.stream.Collectors;
+
+public class Auth implements Writable {
+    private static final Logger LOG = LogManager.getLogger(Auth.class);
+
+    // root user's role is operator.
+    // each Palo system has only one root user.
+    public static final String ROOT_USER = "root";
+    public static final String ADMIN_USER = "admin";
+    // unknown user does not have any privilege, this is just to be compatible with old version.
+    public static final String UNKNOWN_USER = "unknown";
+    public static final String DEFAULT_CATALOG = InternalCatalog.INTERNAL_CATALOG_NAME;
+
+    private RoleManager roleManager = new RoleManager();
+    private UserManager userManager = new UserManager();
+    private UserRoleManager userRoleManager = new UserRoleManager();
+    private UserPropertyMgr propertyMgr = new UserPropertyMgr();
+
+
+    private LdapInfo ldapInfo = new LdapInfo();
+
+    private LdapManager ldapManager = new LdapManager();
+
+    private PasswordPolicyManager passwdPolicyManager = new PasswordPolicyManager();
+
+    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+
+    private void readLock() {
+        lock.readLock().lock();
+    }
+
+    private void readUnlock() {
+        lock.readLock().unlock();
+    }
+
+    private void writeLock() {
+        lock.writeLock().lock();
+    }
+
+    private void writeUnlock() {
+        lock.writeLock().unlock();
+    }
+
+    public enum PrivLevel {
+        GLOBAL, CATALOG, DATABASE, TABLE, RESOURCE
+    }
+
+    public Auth() {
+        initUser();
+    }
+
+    public LdapInfo getLdapInfo() {
+        return ldapInfo;
+    }
+
+    public void setLdapInfo(LdapInfo ldapInfo) {
+        this.ldapInfo = ldapInfo;
+    }
+
+    public LdapManager getLdapManager() {
+        return ldapManager;
+    }
+
+    public PasswordPolicyManager getPasswdPolicyManager() {
+        return passwdPolicyManager;
+    }
+
+    public boolean doesRoleExist(String qualifiedRole) {
+        return roleManager.getRole(qualifiedRole) != null;
+    }
+
+    public void mergeRolesNoCheckName(List<String> roles, Role savedRole) throws DdlException {
+        readLock();
+        try {
+            for (String roleName : roles) {
+                if (doesRoleExist(roleName)) {
+                    Role role = roleManager.getRole(roleName);
+                    savedRole.mergeNotCheck(role);
+                }
+            }
+        } finally {
+            readUnlock();
+        }
+    }
+
+    /*
+     * check password, if matched, save the userIdentity in matched entry.
+     * the following auth checking should use userIdentity saved in currentUser.
+     */
+    public void checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        if ((remoteUser.equals(ROOT_USER) || remoteUser.equals(ADMIN_USER)) && remoteHost.equals("127.0.0.1")) {
+            // root and admin user is allowed to login from 127.0.0.1, in case user forget password.
+            if (remoteUser.equals(ROOT_USER)) {
+                currentUser.add(UserIdentity.ROOT);
+            } else {
+                currentUser.add(UserIdentity.ADMIN);
+            }
+            return;
+        }
+        readLock();
+        try {
+            userManager.checkPassword(remoteUser, remoteHost, remotePasswd, randomString, currentUser);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    // For unit test only, wrapper of "void checkPlainPassword"
+    public boolean checkPlainPasswordForTest(String remoteUser, String remoteHost, String remotePasswd,
+            List<UserIdentity> currentUser) {
+        try {
+            checkPlainPassword(remoteUser, remoteHost, remotePasswd, currentUser);
+            return true;
+        } catch (AuthenticationException e) {
+            return false;
+        }
+    }
+
+    public void checkPlainPassword(String remoteUser, String remoteHost, String remotePasswd,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        // Check the LDAP password when the user exists in the LDAP service.
+        if (ldapManager.doesUserExist(remoteUser)) {
+            if (!ldapManager.checkUserPasswd(remoteUser, remotePasswd, remoteHost, currentUser)) {
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        Strings.isNullOrEmpty(remotePasswd) ? "NO" : "YES");
+            }
+            return;
+        }
+        readLock();
+        try {
+            userManager.checkPlainPassword(remoteUser, remoteHost, remotePasswd, currentUser);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public boolean checkGlobalPriv(ConnectContext ctx, PrivPredicate wanted) {
+        return checkGlobalPriv(ctx.getCurrentUserIdentity(), wanted);
+    }
+
+    public boolean checkGlobalPriv(UserIdentity currentUser, PrivPredicate wanted) {
+        if (isLdapAuthEnabled() && LdapPrivsChecker.hasGlobalPrivFromLdap(currentUser, wanted)) {
+            return true;
+        }
+        readLock();
+        try {
+            Set<String> roles = userRoleManager.getRolesByUser(currentUser);
+            for (String roleName : roles) {
+                if (roleManager.getRole(roleName).checkGlobalPriv(wanted)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            readUnlock();
+        }
+
+    }
+
+    public boolean checkCtlPriv(ConnectContext ctx, String ctl, PrivPredicate wanted) {
+        return checkCtlPriv(ctx.getCurrentUserIdentity(), ctl, wanted);
+    }
+
+    public boolean checkCtlPriv(UserIdentity currentUser, String ctl, PrivPredicate wanted) {
+        if (wanted.getPrivs().containsNodePriv()) {
+            LOG.debug("should not check NODE priv in catalog level. user: {}, catalog: {}",
+                    currentUser, ctl);
+            return false;
+        }
+        //ldap(before change to rbac)
+        readLock();
+        try {
+            Set<String> roles = userRoleManager.getRolesByUser(currentUser);
+            for (String roleName : roles) {
+                if (roleManager.getRole(roleName).checkCtlPriv(ctl, wanted)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            readUnlock();
+        }
+
+    }
+
+    public boolean checkDbPriv(ConnectContext ctx, String qualifiedDb, PrivPredicate wanted) {
+        return checkDbPriv(ctx.getCurrentUserIdentity(), qualifiedDb, wanted);
+    }
+
+    public boolean checkDbPriv(UserIdentity currentUser, String db, PrivPredicate wanted) {
+        return checkDbPriv(currentUser, DEFAULT_CATALOG, db, wanted);
+    }
+
+    public boolean checkDbPriv(ConnectContext ctx, String ctl, String db, PrivPredicate wanted) {
+        return checkDbPriv(ctx.getCurrentUserIdentity(), ctl, db, wanted);
+    }
+
+    /*
+     * Check if 'user'@'host' on 'db' has 'wanted' priv.
+     * If the given db is null, which means it will no check if database name is matched.
+     */
+    public boolean checkDbPriv(UserIdentity currentUser, String ctl, String db, PrivPredicate wanted) {
+        if (isLdapAuthEnabled() && (LdapPrivsChecker.hasDbPrivFromLdap(currentUser, wanted) || LdapPrivsChecker
+                .hasPrivsOfDb(currentUser, db))) {
+            return true;
+        }
+        if (wanted.getPrivs().containsNodePriv()) {
+            LOG.debug("should not check NODE priv in Database level. user: {}, db: {}",
+                    currentUser, db);
+            return false;
+        }
+        readLock();
+        try {
+            Set<String> roles = userRoleManager.getRolesByUser(currentUser);
+            for (String roleName : roles) {
+                if (roleManager.getRole(roleName).checkDbPriv(ctl, db, wanted)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            readUnlock();
+        }
+
+    }
+
+    public boolean checkTblPriv(ConnectContext ctx, String qualifiedCtl,
+            String qualifiedDb, String tbl, PrivPredicate wanted) {
+        return checkTblPriv(ctx.getCurrentUserIdentity(), qualifiedCtl, qualifiedDb, tbl, wanted);
+    }
+
+    public boolean checkTblPriv(ConnectContext ctx, String qualifiedDb, String tbl, PrivPredicate wanted) {
+        return checkTblPriv(ctx, DEFAULT_CATALOG, qualifiedDb, tbl, wanted);
+    }
+
+    public boolean checkTblPriv(ConnectContext ctx, TableName tableName, PrivPredicate wanted) {
+        Preconditions.checkState(tableName.isFullyQualified());
+        return checkTblPriv(ctx, tableName.getDb(), tableName.getTbl(), wanted);
+    }
+
+    public boolean checkTblPriv(UserIdentity currentUser, String db, String tbl, PrivPredicate wanted) {
+        return checkTblPriv(currentUser, DEFAULT_CATALOG, db, tbl, wanted);
+    }
+
+    public boolean checkTblPriv(UserIdentity currentUser, String ctl, String db, String tbl, PrivPredicate wanted) {
+        if (isLdapAuthEnabled() && LdapPrivsChecker.hasTblPrivFromLdap(currentUser, db, tbl, wanted)) {
+            return true;
+        }
+        if (wanted.getPrivs().containsNodePriv()) {
+            LOG.debug("should check NODE priv in GLOBAL level. user: {}, db: {}, tbl: {}", currentUser, db, tbl);
+            return false;
+        }
+        readLock();
+        try {
+            Set<String> roles = userRoleManager.getRolesByUser(currentUser);
+            for (String roleName : roles) {
+                if (roleManager.getRole(roleName).checkTblPriv(ctl, db, tbl, wanted)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public boolean checkResourcePriv(ConnectContext ctx, String resourceName, PrivPredicate wanted) {
+        return checkResourcePriv(ctx.getCurrentUserIdentity(), resourceName, wanted);
+    }
+
+    public boolean checkResourcePriv(UserIdentity currentUser, String resourceName, PrivPredicate wanted) {
+        if (isLdapAuthEnabled() && LdapPrivsChecker.hasResourcePrivFromLdap(currentUser, resourceName, wanted)) {
+            return true;
+        }
+        readLock();
+        try {
+            Set<String> roles = userRoleManager.getRolesByUser(currentUser);
+            for (String roleName : roles) {
+                if (roleManager.getRole(roleName).checkResourcePriv(resourceName, wanted)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public boolean checkPrivByAuthInfo(ConnectContext ctx, AuthorizationInfo authInfo, PrivPredicate wanted) {
+        if (authInfo == null) {
+            return false;
+        }
+        if (authInfo.getDbName() == null) {
+            return false;
+        }
+        if (authInfo.getTableNameList() == null || authInfo.getTableNameList().isEmpty()) {
+            return checkDbPriv(ctx, authInfo.getDbName(), wanted);
+        }
+        for (String tblName : authInfo.getTableNameList()) {
+            if (!checkTblPriv(ConnectContext.get(), authInfo.getDbName(), tblName, wanted)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /*
+     * Check if current user has certain privilege.
+     * This method will check the given privilege levels
+     */
+    public boolean checkHasPriv(ConnectContext ctx, PrivPredicate priv, PrivLevel... levels) {
+        if (isLdapAuthEnabled() && checkHasPrivLdap(ctx.getCurrentUserIdentity(), priv, levels)) {
+            return true;
+        }
+        readLock();
+        try {
+            Set<String> roles = userRoleManager.getRolesByUser(ctx.getCurrentUserIdentity());
+            for (String roleName : roles) {
+                if (roleManager.getRole(roleName).checkHasPriv(priv, levels)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            readUnlock();
+        }
+
+    }
+
+    public boolean checkHasPrivLdap(UserIdentity currentUser, PrivPredicate priv, PrivLevel... levels) {
+        for (PrivLevel privLevel : levels) {
+            switch (privLevel) {
+                case GLOBAL:
+                    if (LdapPrivsChecker.hasGlobalPrivFromLdap(currentUser, priv)) {
+                        return true;
+                    }
+                    break;
+                case DATABASE:
+                    if (LdapPrivsChecker.hasDbPrivFromLdap(currentUser, priv)) {
+                        return true;
+                    }
+                    break;
+                case TABLE:
+                    if (LdapPrivsChecker.hasTblPrivFromLdap(currentUser, priv)) {
+                        return true;
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+        return false;
+
+    }
+
+    // Check if LDAP authentication is enabled.
+    private boolean isLdapAuthEnabled() {
+        return LdapConfig.ldap_authentication_enabled;
+    }
+
+    // create user
+    public void createUser(CreateUserStmt stmt) throws DdlException {
+        createUserInternal(stmt.getUserIdent(), stmt.getQualifiedRole(),
+                stmt.getPassword(), stmt.isIfNotExist(), stmt.getPasswordOptions(), false);
+    }
+
+    public void replayCreateUser(PrivInfo privInfo) {
+        try {
+            createUserInternal(privInfo.getUserIdent(), privInfo.getRole(), privInfo.getPasswd(), false,
+                    privInfo.getPasswordOptions(), true);
+        } catch (DdlException e) {
+            LOG.error("should not happen", e);
+        }
+    }
+
+    private void createUserInternal(UserIdentity userIdent, String roleName, byte[] password,
+            boolean ignoreIfExists, PasswordOptions passwordOptions, boolean isReplay) throws DdlException {
+        writeLock();
+        try {
+            // check if role exist
+            Role role = null;
+            if (roleName != null) {
+                role = roleManager.getRole(roleName);
+                if (role == null) {
+                    throw new DdlException("Role: " + roleName + " does not exist");
+                }
+            }
+
+            // check if user already exist
+            if (doesUserExist(userIdent)) {
+                if (ignoreIfExists) {
+                    LOG.info("user exists, ignored to create user: {}, is replay: {}", userIdent, isReplay);
+                    return;
+                }
+                throw new DdlException("User " + userIdent + " already exist");
+            }
+
+            // create user
+            try {
+                //we should not throw AnalysisException at here,so transfer it
+                userManager.createUser(userIdent, password, null, false);
+            } catch (PatternMatcherException e) {
+                throw new DdlException("create user failed,", e);
+            }
+            if (password != null) {
+                // save password to password history
+                passwdPolicyManager.updatePassword(userIdent, password);
+            }
+            //4.create defaultRole
+            Role defaultRole = roleManager.createDefaultRole(userIdent);
+            // 5.create user role
+            userRoleManager.addUserRole(userIdent, defaultRole.getRoleName());
+            if (role != null) {
+                userRoleManager.addUserRole(userIdent, roleName);
+            }
+            // other user properties
+            propertyMgr.addUserResource(userIdent.getQualifiedUser(), false);
+
+            // 5. update password policy
+            passwdPolicyManager.updatePolicy(userIdent, password, passwordOptions);
+
+            if (!isReplay) {
+                PrivInfo privInfo = new PrivInfo(userIdent, null, password, roleName, passwordOptions);
+                Env.getCurrentEnv().getEditLog().logCreateUser(privInfo);
+            }
+            LOG.info("finished to create user: {}, is replay: {}", userIdent, isReplay);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    // drop user
+    public void dropUser(DropUserStmt stmt) throws DdlException {
+        dropUserInternal(stmt.getUserIdentity(), stmt.isSetIfExists(), false);
+    }
+
+    public void replayDropUser(UserIdentity userIdent) throws DdlException {
+        dropUserInternal(userIdent, false, true);
+    }
+
+    private void dropUserInternal(UserIdentity userIdent, boolean ignoreIfNonExists, boolean isReplay)
+            throws DdlException {
+        writeLock();
+        try {
+            // check if user exists
+            if (!doesUserExist(userIdent)) {
+                if (ignoreIfNonExists) {
+                    LOG.info("user non exists, ignored to drop user: {}, is replay: {}",
+                            userIdent.getQualifiedUser(), isReplay);
+                    return;
+                }
+                throw new DdlException(String.format("User `%s`@`%s` does not exist.",
+                        userIdent.getQualifiedUser(), userIdent.getHost()));
+            }
+
+            // drop default role
+            roleManager.removeDefaultRole(userIdent);
+            //drop user role
+            userRoleManager.dropUser(userIdent);
+            passwdPolicyManager.dropUser(userIdent);
+            userManager.removeUser(userIdent);
+            if (CollectionUtils.isEmpty(userManager.getUserByName(userIdent.getQualifiedUser()))) {
+                propertyMgr.dropUser(userIdent);
+            }
+
+            if (!isReplay) {
+                Env.getCurrentEnv().getEditLog().logNewDropUser(userIdent);
+            }
+            LOG.info("finished to drop user: {}, is replay: {}", userIdent.getQualifiedUser(), isReplay);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    // grant
+    public void grant(GrantStmt stmt) throws DdlException {
+        PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges());
+        if (stmt.getTblPattern() != null) {
+            grantInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getTblPattern(), privs,
+                    true /* err on non exist */, false /* not replay */);
+        } else {
+            grantInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getResourcePattern(), privs,
+                    true /* err on non exist */, false /* not replay */);
+        }
+    }
+
+    public void replayGrant(PrivInfo privInfo) {
+        try {
+            if (privInfo.getTblPattern() != null) {
+                grantInternal(privInfo.getUserIdent(), privInfo.getRole(),
+                        privInfo.getTblPattern(), privInfo.getPrivs(),
+                        true /* err on non exist */, true /* is replay */);
+            } else {
+                grantInternal(privInfo.getUserIdent(), privInfo.getRole(),
+                        privInfo.getResourcePattern(), privInfo.getPrivs(),
+                        true /* err on non exist */, true /* is replay */);
+            }
+        } catch (DdlException e) {
+            LOG.error("should not happen", e);
+        }
+    }
+
+    // grant for TablePattern
+    //if no role,role is default role of userIdent
+    private void grantInternal(UserIdentity userIdent, String role, TablePattern tblPattern,
+            PrivBitSet privs, boolean errOnNonExist, boolean isReplay)
+            throws DdlException {
+        writeLock();
+        try {
+            if (role == null) {
+                if (!doesUserExist(userIdent)) {
+                    throw new DdlException("user " + userIdent + " does not exist");
+                }
+                role = roleManager.getUserDefaultRoleName(userIdent);
+            }
+            Role newRole = new Role(role, tblPattern, privs);
+            roleManager.addRole(newRole, false /* err on exist */);
+            if (!isReplay) {
+                PrivInfo info = new PrivInfo(userIdent, tblPattern, privs, null, role);
+                Env.getCurrentEnv().getEditLog().logGrantPriv(info);
+            }
+            LOG.info("finished to grant privilege. is replay: {}", isReplay);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    // grant for ResourcePattern
+    private void grantInternal(UserIdentity userIdent, String role, ResourcePattern resourcePattern, PrivBitSet privs,
+            boolean errOnNonExist, boolean isReplay) throws DdlException {
+        writeLock();
+        try {
+            if (role == null) {
+                role = roleManager.getUserDefaultRoleName(userIdent);
+            }
+
+            // grant privs to role, role must exist
+            Role newRole = new Role(role, resourcePattern, privs);
+            roleManager.addRole(newRole, false /* err on exist */);
+
+            if (!isReplay) {
+                PrivInfo info = new PrivInfo(userIdent, resourcePattern, privs, null, role);
+                Env.getCurrentEnv().getEditLog().logGrantPriv(info);
+            }
+            LOG.info("finished to grant resource privilege. is replay: {}", isReplay);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+
+    // return true if user ident exist
+    private boolean doesUserExist(UserIdentity userIdent) {
+        return userManager.userIdentityExist(userIdent, false);
+    }
+
+    // Check whether the user exists. If the user exists, return UserIdentity, otherwise return null.
+    public UserIdentity getCurrentUserIdentity(UserIdentity userIdent) {
+        readLock();
+        try {
+            if (doesUserExist(userIdent)) {
+                return userIdent;
+            }
+            return null;
+        } finally {
+            readUnlock();
+        }
+    }
+
+    // revoke
+    public void revoke(RevokeStmt stmt) throws DdlException {
+        PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges());
+        if (stmt.getTblPattern() != null) {
+            revokeInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getTblPattern(), privs,
+                    true /* err on non exist */, false /* is replay */);
+        } else {
+            revokeInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getResourcePattern(), privs,
+                    true /* err on non exist */, false /* is replay */);
+        }
+    }
+
+    public void replayRevoke(PrivInfo info) {
+        try {
+            if (info.getTblPattern() != null) {
+                revokeInternal(info.getUserIdent(), info.getRole(), info.getTblPattern(), info.getPrivs(),
+                        true /* err on non exist */, true /* is replay */);
+            } else {
+                revokeInternal(info.getUserIdent(), info.getRole(), info.getResourcePattern(), info.getPrivs(),
+                        true /* err on non exist */, true /* is replay */);
+            }
+        } catch (DdlException e) {
+            LOG.error("should not happened", e);
+        }
+    }
+
+    private void revokeInternal(UserIdentity userIdent, String role, TablePattern tblPattern,
+            PrivBitSet privs, boolean errOnNonExist, boolean isReplay) throws DdlException {
+        writeLock();
+        try {
+            if (role == null) {
+                role = roleManager.getUserDefaultRoleName(userIdent);
+            }
+            // revoke privs from role
+            roleManager.revokePrivs(role, tblPattern, privs, errOnNonExist);
+
+            if (!isReplay) {
+                PrivInfo info = new PrivInfo(userIdent, tblPattern, privs, null, role);
+                Env.getCurrentEnv().getEditLog().logRevokePriv(info);
+            }
+            LOG.info("finished to revoke privilege. is replay: {}", isReplay);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    private void revokeInternal(UserIdentity userIdent, String role, ResourcePattern resourcePattern,
+            PrivBitSet privs, boolean errOnNonExist, boolean isReplay) throws DdlException {
+        writeLock();
+        try {
+            if (role == null) {
+                role = roleManager.getUserDefaultRoleName(userIdent);
+            }
+
+            // revoke privs from role
+            roleManager.revokePrivs(role, resourcePattern, privs, errOnNonExist);
+
+            if (!isReplay) {
+                PrivInfo info = new PrivInfo(userIdent, resourcePattern, privs, null, role);
+                Env.getCurrentEnv().getEditLog().logRevokePriv(info);
+            }
+            LOG.info("finished to revoke privilege. is replay: {}", isReplay);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    // set password
+    public void setPassword(SetPassVar stmt) throws DdlException {
+        setPasswordInternal(stmt.getUserIdent(), stmt.getPassword(), null, true /* err on non exist */,
+                false /* set by resolver */, false);
+    }
+
+    public void replaySetPassword(PrivInfo info) {
+        try {
+            setPasswordInternal(info.getUserIdent(), info.getPasswd(), null, true /* err on non exist */,
+                    false /* set by resolver */, true);
+        } catch (DdlException e) {
+            LOG.error("should not happened", e);
+        }
+    }
+
+    public void setPasswordInternal(UserIdentity userIdent, byte[] password, UserIdentity domainUserIdent,
+            boolean errOnNonExist, boolean setByResolver, boolean isReplay) throws DdlException {
+        Preconditions.checkArgument(!setByResolver || domainUserIdent != null, setByResolver + ", " + domainUserIdent);
+        writeLock();
+        try {
+            if (!isReplay) {
+                if (!passwdPolicyManager.checkPasswordHistory(userIdent, password)) {
+                    ErrorReport.reportDdlException(ErrorCode.ERR_CREDENTIALS_CONTRADICT_TO_HISTORY,
+                            userIdent.getQualifiedUser(), userIdent.getHost());
+                }
+            }
+            userManager.setPassword(userIdent, password, errOnNonExist);
+            if (password != null) {
+                // save password to password history
+                passwdPolicyManager.updatePassword(userIdent, password);
+            }
+
+            if (!isReplay) {
+                PrivInfo info = new PrivInfo(userIdent, null, password, null, null);
+                Env.getCurrentEnv().getEditLog().logSetPassword(info);
+            }
+        } finally {
+            writeUnlock();
+        }
+        LOG.info("finished to set password for {}. is replay: {}", userIdent, isReplay);
+    }
+
+    // set ldap admin password.
+    public void setLdapPassword(SetLdapPassVar stmt) {
+        ldapInfo = new LdapInfo(stmt.getLdapPassword());
+        Env.getCurrentEnv().getEditLog().logSetLdapPassword(ldapInfo);
+        LOG.info("finished to set ldap password.");
+    }
+
+    public void replaySetLdapPassword(LdapInfo info) {
+        ldapInfo = info;
+        LOG.debug("finish replaying ldap admin password.");
+    }
+
+    // create role
+    public void createRole(CreateRoleStmt stmt) throws DdlException {
+        createRoleInternal(stmt.getQualifiedRole(), stmt.isSetIfNotExists(), false);
+    }
+
+    public void replayCreateRole(PrivInfo info) {
+        try {
+            createRoleInternal(info.getRole(), false, true);
+        } catch (DdlException e) {
+            LOG.error("should not happened", e);
+        }
+    }
+
+    private void createRoleInternal(String role, boolean ignoreIfExists, boolean isReplay) throws DdlException {
+        Role emptyPrivsRole = new Role(role);
+        writeLock();
+        try {
+            if (ignoreIfExists && roleManager.getRole(role) != null) {
+                LOG.info("role exists, ignored to create role: {}, is replay: {}", role, isReplay);
+                return;
+            }
+
+            roleManager.addRole(emptyPrivsRole, true /* err on exist */);
+
+            if (!isReplay) {
+                PrivInfo info = new PrivInfo(null, null, null, role, null);
+                Env.getCurrentEnv().getEditLog().logCreateRole(info);
+            }
+        } finally {
+            writeUnlock();
+        }
+        LOG.info("finished to create role: {}, is replay: {}", role, isReplay);
+    }
+
+    // drop role
+    public void dropRole(DropRoleStmt stmt) throws DdlException {
+        dropRoleInternal(stmt.getQualifiedRole(), stmt.isSetIfExists(), false);
+    }
+
+    public void replayDropRole(PrivInfo info) {
+        try {
+            dropRoleInternal(info.getRole(), false, true);
+        } catch (DdlException e) {
+            LOG.error("should not happened", e);
+        }
+    }
+
+    private void dropRoleInternal(String role, boolean ignoreIfNonExists, boolean isReplay) throws DdlException {
+        writeLock();
+        try {
+            if (role.startsWith(RoleManager.DEFAULT_ROLE_PREFIX)) {
+                throw new DdlException("Can not drop default role.");
+            }
+            if (ignoreIfNonExists && roleManager.getRole(role) == null) {
+                LOG.info("role non exists, ignored to drop role: {}, is replay: {}", role, isReplay);
+                return;
+            }
+
+            roleManager.dropRole(role, true /* err on non exist */);
+            userRoleManager.dropRole(role);
+            if (!isReplay) {
+                PrivInfo info = new PrivInfo(null, null, null, role, null);
+                Env.getCurrentEnv().getEditLog().logDropRole(info);
+            }
+        } finally {
+            writeUnlock();
+        }
+        LOG.info("finished to drop role: {}, is replay: {}", role, isReplay);
+    }
+
+    // update user property
+    public void updateUserProperty(SetUserPropertyStmt stmt) throws UserException {
+        List<Pair<String, String>> properties = stmt.getPropertyPairList();
+        updateUserPropertyInternal(stmt.getUser(), properties, false /* is replay */);
+    }
+
+    public void replayUpdateUserProperty(UserPropertyInfo propInfo) throws UserException {
+        updateUserPropertyInternal(propInfo.getUser(), propInfo.getProperties(), true /* is replay */);
+    }
+
+    public void updateUserPropertyInternal(String user, List<Pair<String, String>> properties, boolean isReplay)
+            throws UserException {
+        writeLock();
+        try {
+            propertyMgr.updateUserProperty(user, properties);
+            if (!isReplay) {
+                UserPropertyInfo propertyInfo = new UserPropertyInfo(user, properties);
+                Env.getCurrentEnv().getEditLog().logUpdateUserProperty(propertyInfo);
+            }
+            LOG.info("finished to set properties for user: {}", user);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    public long getMaxConn(String qualifiedUser) {
+        readLock();
+        try {
+            return propertyMgr.getMaxConn(qualifiedUser);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public long getQueryTimeout(String qualifiedUser) {
+        readLock();
+        try {
+            return propertyMgr.getQueryTimeout(qualifiedUser);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public long getMaxQueryInstances(String qualifiedUser) {
+        readLock();
+        try {
+            return propertyMgr.getMaxQueryInstances(qualifiedUser);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public String[] getSqlBlockRules(String qualifiedUser) {
+        readLock();
+        try {
+            return propertyMgr.getSqlBlockRules(qualifiedUser);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public int getCpuResourceLimit(String qualifiedUser) {
+        readLock();
+        try {
+            return propertyMgr.getCpuResourceLimit(qualifiedUser);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public Set<Tag> getResourceTags(String qualifiedUser) {
+        readLock();
+        try {
+            return propertyMgr.getResourceTags(qualifiedUser);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public long getExecMemLimit(String qualifiedUser) {
+        readLock();
+        try {
+            return propertyMgr.getExecMemLimit(qualifiedUser);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public void getAllDomains(Set<String> allDomains) {
+        readLock();
+        try {
+            userManager.getAllDomains(allDomains);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    // refresh all user set by domain resolver.
+    public void refreshUserPrivEntriesByResovledIPs(Map<String, Set<String>> resolvedIPsMap) {
+        writeLock();
+        try {
+            // 1. delete all user
+            userManager.clearEntriesSetByResolver();
+            // 2. add new user
+            userManager.addUserPrivEntriesByResolvedIPs(resolvedIPsMap);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    // return the auth info of specified user, or infos of all users, if user is not specified.
+    // the returned columns are defined in AuthProcDir
+    // the specified user identity should be the identity created by CREATE USER, same as result of
+    // SELECT CURRENT_USER();
+    public List<List<String>> getAuthInfo(UserIdentity specifiedUserIdent) {
+        List<List<String>> userAuthInfos = Lists.newArrayList();
+        readLock();
+        try {
+            if (specifiedUserIdent == null) {
+                // get all users' auth info
+                Map<String, List<User>> nameToUsers = userManager.getNameToUsers();
+                for (List<User> users : nameToUsers.values()) {
+                    for (User user : users) {
+                        if (!user.isSetByDomainResolver()) {
+                            getUserAuthInfo(userAuthInfos, user.getUserIdentity());
+                        }
+                    }
+                }
+            } else {
+                getUserAuthInfo(userAuthInfos, specifiedUserIdent);
+            }
+        } finally {
+            readUnlock();
+        }
+        return userAuthInfos;
+    }
+
+    private void getUserAuthInfo(List<List<String>> userAuthInfos, UserIdentity userIdent) {
+        // AuthProcDir.TITLE_NAMES
+        List<String> userAuthInfo = Lists.newArrayList();
+        User user = userManager.getUserByUserIdentity(userIdent);
+        // ================= UserIdentity =======================
+        userAuthInfo.add(userIdent.toString());
+        // ============== Password ==============
+        userAuthInfo.add(user.hasPassword() ? "Yes" : "No");
+        // ==============GlobalPrivs==============
+        PrivBitSet ldapGlobalPrivs = LdapPrivsChecker.getGlobalPrivFromLdap(userIdent);
+        PrivBitSet globalPrivs = ldapGlobalPrivs.copy();
+        List<PrivEntry> globalEntries = getUserGlobalPrivTable(userIdent).entries;
+        if (!CollectionUtils.isEmpty(globalEntries)) {
+            globalPrivs.or(globalEntries.get(0).privSet);
+        }
+        userAuthInfo.add(globalPrivs.isEmpty() ? FeConstants.null_string : globalPrivs.toString());
+        // ============== CatalogPrivs ========================
+        String ctlPrivs = getUserCtlPrivTable(userIdent).entries.stream()
+                .map(entry -> String.format("%s: %s",
+                        ((CatalogPrivEntry) entry).getOrigCtl(), entry.privSet, user.isSetByDomainResolver()))
+                .collect(Collectors.joining("; "));
+        if (Strings.isNullOrEmpty(ctlPrivs)) {
+            ctlPrivs = FeConstants.null_string;
+        }
+        userAuthInfo.add(ctlPrivs);
+        // ============== DatabasePrivs ==============
+        List<String> dbPrivs = Lists.newArrayList();
+        Set<String> addedDbs = Sets.newHashSet();
+        for (PrivEntry entry : getUserDbPrivTable(userIdent).entries) {
+            DbPrivEntry dEntry = (DbPrivEntry) entry;
+            /**
+             * Doris and Ldap may have different privs on one database.
+             * Merge these privs and add.
+             */
+            PrivBitSet savedPrivs = dEntry.getPrivSet().copy();
+            savedPrivs.or(LdapPrivsChecker.getDbPrivFromLdap(userIdent, dEntry.getOrigDb()));
+            addedDbs.add(dEntry.getOrigDb());
+            dbPrivs.add(String.format("%s.%s: %s", dEntry.getOrigCtl(), dEntry.getOrigDb(),
+                    savedPrivs));
+        }
+        // Add privs from ldap groups that have not been added in Doris.
+        if (LdapPrivsChecker.hasLdapPrivs(userIdent)) {
+            Map<TablePattern, PrivBitSet> ldapDbPrivs = LdapPrivsChecker.getLdapAllDbPrivs(userIdent);
+            for (Entry<TablePattern, PrivBitSet> entry : ldapDbPrivs.entrySet()) {
+                if (!addedDbs.contains(entry.getKey().getQualifiedDb())) {
+                    dbPrivs.add(String.format("%s.%s: %s", entry.getKey().getQualifiedCtl(),
+                            entry.getKey().getQualifiedDb(), entry.getValue()));
+                }
+            }
+        }
+
+        if (dbPrivs.isEmpty()) {
+            userAuthInfo.add(FeConstants.null_string);
+        } else {
+            userAuthInfo.add(Joiner.on("; ").join(dbPrivs));
+        }
+
+        // tbl
+        List<String> tblPrivs = Lists.newArrayList();
+        Set<String> addedtbls = Sets.newHashSet();
+        for (PrivEntry entry : getUserTblPrivTable(userIdent).entries) {
+            TablePrivEntry tEntry = (TablePrivEntry) entry;
+            /**
+             * Doris and Ldap may have different privs on one table.
+             * Merge these privs and add.
+             */
+            PrivBitSet savedPrivs = tEntry.getPrivSet().copy();
+            savedPrivs.or(LdapPrivsChecker.getTblPrivFromLdap(userIdent, tEntry.getOrigDb(), tEntry.getOrigTbl()));
+            addedtbls.add(tEntry.getOrigDb().concat(".").concat(tEntry.getOrigTbl()));
+            tblPrivs.add(String.format("%s.%s.%s: %s", tEntry.getOrigCtl(), tEntry.getOrigDb(),
+                    tEntry.getOrigTbl(), savedPrivs));
+        }
+        // Add privs from ldap groups that have not been added in Doris.
+        if (LdapPrivsChecker.hasLdapPrivs(userIdent)) {
+            Map<TablePattern, PrivBitSet> ldapTblPrivs = LdapPrivsChecker.getLdapAllTblPrivs(userIdent);
+            for (Entry<TablePattern, PrivBitSet> entry : ldapTblPrivs.entrySet()) {
+                if (!addedtbls.contains(entry.getKey().getQualifiedDb().concat(".").concat(entry.getKey().getTbl()))) {
+                    tblPrivs.add(String.format("%s: %s", entry.getKey(), entry.getValue()));
+                }
+            }
+        }
+
+        if (tblPrivs.isEmpty()) {
+            userAuthInfo.add(FeConstants.null_string);
+        } else {
+            userAuthInfo.add(Joiner.on("; ").join(tblPrivs));
+        }
+
+        // resource
+        List<String> resourcePrivs = Lists.newArrayList();
+        Set<String> addedResources = Sets.newHashSet();
+        for (PrivEntry entry : getUserResourcePrivTable(userIdent).entries) {
+            ResourcePrivEntry rEntry = (ResourcePrivEntry) entry;
+            /**
+             * Doris and Ldap may have different privs on one resource.
+             * Merge these privs and add.
+             */
+            PrivBitSet savedPrivs = rEntry.getPrivSet().copy();
+            savedPrivs.or(LdapPrivsChecker.getResourcePrivFromLdap(userIdent, rEntry.getOrigResource()));
+            addedResources.add(rEntry.getOrigResource());
+            resourcePrivs.add(rEntry.getOrigResource() + ": " + savedPrivs.toString());
+        }
+        // Add privs from ldap groups that have not been added in Doris.
+        if (LdapPrivsChecker.hasLdapPrivs(userIdent)) {
+            Map<ResourcePattern, PrivBitSet> ldapResourcePrivs = LdapPrivsChecker.getLdapAllResourcePrivs(userIdent);
+            for (Entry<ResourcePattern, PrivBitSet> entry : ldapResourcePrivs.entrySet()) {
+                if (!addedResources.contains(entry.getKey().getResourceName())) {
+                    tblPrivs.add(entry.getKey().getResourceName().concat(": ").concat(entry.getValue().toString()));
+                }
+            }
+        }
+
+        if (resourcePrivs.isEmpty()) {
+            userAuthInfo.add(FeConstants.null_string);
+        } else {
+            userAuthInfo.add(Joiner.on("; ").join(resourcePrivs));
+        }
+
+        userAuthInfos.add(userAuthInfo);
+    }
+
+    private GlobalPrivTable getUserGlobalPrivTable(UserIdentity userIdentity) {
+        GlobalPrivTable table = new GlobalPrivTable();
+        Set<String> roles = userRoleManager.getRolesByUser(userIdentity);
+        for (String roleName : roles) {
+            table.merge(roleManager.getRole(roleName).getGlobalPrivTable());
+        }
+        return table;
+    }
+
+    private CatalogPrivTable getUserCtlPrivTable(UserIdentity userIdentity) {
+        CatalogPrivTable table = new CatalogPrivTable();
+        Set<String> roles = userRoleManager.getRolesByUser(userIdentity);
+        for (String roleName : roles) {
+            table.merge(roleManager.getRole(roleName).getCatalogPrivTable());
+        }
+        return table;
+    }
+
+    private DbPrivTable getUserDbPrivTable(UserIdentity userIdentity) {
+        DbPrivTable table = new DbPrivTable();
+        Set<String> roles = userRoleManager.getRolesByUser(userIdentity);
+        for (String roleName : roles) {
+            table.merge(roleManager.getRole(roleName).getDbPrivTable());
+        }
+        return table;
+    }
+
+    private TablePrivTable getUserTblPrivTable(UserIdentity userIdentity) {
+        TablePrivTable table = new TablePrivTable();
+        Set<String> roles = userRoleManager.getRolesByUser(userIdentity);
+        for (String roleName : roles) {
+            table.merge(roleManager.getRole(roleName).getTablePrivTable());
+        }
+        return table;
+    }
+
+    private ResourcePrivTable getUserResourcePrivTable(UserIdentity userIdentity) {
+        ResourcePrivTable table = new ResourcePrivTable();
+        Set<String> roles = userRoleManager.getRolesByUser(userIdentity);
+        for (String roleName : roles) {
+            table.merge(roleManager.getRole(roleName).getResourcePrivTable());
+        }
+        return table;
+    }
+
+    public List<List<String>> getUserProperties(String qualifiedUser) {
+        readLock();
+        try {
+            return propertyMgr.fetchUserProperty(qualifiedUser);
+        } catch (AnalysisException e) {
+            return Lists.newArrayList();
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public void dropUserOfCluster(String clusterName, boolean isReplay) throws DdlException {
+        writeLock();
+        try {
+            Map<String, List<User>> nameToUsers = userManager.getNameToUsers();
+            for (List<User> users : nameToUsers.values()) {
+                for (User user : users) {
+                    if (user.getUserIdentity().getQualifiedUser().startsWith(clusterName)) {
+                        dropUserInternal(user.getUserIdentity(), false, isReplay);
+                    }
+                }
+            }
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    public Pair<String, DppConfig> getLoadClusterInfo(String qualifiedUser, String cluster) throws DdlException {
+        readLock();
+        try {
+            return propertyMgr.getLoadClusterInfo(qualifiedUser, cluster);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    // user can enter a cluster, if it has any privs of database or table in this cluster.
+    public boolean checkCanEnterCluster(ConnectContext ctx, String clusterName) {
+        readLock();
+        try {
+            Set<String> roles = userRoleManager.getRolesByUser(ctx.getCurrentUserIdentity());
+            for (String roleName : roles) {
+                if (roleManager.getRole(roleName).checkCanEnterCluster(clusterName)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            readUnlock();
+        }
+    }
+
+    private void initUser() {
+        try {
+            UserIdentity rootUser = new UserIdentity(ROOT_USER, "%");
+            rootUser.setIsAnalyzed();
+            createUserInternal(rootUser, Role.OPERATOR_ROLE, new byte[0],
+                    false /* ignore if exists */, PasswordOptions.UNSET_OPTION, true /* is replay */);
+            UserIdentity adminUser = new UserIdentity(ADMIN_USER, "%");
+            adminUser.setIsAnalyzed();
+            createUserInternal(adminUser, Role.ADMIN_ROLE, new byte[0],
+                    false /* ignore if exists */, PasswordOptions.UNSET_OPTION, true /* is replay */);
+        } catch (DdlException e) {
+            LOG.error("should not happened", e);
+        }
+    }
+
+    public TFetchResourceResult toResourceThrift() {
+        readLock();
+        try {
+            return propertyMgr.toResourceThrift();
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public List<List<String>> getRoleInfo() {
+        readLock();
+        try {
+            List<List<String>> results = Lists.newArrayList();
+            roleManager.getRoleInfo(results);
+            return results;
+        } finally {
+            readUnlock();
+        }
+    }
+
+    // Used for creating table_privileges table in information_schema.
+    public void getTablePrivStatus(List<TPrivilegeStatus> tblPrivResult, UserIdentity currentUser) {
+        readLock();
+        try {
+            Map<String, List<User>> nameToUsers = userManager.getNameToUsers();
+            for (List<User> users : nameToUsers.values()) {
+                for (User user : users) {
+                    if (!user.isSetByDomainResolver()) {
+                        TablePrivTable tablePrivTable = getUserTblPrivTable(user.getUserIdentity());
+                        if (tablePrivTable.isEmpty()) {
+                            continue;
+                        }
+                        for (PrivEntry entry : tablePrivTable.getEntries()) {
+                            TablePrivEntry tablePrivEntry = (TablePrivEntry) entry;
+                            String dbName = ClusterNamespace.getNameFromFullName(tablePrivEntry.getOrigDb());
+                            String tblName = tablePrivEntry.getOrigTbl();
+                            if (dbName.equals("information_schema" /* Don't show privileges in information_schema */)

Review Comment:
   InfoschemaDb.xxxx



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java:
##########
@@ -72,37 +61,29 @@ public boolean hasPriv(String host, String user, PrivPredicate wanted) {
      * NOTICE, this method does not set password for the newly added entry if this is a user priv table, the caller
      * need to set password later.
      */
-    public PrivEntry addEntry(PrivEntry newEntry, boolean errOnExist, boolean errOnNonExist) throws DdlException {
+    public PrivEntry addEntry(PrivEntry newEntry,
+            boolean errOnExist, boolean errOnNonExist) throws DdlException {
         PrivEntry existingEntry = getExistingEntry(newEntry);
         if (existingEntry == null) {
             if (errOnNonExist) {
-                throw new DdlException("User " + newEntry.getUserIdent() + " does not exist");
+                throw new DdlException("entry does not exist");
             }
             entries.add(newEntry);
             Collections.sort(entries);
             LOG.info("add priv entry: {}", newEntry);
             return newEntry;
         } else {
             if (errOnExist) {
-                throw new DdlException("User already exist");
+                throw new DdlException("entry already exist");
             } else {
-                checkOperationAllowed(existingEntry, newEntry, "ADD ENTRY");
-                // if existing entry is set by domain resolver, just replace it with the new entry.
-                // if existing entry is not set by domain resolver, merge the 2 entries.
-                if (existingEntry.isSetByDomainResolver()) {
-                    existingEntry.setPrivSet(newEntry.getPrivSet());
-                    existingEntry.setSetByDomainResolver(newEntry.isSetByDomainResolver());
-                    LOG.debug("reset priv entry: {}", existingEntry);
-                } else if (!newEntry.isSetByDomainResolver()) {
-                    mergePriv(existingEntry, newEntry);
-                    existingEntry.setSetByDomainResolver(false);
-                    LOG.debug("merge priv entry: {}", existingEntry);
-                }
+                mergePriv(existingEntry, newEntry);
+                LOG.debug("merge priv entry: {}", existingEntry);

Review Comment:
   1. create user user1@domain   -> domain(ip1, ip2)
   2. create user user1@ip1
   How to handle it?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr closed pull request #16091: [wip](auth)Implementing privilege management with rbac model

Posted by GitBox <gi...@apache.org>.
zddr closed pull request #16091: [wip](auth)Implementing privilege management with rbac model
URL: https://github.com/apache/doris/pull/16091


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] morningman commented on pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "morningman (via GitHub)" <gi...@apache.org>.
morningman commented on PR #16091:
URL: https://github.com/apache/doris/pull/16091#issuecomment-1420917310

   And need to check that the user name can not start with `DEFAULT_ROLE_PREFIX`


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] morningman commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "morningman (via GitHub)" <gi...@apache.org>.
morningman commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1097528550


##########
fe/fe-core/src/main/java/org/apache/doris/ldap/LdapManager.java:
##########
@@ -76,7 +77,12 @@ public LdapUserInfo getUserInfo(String fullName) {
         if (ldapUserInfo != null && !ldapUserInfo.checkTimeout()) {
             return ldapUserInfo;
         }
-        return getUserInfoAndUpdateCache(fullName);
+        try {
+            return getUserInfoAndUpdateCache(fullName);
+        } catch (DdlException e) {
+            LOG.warn("getUserInfo failed,", e);

Review Comment:
   ```suggestion
               LOG.warn("getUserInfo for {} failed", fullName, e);
   ```



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogPrivTable.java:
##########
@@ -36,15 +30,11 @@ public class CatalogPrivTable extends PrivTable {
      * Return first priv which match the user@host on ctl.* The returned priv will be
      * saved in 'savedPrivs'.
      */
-    public void getPrivs(UserIdentity currentUser, String ctl, PrivBitSet savedPrivs) {
+    public void getPrivs(String ctl, PrivBitSet savedPrivs) {
         CatalogPrivEntry matchedEntry = null;
         for (PrivEntry entry : entries) {
             CatalogPrivEntry dsPrivEntry = (CatalogPrivEntry) entry;

Review Comment:
   ```suggestion
               CatalogPrivEntry ctlPrivEntry = (CatalogPrivEntry) entry;
   ```



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/GlobalPrivTable.java:
##########
@@ -0,0 +1,46 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/*
+ * GlobalPrivTable saves all global privs and also password for users
+ */
+public class GlobalPrivTable extends PrivTable {
+    private static final Logger LOG = LogManager.getLogger(GlobalPrivTable.class);
+
+    public GlobalPrivTable() {
+    }
+
+    public void getPrivs(PrivBitSet savedPrivs) {
+        GlobalPrivEntry matchedEntry = null;
+        for (PrivEntry entry : entries) {
+            GlobalPrivEntry globalPrivEntry = (GlobalPrivEntry) entry;

Review Comment:
   add comment in code to explain this logic



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivEntry.java:
##########
@@ -82,108 +112,7 @@ protected PrivEntry(PatternMatcher hostPattern, String origHost, PatternMatcher
         }
     }
 
-    public PatternMatcher getHostPattern() {
-        return hostPattern;
-    }
-
-    public String getOrigHost() {
-        return origHost;
-    }
-
-    public boolean isAnyHost() {
-        return isAnyHost;
-    }
-
-    public PatternMatcher getUserPattern() {
-        return userPattern;
-    }
-
-    public String getOrigUser() {
-        return origUser;
-    }
-
-    public boolean isAnyUser() {
-        return isAnyUser;
-    }
-
-    public PrivBitSet getPrivSet() {
-        return privSet;
-    }
-
-    public void setPrivSet(PrivBitSet privSet) {
-        this.privSet = privSet;
-    }
-
-    public boolean isSetByDomainResolver() {
-        return isSetByDomainResolver;
-    }
-
-    public void setSetByDomainResolver(boolean isSetByDomainResolver) {
-        this.isSetByDomainResolver = isSetByDomainResolver;
-    }
-
-    public UserIdentity getUserIdent() {
-        return userIdentity;
-    }
-
-    public boolean match(UserIdentity userIdent, boolean exactMatch) {
-        if (exactMatch) {
-            return origUser.equals(userIdent.getQualifiedUser()) && origHost.equals(userIdent.getHost());
-        } else {
-            return origUser.equals(userIdent.getQualifiedUser()) && hostPattern.match(userIdent.getHost());
-        }
-    }
-
-    public abstract boolean keyMatch(PrivEntry other);
-
-    /*
-     * It's a bit complicated when persisting instance which its class has derived classes.
-     * eg: A (top class) -> B (derived) -> C (derived)
-     *
-     * Write process:
-     * C.write()
-     *      |
-     *      --- write class name
-     *      |
-     *      --- super.write()    -----> B.write()
-     *      |                               |
-     *      --- write C's self members      --- write class name (if not write before)
-     *                                      |
-     *                                      --- super.write()    -----> A.write()
-     *                                      |                               |
-     *                                      --- write B's self members      --- write class name (if not write before)
-     *                                                                      |
-     *                                                                      --- write A's self members
-     *
-     * So the final write order is:
-     *      1. C's class name
-     *      2. A's self members
-     *      3. B's self members
-     *      4. C's self members
-     *
-     * In case that class name should only be wrote once, we use isClassNameWrote flag.
-     *
-     * Read process:
-     * static A.read()
-     *      |
-     *      --- read class name and instantiated the class instance (eg. C class)
-     *      |
-     *      --- C.readFields()
-     *          |
-     *          --- super.readFields() --> B.readFields()
-     *          |                           |
-     *          --- read C's self members   --- super.readFields() --> A.readFields()
-     *                                      |                           |
-     *                                      --- read B's self members   --- read A's self members
-     *
-     *  So the final read order is:
-     *      1. C's class name
-     *      2. A's self members
-     *      3. B's self members
-     *      4. C's self members
-     *
-     *  Which is same as Write order.
-     */
+    @Deprecated
     public static PrivEntry read(DataInput in) throws IOException {
         String className = Text.readString(in);
         if (className.startsWith("com.baidu.palo")) {

Review Comment:
   This can be removed



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Password.java:
##########
@@ -0,0 +1,61 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.common.io.Writable;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class Password implements Writable {
+    public Password() {
+    }
+
+    public Password(byte[] password) {
+        this.password = password;
+    }
+
+    private byte[] password;

Review Comment:
   Move the field at the begin of this class



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Password.java:
##########
@@ -0,0 +1,61 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.common.io.Writable;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class Password implements Writable {
+    public Password() {
+    }
+
+    public Password(byte[] password) {
+        this.password = password;
+    }
+
+    private byte[] password;
+
+    public static Password read(DataInput in) throws IOException {
+        Password pwd = new Password();

Review Comment:
   use Gson to do the read/write



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java:
##########
@@ -40,19 +44,25 @@
 import java.util.stream.Stream;
 
 public class RoleManager implements Writable {
-    private Map<String, PaloRole> roles = Maps.newHashMap();
+    private static final Logger LOG = LogManager.getLogger(RoleManager.class);
+    //prefix of each user default role
+    public static String DEFAULT_ROLE_PREFIX = "default_role_rbac_";

Review Comment:
   Need to check to not allow user create role with this prefix



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/User.java:
##########
@@ -0,0 +1,143 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Writable;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class User implements Comparable<User>, Writable {
+    private static final Logger LOG = LogManager.getLogger(User.class);
+
+    private UserIdentity userIdentity;
+    private UserIdentity domainUserIdentity;
+    private boolean isSetByDomainResolver = false;
+    // host is not case sensitive
+    protected PatternMatcher hostPattern;
+    protected boolean isAnyHost = false;
+    private Password password;
+
+
+    public Password getPassword() {
+        return password;
+    }
+
+    public void setPassword(Password password) {
+        this.password = password;
+    }
+
+    public void setPassword(byte[] password) {
+        this.password = new Password(password);
+    }
+
+    public UserIdentity getUserIdentity() {
+        return userIdentity;
+    }
+
+    public void setUserIdentity(UserIdentity userIdentity) {
+        this.userIdentity = userIdentity;
+    }
+
+    public UserIdentity getDomainUserIdentity() {
+        if (isSetByDomainResolver()) {
+            return domainUserIdentity;
+        } else {
+            return userIdentity;
+        }
+
+    }
+
+    public void setDomainUserIdentity(UserIdentity domainUserIdentity) {
+        this.domainUserIdentity = domainUserIdentity;
+    }
+
+    public boolean isSetByDomainResolver() {
+        return isSetByDomainResolver;
+    }
+
+    public void setSetByDomainResolver(boolean setByDomainResolver) {
+        isSetByDomainResolver = setByDomainResolver;
+    }
+
+    public PatternMatcher getHostPattern() {
+        return hostPattern;
+    }
+
+    public void setHostPattern(PatternMatcher hostPattern) {
+        this.hostPattern = hostPattern;
+    }
+
+    public boolean isAnyHost() {
+        return isAnyHost;
+    }
+
+    public void setAnyHost(boolean anyHost) {
+        isAnyHost = anyHost;
+    }
+
+    public boolean hasPassword() {
+        return password != null && password.getPassword() != null && password.getPassword().length != 0;
+    }
+
+    @Override
+    public int compareTo(@NotNull User o) {
+        return -userIdentity.getHost().compareTo(o.userIdentity.getHost());
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        userIdentity.write(out);

Review Comment:
   Use Gson serde



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+    public static final String ANY_HOST = "%";
+    private static final Logger LOG = LogManager.getLogger(UserManager.class);
+    //    Set<User> users = new HashSet<>();
+    //One name may have multiple User,because host can be different
+    private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+    public boolean userIdentityExist(UserIdentity userIdentity, boolean includeByDomain) {
+        List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+        if (CollectionUtils.isEmpty(users)) {
+            return false;
+        }
+        for (User user : users) {
+            if (user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+                if (includeByDomain || !user.isSetByDomainResolver()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public List<User> getUserByName(String name) {
+        List<User> users = nameToUsers.get(name);
+        return users == null ? Collections.EMPTY_LIST : users;
+    }
+
+    public void checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        LOG.debug("check password for user: {} from {}, password: {}, random string: {}",
+                remoteUser, remoteHost, remotePasswd, randomString);
+
+        PasswordPolicyManager passwdPolicyMgr = Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+        List<User> users = nameToUsers.get(remoteUser);
+        if (CollectionUtils.isEmpty(users)) {
+            return;
+        }
+
+        for (User user : users) {
+            if (user.getUserIdentity().isDomain()) {
+                continue;
+            }
+            // check host
+            if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost)) {
+                continue;
+            }
+            UserIdentity curUser = user.getDomainUserIdentity();
+            // check password
+            byte[] saltPassword = MysqlPassword.getSaltFromPassword(user.getPassword().getPassword());
+            // when the length of password is zero, the user has no password
+            if ((remotePasswd.length == saltPassword.length)
+                    && (remotePasswd.length == 0
+                    || MysqlPassword.checkScramble(remotePasswd, randomString, saltPassword))) {
+                passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+                // found the matched entry
+                if (currentUser != null) {
+                    currentUser.add(curUser);
+                }
+                return;
+            } else {
+                // case A. this means we already matched a entry by user@host, but password is incorrect.
+                // return false, NOT continue matching other entries.
+                // For example, there are 2 entries in order:
+                // 1. cmy@"192.168.%" identified by '123';
+                // 2. cmy@"%" identified by 'abc';
+                // if user cmy@'192.168.1.1' try to login with password 'abc', it will be denied.
+                passwdPolicyMgr.onFailedLogin(curUser);
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        remotePasswd.length == 0 ? "NO" : "YES");
+            }
+        }
+
+    }
+
+    public void checkPlainPassword(String remoteUser, String remoteHost, String remotePasswd,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        PasswordPolicyManager passwdPolicyMgr = Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+        List<User> users = nameToUsers.get(remoteUser);
+        if (CollectionUtils.isEmpty(users)) {
+            throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                    "YES");
+        }
+        for (User user : users) {
+            if (user.getUserIdentity().isDomain()) {
+                continue;
+            }
+            // check host
+            if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost)) {
+                continue;
+            }
+            UserIdentity curUser = user.getDomainUserIdentity();
+            if (MysqlPassword.checkPlainPass(user.getPassword().getPassword(), remotePasswd)) {
+                passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+                if (currentUser != null) {
+                    currentUser.add(curUser);
+                }
+                return;
+            } else {
+                // set case A. in checkPassword()
+                passwdPolicyMgr.onFailedLogin(curUser);
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        "YES");
+            }
+        }
+        throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                "YES");
+    }
+
+    public void clearEntriesSetByResolver() {
+        Iterator<Entry<String, List<User>>> iterator = nameToUsers.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Entry<String, List<User>> next = iterator.next();
+            Iterator<User> iter = next.getValue().iterator();
+            while (iter.hasNext()) {
+                User user = iter.next();
+                if (user.isSetByDomainResolver()) {
+                    iter.remove();
+                }
+            }
+            if (CollectionUtils.isEmpty(next.getValue())) {
+                iterator.remove();
+            } else {
+                Collections.sort(next.getValue());
+            }
+        }
+
+    }
+
+    public User createUser(UserIdentity userIdent, byte[] pwd, UserIdentity domainUserIdent, boolean setByResolver)
+            throws PatternMatcherException {
+        if (userIdentityExist(userIdent, true)) {
+            User userByUserIdentity = getUserByUserIdentity(userIdent);
+            userByUserIdentity.setPassword(pwd);
+            userByUserIdentity.setSetByDomainResolver(setByResolver);
+            return userByUserIdentity;
+        }
+
+        PatternMatcher hostPattern = PatternMatcher
+                .createMysqlPattern(userIdent.getHost(), CaseSensibility.HOST.getCaseSensibility());
+        User user = new User();
+        user.setAnyHost(userIdent.getHost().equals(ANY_HOST));
+        user.setUserIdentity(userIdent);
+        Password password = new Password();
+        password.setPassword(pwd);
+        user.setPassword(password);
+        user.setHostPattern(hostPattern);
+        user.setSetByDomainResolver(setByResolver);
+        if (setByResolver) {
+            Preconditions.checkNotNull(domainUserIdent);
+            user.setDomainUserIdentity(domainUserIdent);
+        }
+        List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            nameToLists = Lists.newArrayList(user);
+            nameToUsers.put(userIdent.getQualifiedUser(), nameToLists);
+        } else {
+            nameToLists.add(user);
+            Collections.sort(nameToLists);
+        }
+        return user;
+    }
+
+    public User getUserByUserIdentity(UserIdentity userIdent) {
+        List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            return null;
+        }
+        Iterator<User> iter = nameToLists.iterator();
+        while (iter.hasNext()) {
+            User user = iter.next();
+            if (user.getUserIdentity().equals(userIdent)) {
+                return user;
+            }
+        }
+        return null;
+    }
+
+    public void removeUser(UserIdentity userIdent) {
+        List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            return;
+        }
+        Iterator<User> iter = nameToLists.iterator();
+        while (iter.hasNext()) {
+            User user = iter.next();
+            if (user.getUserIdentity().equals(userIdent)) {
+                iter.remove();
+            }
+        }
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            nameToUsers.remove(userIdent.getQualifiedUser());
+        } else {
+            Collections.sort(nameToLists);
+        }
+    }
+
+    public Map<String, List<User>> getNameToUsers() {
+        return nameToUsers;

Review Comment:
   Not a good idea to expose field unprotected.



##########
fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java:
##########
@@ -34,7 +34,6 @@ public class ShowRolesStmt extends ShowStmt {
         ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder();
 
         builder.addColumn(new Column("Name", ScalarType.createVarchar(100)));
-        builder.addColumn(new Column("Users", ScalarType.createVarchar(100)));

Review Comment:
   Why removing this?



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/GlobalPrivEntry.java:
##########
@@ -18,144 +18,52 @@
 package org.apache.doris.mysql.privilege;
 
 import org.apache.doris.analysis.UserIdentity;
-import org.apache.doris.common.AnalysisException;
-import org.apache.doris.common.CaseSensibility;
-import org.apache.doris.common.PatternMatcher;
-import org.apache.doris.common.PatternMatcherWrapper;
-import org.apache.doris.common.io.Text;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.io.DataInput;
-import java.io.DataOutput;
 import java.io.IOException;
 
 public class GlobalPrivEntry extends PrivEntry {
     private static final Logger LOG = LogManager.getLogger(GlobalPrivEntry.class);
 
-    private byte[] password;
-    // set domainUserIdent when this a password entry and is set by domain resolver.
-    // so that when user checking password with user@'IP' and match a entry set by the resolver,
-    // it should return this domainUserIdent as "current user". And user can use this user ident to get privileges
-    // further.
-    private UserIdentity domainUserIdent;
+    @Deprecated
+    protected byte[] password;
+    @Deprecated
+    protected UserIdentity domainUserIdent;

Review Comment:
   Is this `GlobalPrivEntry` deprecated?



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+    public static final String ANY_HOST = "%";
+    private static final Logger LOG = LogManager.getLogger(UserManager.class);
+    //    Set<User> users = new HashSet<>();
+    //One name may have multiple User,because host can be different
+    private Map<String, List<User>> nameToUsers = new HashMap<>();

Review Comment:
   ```suggestion
       private Map<String, List<User>> nameToUsers = Maps.newConcurrentMap();
   ```



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivEntry.java:
##########
@@ -82,108 +112,7 @@ protected PrivEntry(PatternMatcher hostPattern, String origHost, PatternMatcher
         }
     }
 
-    public PatternMatcher getHostPattern() {
-        return hostPattern;
-    }
-
-    public String getOrigHost() {
-        return origHost;
-    }
-
-    public boolean isAnyHost() {
-        return isAnyHost;
-    }
-
-    public PatternMatcher getUserPattern() {
-        return userPattern;
-    }
-
-    public String getOrigUser() {
-        return origUser;
-    }
-
-    public boolean isAnyUser() {
-        return isAnyUser;
-    }
-
-    public PrivBitSet getPrivSet() {
-        return privSet;
-    }
-
-    public void setPrivSet(PrivBitSet privSet) {
-        this.privSet = privSet;
-    }
-
-    public boolean isSetByDomainResolver() {
-        return isSetByDomainResolver;
-    }
-
-    public void setSetByDomainResolver(boolean isSetByDomainResolver) {
-        this.isSetByDomainResolver = isSetByDomainResolver;
-    }
-
-    public UserIdentity getUserIdent() {
-        return userIdentity;
-    }
-
-    public boolean match(UserIdentity userIdent, boolean exactMatch) {
-        if (exactMatch) {
-            return origUser.equals(userIdent.getQualifiedUser()) && origHost.equals(userIdent.getHost());
-        } else {
-            return origUser.equals(userIdent.getQualifiedUser()) && hostPattern.match(userIdent.getHost());
-        }
-    }
-
-    public abstract boolean keyMatch(PrivEntry other);
-
-    /*
-     * It's a bit complicated when persisting instance which its class has derived classes.
-     * eg: A (top class) -> B (derived) -> C (derived)
-     *
-     * Write process:
-     * C.write()
-     *      |
-     *      --- write class name
-     *      |
-     *      --- super.write()    -----> B.write()
-     *      |                               |
-     *      --- write C's self members      --- write class name (if not write before)
-     *                                      |
-     *                                      --- super.write()    -----> A.write()
-     *                                      |                               |
-     *                                      --- write B's self members      --- write class name (if not write before)
-     *                                                                      |
-     *                                                                      --- write A's self members
-     *
-     * So the final write order is:
-     *      1. C's class name
-     *      2. A's self members
-     *      3. B's self members
-     *      4. C's self members
-     *
-     * In case that class name should only be wrote once, we use isClassNameWrote flag.
-     *
-     * Read process:
-     * static A.read()
-     *      |
-     *      --- read class name and instantiated the class instance (eg. C class)
-     *      |
-     *      --- C.readFields()
-     *          |
-     *          --- super.readFields() --> B.readFields()
-     *          |                           |
-     *          --- read C's self members   --- super.readFields() --> A.readFields()
-     *                                      |                           |
-     *                                      --- read B's self members   --- read A's self members
-     *
-     *  So the final read order is:
-     *      1. C's class name
-     *      2. A's self members
-     *      3. B's self members
-     *      4. C's self members
-     *
-     *  Which is same as Write order.
-     */
+    @Deprecated
     public static PrivEntry read(DataInput in) throws IOException {

Review Comment:
   I think we can refactor this read method.



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserRoleManager.java:
##########
@@ -0,0 +1,137 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserRoleManager implements Writable {
+    private Map<UserIdentity, Set<String>> userToRoles = Maps.newConcurrentMap();
+    private Map<String, Set<UserIdentity>> roleToUsers = Maps.newConcurrentMap();
+
+    public void addUserRole(UserIdentity userIdentity, String roleName) {
+        Set<String> roles = userToRoles.get(userIdentity);
+        if (CollectionUtils.isEmpty(roles)) {
+            roles = Sets.newHashSet();
+        }
+        roles.add(roleName);
+        userToRoles.put(userIdentity, roles);
+        Set<UserIdentity> userIdentities = roleToUsers.get(roleName);
+        if (CollectionUtils.isEmpty(userIdentities)) {
+            userIdentities = Sets.newHashSet();
+        }
+        userIdentities.add(userIdentity);
+        roleToUsers.put(roleName, userIdentities);
+    }
+
+    public void dropUser(UserIdentity userIdentity) {
+        if (!userToRoles.containsKey(userIdentity)) {
+            return;
+        }
+        Set<String> roles = userToRoles.remove(userIdentity);
+        for (String roleName : roles) {
+            Set<UserIdentity> userIdentities = roleToUsers.get(roleName);
+            if (CollectionUtils.isEmpty(userIdentities)) {
+                continue;
+            }
+            userIdentities.remove(userIdentity);
+            if (CollectionUtils.isEmpty(userIdentities)) {
+                roleToUsers.remove(roleName);
+            }
+        }
+    }
+
+    public void dropRole(String roleName) {
+        if (!roleToUsers.containsKey(roleName)) {
+            return;
+        }
+        Set<UserIdentity> remove = roleToUsers.remove(roleName);
+        for (UserIdentity userIdentity : remove) {
+            Set<String> roles = userToRoles.get(userIdentity);
+            if (CollectionUtils.isEmpty(roles)) {
+                continue;
+            }
+            roles.remove(roleName);
+            if (CollectionUtils.isEmpty(roles)) {
+                userToRoles.remove(userIdentity);
+            }
+        }
+    }
+
+    public Set<String> getRolesByUser(UserIdentity user) {
+        Set<String> roles = userToRoles.get(user);
+        return roles == null ? Collections.EMPTY_SET : roles;
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        out.writeInt(userToRoles.size());

Review Comment:
   Use Gson serde



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserRoleManager.java:
##########
@@ -0,0 +1,137 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserRoleManager implements Writable {
+    private Map<UserIdentity, Set<String>> userToRoles = Maps.newConcurrentMap();

Review Comment:
   Need a lock to make sure these 2 maps are updated atomically



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java:
##########
@@ -72,37 +61,29 @@ public boolean hasPriv(String host, String user, PrivPredicate wanted) {
      * NOTICE, this method does not set password for the newly added entry if this is a user priv table, the caller
      * need to set password later.
      */
-    public PrivEntry addEntry(PrivEntry newEntry, boolean errOnExist, boolean errOnNonExist) throws DdlException {
+    public PrivEntry addEntry(PrivEntry newEntry,
+            boolean errOnExist, boolean errOnNonExist) throws DdlException {
         PrivEntry existingEntry = getExistingEntry(newEntry);
         if (existingEntry == null) {
             if (errOnNonExist) {
-                throw new DdlException("User " + newEntry.getUserIdent() + " does not exist");
+                throw new DdlException("entry does not exist");
             }
             entries.add(newEntry);
             Collections.sort(entries);
             LOG.info("add priv entry: {}", newEntry);
             return newEntry;
         } else {
             if (errOnExist) {
-                throw new DdlException("User already exist");
+                throw new DdlException("entry already exist");
             } else {
-                checkOperationAllowed(existingEntry, newEntry, "ADD ENTRY");
-                // if existing entry is set by domain resolver, just replace it with the new entry.
-                // if existing entry is not set by domain resolver, merge the 2 entries.
-                if (existingEntry.isSetByDomainResolver()) {
-                    existingEntry.setPrivSet(newEntry.getPrivSet());
-                    existingEntry.setSetByDomainResolver(newEntry.isSetByDomainResolver());
-                    LOG.debug("reset priv entry: {}", existingEntry);
-                } else if (!newEntry.isSetByDomainResolver()) {
-                    mergePriv(existingEntry, newEntry);
-                    existingEntry.setSetByDomainResolver(false);
-                    LOG.debug("merge priv entry: {}", existingEntry);
-                }
+                mergePriv(existingEntry, newEntry);
+                LOG.debug("merge priv entry: {}", existingEntry);

Review Comment:
   How to handle `domain` now?



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java:
##########
@@ -0,0 +1,584 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.ResourcePattern;
+import org.apache.doris.analysis.TablePattern;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.FeMetaVersion;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.privilege.Auth.PrivLevel;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+public class Role implements Writable {
+    private static final Logger LOG = LogManager.getLogger(Role.class);
+
+    // operator is responsible for operating cluster, such as add/drop node
+    public static String OPERATOR_ROLE = "operator";
+    // admin is like DBA, who has all privileges except for NODE privilege held by operator
+    public static String ADMIN_ROLE = "admin";
+
+    public static Role OPERATOR = new Role(OPERATOR_ROLE,
+            TablePattern.ALL, PrivBitSet.of(Privilege.NODE_PRIV, Privilege.ADMIN_PRIV),
+            ResourcePattern.ALL, PrivBitSet.of(Privilege.NODE_PRIV, Privilege.ADMIN_PRIV));
+    public static Role ADMIN = new Role(ADMIN_ROLE,
+            TablePattern.ALL, PrivBitSet.of(Privilege.ADMIN_PRIV),
+            ResourcePattern.ALL, PrivBitSet.of(Privilege.ADMIN_PRIV));
+
+    private String roleName;
+    private Map<TablePattern, PrivBitSet> tblPatternToPrivs = Maps.newConcurrentMap();
+    private Map<ResourcePattern, PrivBitSet> resourcePatternToPrivs = Maps.newConcurrentMap();
+
+    private GlobalPrivTable globalPrivTable = new GlobalPrivTable();
+    private CatalogPrivTable catalogPrivTable = new CatalogPrivTable();
+    private DbPrivTable dbPrivTable = new DbPrivTable();
+    private TablePrivTable tablePrivTable = new TablePrivTable();
+    private ResourcePrivTable resourcePrivTable = new ResourcePrivTable();
+
+    @Deprecated
+    private Set<UserIdentity> users = Sets.newConcurrentHashSet();
+
+    @Deprecated
+    public Set<UserIdentity> getUsers() {
+        return users;
+    }
+
+    private Role() {
+
+    }
+
+    public Role(String roleName) {
+        this.roleName = roleName;
+    }
+
+    public Role(String roleName, TablePattern tablePattern, PrivBitSet privs) throws DdlException {
+        this.roleName = roleName;
+        this.tblPatternToPrivs.put(tablePattern, privs);
+        grantPrivs(tablePattern, privs.copy());
+    }
+
+    public Role(String roleName, ResourcePattern resourcePattern, PrivBitSet privs) throws DdlException {
+        this.roleName = roleName;
+        this.resourcePatternToPrivs.put(resourcePattern, privs);
+        grantPrivs(resourcePattern, privs.copy());
+    }
+
+    public Role(String roleName, TablePattern tablePattern, PrivBitSet tablePrivs,
+            ResourcePattern resourcePattern, PrivBitSet resourcePrivs) {
+        this.roleName = roleName;
+        this.tblPatternToPrivs.put(tablePattern, tablePrivs);
+        this.resourcePatternToPrivs.put(resourcePattern, resourcePrivs);
+        //for init admin role,will not generate exception
+        try {
+            grantPrivs(tablePattern, tablePrivs.copy());
+            grantPrivs(resourcePattern, resourcePrivs.copy());
+        } catch (DdlException e) {
+            LOG.warn("grant failed,", e);
+        }
+
+    }
+
+    public String getRoleName() {
+        return roleName;
+    }
+
+    public Map<TablePattern, PrivBitSet> getTblPatternToPrivs() {
+        return tblPatternToPrivs;
+    }
+
+    public Map<ResourcePattern, PrivBitSet> getResourcePatternToPrivs() {
+        return resourcePatternToPrivs;
+    }
+
+    // merge role not check role name.
+    public void mergeNotCheck(Role other) throws DdlException {
+        for (Map.Entry<TablePattern, PrivBitSet> entry : other.getTblPatternToPrivs().entrySet()) {
+            if (tblPatternToPrivs.containsKey(entry.getKey())) {
+                PrivBitSet existPrivs = tblPatternToPrivs.get(entry.getKey());
+                existPrivs.or(entry.getValue());
+            } else {
+                tblPatternToPrivs.put(entry.getKey(), entry.getValue());
+            }
+            grantPrivs(entry.getKey(), entry.getValue().copy());
+        }
+        for (Map.Entry<ResourcePattern, PrivBitSet> entry : other.resourcePatternToPrivs.entrySet()) {
+            if (resourcePatternToPrivs.containsKey(entry.getKey())) {
+                PrivBitSet existPrivs = resourcePatternToPrivs.get(entry.getKey());
+                existPrivs.or(entry.getValue());
+            } else {
+                resourcePatternToPrivs.put(entry.getKey(), entry.getValue());
+            }
+            grantPrivs(entry.getKey(), entry.getValue().copy());
+        }
+    }
+
+    public void merge(Role other) throws DdlException {
+        Preconditions.checkState(roleName.equalsIgnoreCase(other.getRoleName()));
+        mergeNotCheck(other);
+    }
+
+
+    public static Role read(DataInput in) throws IOException {
+        Role role = new Role();
+        try {
+            role.readFields(in);
+        } catch (DdlException e) {
+            LOG.warn("grant failed,", e);
+        }
+        return role;
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        Text.writeString(out, roleName);

Review Comment:
   use Gson serde



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java:
##########
@@ -40,19 +44,25 @@
 import java.util.stream.Stream;
 
 public class RoleManager implements Writable {
-    private Map<String, PaloRole> roles = Maps.newHashMap();
+    private static final Logger LOG = LogManager.getLogger(RoleManager.class);
+    //prefix of each user default role
+    public static String DEFAULT_ROLE_PREFIX = "default_role_rbac_";
+
+    private Map<String, Role> roles = Maps.newHashMap();
 
     public RoleManager() {
-        roles.put(PaloRole.OPERATOR.getRoleName(), PaloRole.OPERATOR);
-        roles.put(PaloRole.ADMIN.getRoleName(), PaloRole.ADMIN);
+        roles.put(
+                Role.OPERATOR.getRoleName(), Role.OPERATOR);
+        roles.put(
+                Role.ADMIN.getRoleName(), Role.ADMIN);
     }
 
-    public PaloRole getRole(String role) {
-        return roles.get(role);
+    public Role getRole(String name) {
+        return roles.get(name);
     }
 
-    public PaloRole addRole(PaloRole newRole, boolean errOnExist) throws DdlException {
-        PaloRole existingRole = roles.get(newRole.getRoleName());
+    public Role addRole(Role newRole, boolean errOnExist) throws DdlException {

Review Comment:
   How about name it as `addOrMergeRole`?



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java:
##########
@@ -40,19 +44,25 @@
 import java.util.stream.Stream;
 
 public class RoleManager implements Writable {
-    private Map<String, PaloRole> roles = Maps.newHashMap();
+    private static final Logger LOG = LogManager.getLogger(RoleManager.class);
+    //prefix of each user default role
+    public static String DEFAULT_ROLE_PREFIX = "default_role_rbac_";
+
+    private Map<String, Role> roles = Maps.newHashMap();

Review Comment:
   ```suggestion
       private Map<String, Role> roles = Maps.concurrentMap();
   ```
   ?



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+    public static final String ANY_HOST = "%";
+    private static final Logger LOG = LogManager.getLogger(UserManager.class);
+    //    Set<User> users = new HashSet<>();

Review Comment:
   Remove unused code



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+    public static final String ANY_HOST = "%";
+    private static final Logger LOG = LogManager.getLogger(UserManager.class);
+    //    Set<User> users = new HashSet<>();
+    //One name may have multiple User,because host can be different
+    private Map<String, List<User>> nameToUsers = new HashMap<>();

Review Comment:
   And add comment to explain the key



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+    public static final String ANY_HOST = "%";
+    private static final Logger LOG = LogManager.getLogger(UserManager.class);
+    //    Set<User> users = new HashSet<>();
+    //One name may have multiple User,because host can be different
+    private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+    public boolean userIdentityExist(UserIdentity userIdentity, boolean includeByDomain) {
+        List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+        if (CollectionUtils.isEmpty(users)) {
+            return false;
+        }
+        for (User user : users) {
+            if (user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+                if (includeByDomain || !user.isSetByDomainResolver()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public List<User> getUserByName(String name) {
+        List<User> users = nameToUsers.get(name);
+        return users == null ? Collections.EMPTY_LIST : users;
+    }
+
+    public void checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        LOG.debug("check password for user: {} from {}, password: {}, random string: {}",
+                remoteUser, remoteHost, remotePasswd, randomString);
+
+        PasswordPolicyManager passwdPolicyMgr = Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+        List<User> users = nameToUsers.get(remoteUser);
+        if (CollectionUtils.isEmpty(users)) {
+            return;
+        }
+
+        for (User user : users) {
+            if (user.getUserIdentity().isDomain()) {
+                continue;
+            }
+            // check host
+            if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost)) {
+                continue;
+            }
+            UserIdentity curUser = user.getDomainUserIdentity();
+            // check password
+            byte[] saltPassword = MysqlPassword.getSaltFromPassword(user.getPassword().getPassword());
+            // when the length of password is zero, the user has no password
+            if ((remotePasswd.length == saltPassword.length)
+                    && (remotePasswd.length == 0
+                    || MysqlPassword.checkScramble(remotePasswd, randomString, saltPassword))) {
+                passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+                // found the matched entry
+                if (currentUser != null) {
+                    currentUser.add(curUser);
+                }
+                return;
+            } else {
+                // case A. this means we already matched a entry by user@host, but password is incorrect.
+                // return false, NOT continue matching other entries.
+                // For example, there are 2 entries in order:
+                // 1. cmy@"192.168.%" identified by '123';
+                // 2. cmy@"%" identified by 'abc';
+                // if user cmy@'192.168.1.1' try to login with password 'abc', it will be denied.
+                passwdPolicyMgr.onFailedLogin(curUser);
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        remotePasswd.length == 0 ? "NO" : "YES");
+            }
+        }
+
+    }
+
+    public void checkPlainPassword(String remoteUser, String remoteHost, String remotePasswd,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        PasswordPolicyManager passwdPolicyMgr = Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+        List<User> users = nameToUsers.get(remoteUser);
+        if (CollectionUtils.isEmpty(users)) {
+            throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                    "YES");
+        }
+        for (User user : users) {
+            if (user.getUserIdentity().isDomain()) {
+                continue;
+            }
+            // check host
+            if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost)) {
+                continue;
+            }
+            UserIdentity curUser = user.getDomainUserIdentity();
+            if (MysqlPassword.checkPlainPass(user.getPassword().getPassword(), remotePasswd)) {
+                passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+                if (currentUser != null) {
+                    currentUser.add(curUser);
+                }
+                return;
+            } else {
+                // set case A. in checkPassword()
+                passwdPolicyMgr.onFailedLogin(curUser);
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        "YES");
+            }
+        }
+        throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                "YES");
+    }
+
+    public void clearEntriesSetByResolver() {
+        Iterator<Entry<String, List<User>>> iterator = nameToUsers.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Entry<String, List<User>> next = iterator.next();
+            Iterator<User> iter = next.getValue().iterator();
+            while (iter.hasNext()) {
+                User user = iter.next();
+                if (user.isSetByDomainResolver()) {
+                    iter.remove();
+                }
+            }
+            if (CollectionUtils.isEmpty(next.getValue())) {
+                iterator.remove();
+            } else {
+                Collections.sort(next.getValue());
+            }
+        }
+
+    }
+
+    public User createUser(UserIdentity userIdent, byte[] pwd, UserIdentity domainUserIdent, boolean setByResolver)
+            throws PatternMatcherException {
+        if (userIdentityExist(userIdent, true)) {
+            User userByUserIdentity = getUserByUserIdentity(userIdent);
+            userByUserIdentity.setPassword(pwd);
+            userByUserIdentity.setSetByDomainResolver(setByResolver);
+            return userByUserIdentity;
+        }
+
+        PatternMatcher hostPattern = PatternMatcher
+                .createMysqlPattern(userIdent.getHost(), CaseSensibility.HOST.getCaseSensibility());
+        User user = new User();
+        user.setAnyHost(userIdent.getHost().equals(ANY_HOST));
+        user.setUserIdentity(userIdent);
+        Password password = new Password();
+        password.setPassword(pwd);
+        user.setPassword(password);
+        user.setHostPattern(hostPattern);
+        user.setSetByDomainResolver(setByResolver);
+        if (setByResolver) {
+            Preconditions.checkNotNull(domainUserIdent);
+            user.setDomainUserIdentity(domainUserIdent);
+        }
+        List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            nameToLists = Lists.newArrayList(user);
+            nameToUsers.put(userIdent.getQualifiedUser(), nameToLists);
+        } else {
+            nameToLists.add(user);
+            Collections.sort(nameToLists);
+        }
+        return user;
+    }
+
+    public User getUserByUserIdentity(UserIdentity userIdent) {
+        List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            return null;
+        }
+        Iterator<User> iter = nameToLists.iterator();
+        while (iter.hasNext()) {
+            User user = iter.next();
+            if (user.getUserIdentity().equals(userIdent)) {
+                return user;
+            }
+        }
+        return null;
+    }
+
+    public void removeUser(UserIdentity userIdent) {
+        List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            return;
+        }
+        Iterator<User> iter = nameToLists.iterator();
+        while (iter.hasNext()) {
+            User user = iter.next();
+            if (user.getUserIdentity().equals(userIdent)) {
+                iter.remove();
+            }
+        }
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            nameToUsers.remove(userIdent.getQualifiedUser());
+        } else {
+            Collections.sort(nameToLists);
+        }
+    }
+
+    public Map<String, List<User>> getNameToUsers() {
+        return nameToUsers;
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        out.writeInt(nameToUsers.size());

Review Comment:
   And better to move read/write method at the end of the class. it is a tradition.



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+    public static final String ANY_HOST = "%";
+    private static final Logger LOG = LogManager.getLogger(UserManager.class);
+    //    Set<User> users = new HashSet<>();
+    //One name may have multiple User,because host can be different
+    private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+    public boolean userIdentityExist(UserIdentity userIdentity, boolean includeByDomain) {
+        List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+        if (CollectionUtils.isEmpty(users)) {
+            return false;
+        }
+        for (User user : users) {
+            if (user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+                if (includeByDomain || !user.isSetByDomainResolver()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public List<User> getUserByName(String name) {
+        List<User> users = nameToUsers.get(name);
+        return users == null ? Collections.EMPTY_LIST : users;
+    }
+
+    public void checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        LOG.debug("check password for user: {} from {}, password: {}, random string: {}",
+                remoteUser, remoteHost, remotePasswd, randomString);
+
+        PasswordPolicyManager passwdPolicyMgr = Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+        List<User> users = nameToUsers.get(remoteUser);
+        if (CollectionUtils.isEmpty(users)) {
+            return;
+        }
+
+        for (User user : users) {
+            if (user.getUserIdentity().isDomain()) {
+                continue;
+            }
+            // check host
+            if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost)) {
+                continue;
+            }
+            UserIdentity curUser = user.getDomainUserIdentity();
+            // check password
+            byte[] saltPassword = MysqlPassword.getSaltFromPassword(user.getPassword().getPassword());
+            // when the length of password is zero, the user has no password
+            if ((remotePasswd.length == saltPassword.length)
+                    && (remotePasswd.length == 0
+                    || MysqlPassword.checkScramble(remotePasswd, randomString, saltPassword))) {
+                passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+                // found the matched entry
+                if (currentUser != null) {
+                    currentUser.add(curUser);
+                }
+                return;
+            } else {
+                // case A. this means we already matched a entry by user@host, but password is incorrect.
+                // return false, NOT continue matching other entries.
+                // For example, there are 2 entries in order:
+                // 1. cmy@"192.168.%" identified by '123';
+                // 2. cmy@"%" identified by 'abc';
+                // if user cmy@'192.168.1.1' try to login with password 'abc', it will be denied.
+                passwdPolicyMgr.onFailedLogin(curUser);
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        remotePasswd.length == 0 ? "NO" : "YES");
+            }
+        }
+
+    }
+
+    public void checkPlainPassword(String remoteUser, String remoteHost, String remotePasswd,

Review Comment:
   The logic of `checkPassword()` and `checkPlainPassword()` are almost same, better merge them into one method.



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+    public static final String ANY_HOST = "%";
+    private static final Logger LOG = LogManager.getLogger(UserManager.class);
+    //    Set<User> users = new HashSet<>();
+    //One name may have multiple User,because host can be different
+    private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+    public boolean userIdentityExist(UserIdentity userIdentity, boolean includeByDomain) {
+        List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+        if (CollectionUtils.isEmpty(users)) {
+            return false;
+        }
+        for (User user : users) {
+            if (user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+                if (includeByDomain || !user.isSetByDomainResolver()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public List<User> getUserByName(String name) {
+        List<User> users = nameToUsers.get(name);
+        return users == null ? Collections.EMPTY_LIST : users;
+    }
+
+    public void checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        LOG.debug("check password for user: {} from {}, password: {}, random string: {}",
+                remoteUser, remoteHost, remotePasswd, randomString);
+
+        PasswordPolicyManager passwdPolicyMgr = Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+        List<User> users = nameToUsers.get(remoteUser);
+        if (CollectionUtils.isEmpty(users)) {
+            return;

Review Comment:
   should throw exception?



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+    public static final String ANY_HOST = "%";
+    private static final Logger LOG = LogManager.getLogger(UserManager.class);
+    //    Set<User> users = new HashSet<>();
+    //One name may have multiple User,because host can be different
+    private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+    public boolean userIdentityExist(UserIdentity userIdentity, boolean includeByDomain) {
+        List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+        if (CollectionUtils.isEmpty(users)) {
+            return false;
+        }
+        for (User user : users) {
+            if (user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+                if (includeByDomain || !user.isSetByDomainResolver()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public List<User> getUserByName(String name) {
+        List<User> users = nameToUsers.get(name);
+        return users == null ? Collections.EMPTY_LIST : users;
+    }
+
+    public void checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        LOG.debug("check password for user: {} from {}, password: {}, random string: {}",
+                remoteUser, remoteHost, remotePasswd, randomString);
+
+        PasswordPolicyManager passwdPolicyMgr = Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+        List<User> users = nameToUsers.get(remoteUser);
+        if (CollectionUtils.isEmpty(users)) {
+            return;
+        }
+
+        for (User user : users) {
+            if (user.getUserIdentity().isDomain()) {
+                continue;
+            }
+            // check host
+            if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost)) {
+                continue;
+            }
+            UserIdentity curUser = user.getDomainUserIdentity();
+            // check password
+            byte[] saltPassword = MysqlPassword.getSaltFromPassword(user.getPassword().getPassword());
+            // when the length of password is zero, the user has no password
+            if ((remotePasswd.length == saltPassword.length)
+                    && (remotePasswd.length == 0
+                    || MysqlPassword.checkScramble(remotePasswd, randomString, saltPassword))) {
+                passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+                // found the matched entry
+                if (currentUser != null) {
+                    currentUser.add(curUser);
+                }
+                return;
+            } else {
+                // case A. this means we already matched a entry by user@host, but password is incorrect.
+                // return false, NOT continue matching other entries.
+                // For example, there are 2 entries in order:
+                // 1. cmy@"192.168.%" identified by '123';
+                // 2. cmy@"%" identified by 'abc';
+                // if user cmy@'192.168.1.1' try to login with password 'abc', it will be denied.
+                passwdPolicyMgr.onFailedLogin(curUser);
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        remotePasswd.length == 0 ? "NO" : "YES");
+            }
+        }
+
+    }
+
+    public void checkPlainPassword(String remoteUser, String remoteHost, String remotePasswd,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        PasswordPolicyManager passwdPolicyMgr = Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+        List<User> users = nameToUsers.get(remoteUser);
+        if (CollectionUtils.isEmpty(users)) {
+            throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                    "YES");
+        }
+        for (User user : users) {
+            if (user.getUserIdentity().isDomain()) {
+                continue;
+            }
+            // check host
+            if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost)) {
+                continue;
+            }
+            UserIdentity curUser = user.getDomainUserIdentity();
+            if (MysqlPassword.checkPlainPass(user.getPassword().getPassword(), remotePasswd)) {
+                passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+                if (currentUser != null) {
+                    currentUser.add(curUser);
+                }
+                return;
+            } else {
+                // set case A. in checkPassword()
+                passwdPolicyMgr.onFailedLogin(curUser);
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        "YES");
+            }
+        }
+        throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                "YES");
+    }
+
+    public void clearEntriesSetByResolver() {
+        Iterator<Entry<String, List<User>>> iterator = nameToUsers.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Entry<String, List<User>> next = iterator.next();
+            Iterator<User> iter = next.getValue().iterator();
+            while (iter.hasNext()) {
+                User user = iter.next();
+                if (user.isSetByDomainResolver()) {
+                    iter.remove();
+                }
+            }
+            if (CollectionUtils.isEmpty(next.getValue())) {
+                iterator.remove();
+            } else {
+                Collections.sort(next.getValue());
+            }
+        }
+
+    }
+
+    public User createUser(UserIdentity userIdent, byte[] pwd, UserIdentity domainUserIdent, boolean setByResolver)
+            throws PatternMatcherException {
+        if (userIdentityExist(userIdent, true)) {
+            User userByUserIdentity = getUserByUserIdentity(userIdent);
+            userByUserIdentity.setPassword(pwd);
+            userByUserIdentity.setSetByDomainResolver(setByResolver);
+            return userByUserIdentity;
+        }
+
+        PatternMatcher hostPattern = PatternMatcher
+                .createMysqlPattern(userIdent.getHost(), CaseSensibility.HOST.getCaseSensibility());
+        User user = new User();
+        user.setAnyHost(userIdent.getHost().equals(ANY_HOST));
+        user.setUserIdentity(userIdent);
+        Password password = new Password();
+        password.setPassword(pwd);
+        user.setPassword(password);
+        user.setHostPattern(hostPattern);
+        user.setSetByDomainResolver(setByResolver);
+        if (setByResolver) {
+            Preconditions.checkNotNull(domainUserIdent);
+            user.setDomainUserIdentity(domainUserIdent);
+        }
+        List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            nameToLists = Lists.newArrayList(user);
+            nameToUsers.put(userIdent.getQualifiedUser(), nameToLists);
+        } else {
+            nameToLists.add(user);
+            Collections.sort(nameToLists);
+        }
+        return user;
+    }
+
+    public User getUserByUserIdentity(UserIdentity userIdent) {
+        List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            return null;
+        }
+        Iterator<User> iter = nameToLists.iterator();
+        while (iter.hasNext()) {
+            User user = iter.next();
+            if (user.getUserIdentity().equals(userIdent)) {
+                return user;
+            }
+        }
+        return null;
+    }
+
+    public void removeUser(UserIdentity userIdent) {
+        List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            return;
+        }
+        Iterator<User> iter = nameToLists.iterator();
+        while (iter.hasNext()) {
+            User user = iter.next();
+            if (user.getUserIdentity().equals(userIdent)) {
+                iter.remove();
+            }
+        }
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            nameToUsers.remove(userIdent.getQualifiedUser());
+        } else {
+            Collections.sort(nameToLists);
+        }
+    }
+
+    public Map<String, List<User>> getNameToUsers() {
+        return nameToUsers;
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        out.writeInt(nameToUsers.size());

Review Comment:
   Use Gson serde



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+    public static final String ANY_HOST = "%";
+    private static final Logger LOG = LogManager.getLogger(UserManager.class);
+    //    Set<User> users = new HashSet<>();
+    //One name may have multiple User,because host can be different
+    private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+    public boolean userIdentityExist(UserIdentity userIdentity, boolean includeByDomain) {
+        List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+        if (CollectionUtils.isEmpty(users)) {
+            return false;
+        }
+        for (User user : users) {
+            if (user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+                if (includeByDomain || !user.isSetByDomainResolver()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public List<User> getUserByName(String name) {
+        List<User> users = nameToUsers.get(name);
+        return users == null ? Collections.EMPTY_LIST : users;
+    }
+
+    public void checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        LOG.debug("check password for user: {} from {}, password: {}, random string: {}",
+                remoteUser, remoteHost, remotePasswd, randomString);
+
+        PasswordPolicyManager passwdPolicyMgr = Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+        List<User> users = nameToUsers.get(remoteUser);
+        if (CollectionUtils.isEmpty(users)) {
+            return;
+        }
+
+        for (User user : users) {
+            if (user.getUserIdentity().isDomain()) {
+                continue;
+            }
+            // check host
+            if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost)) {
+                continue;
+            }
+            UserIdentity curUser = user.getDomainUserIdentity();
+            // check password
+            byte[] saltPassword = MysqlPassword.getSaltFromPassword(user.getPassword().getPassword());
+            // when the length of password is zero, the user has no password
+            if ((remotePasswd.length == saltPassword.length)
+                    && (remotePasswd.length == 0
+                    || MysqlPassword.checkScramble(remotePasswd, randomString, saltPassword))) {
+                passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+                // found the matched entry
+                if (currentUser != null) {
+                    currentUser.add(curUser);
+                }
+                return;
+            } else {
+                // case A. this means we already matched a entry by user@host, but password is incorrect.
+                // return false, NOT continue matching other entries.
+                // For example, there are 2 entries in order:
+                // 1. cmy@"192.168.%" identified by '123';
+                // 2. cmy@"%" identified by 'abc';
+                // if user cmy@'192.168.1.1' try to login with password 'abc', it will be denied.
+                passwdPolicyMgr.onFailedLogin(curUser);
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        remotePasswd.length == 0 ? "NO" : "YES");
+            }
+        }
+
+    }
+
+    public void checkPlainPassword(String remoteUser, String remoteHost, String remotePasswd,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        PasswordPolicyManager passwdPolicyMgr = Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+        List<User> users = nameToUsers.get(remoteUser);
+        if (CollectionUtils.isEmpty(users)) {
+            throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                    "YES");
+        }
+        for (User user : users) {
+            if (user.getUserIdentity().isDomain()) {
+                continue;
+            }
+            // check host
+            if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost)) {
+                continue;
+            }
+            UserIdentity curUser = user.getDomainUserIdentity();
+            if (MysqlPassword.checkPlainPass(user.getPassword().getPassword(), remotePasswd)) {
+                passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+                if (currentUser != null) {
+                    currentUser.add(curUser);
+                }
+                return;
+            } else {
+                // set case A. in checkPassword()
+                passwdPolicyMgr.onFailedLogin(curUser);
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        "YES");
+            }
+        }
+        throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                "YES");
+    }
+
+    public void clearEntriesSetByResolver() {
+        Iterator<Entry<String, List<User>>> iterator = nameToUsers.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Entry<String, List<User>> next = iterator.next();
+            Iterator<User> iter = next.getValue().iterator();
+            while (iter.hasNext()) {
+                User user = iter.next();
+                if (user.isSetByDomainResolver()) {
+                    iter.remove();
+                }
+            }
+            if (CollectionUtils.isEmpty(next.getValue())) {
+                iterator.remove();
+            } else {
+                Collections.sort(next.getValue());
+            }
+        }
+
+    }
+
+    public User createUser(UserIdentity userIdent, byte[] pwd, UserIdentity domainUserIdent, boolean setByResolver)
+            throws PatternMatcherException {
+        if (userIdentityExist(userIdent, true)) {
+            User userByUserIdentity = getUserByUserIdentity(userIdent);
+            userByUserIdentity.setPassword(pwd);
+            userByUserIdentity.setSetByDomainResolver(setByResolver);
+            return userByUserIdentity;
+        }
+
+        PatternMatcher hostPattern = PatternMatcher
+                .createMysqlPattern(userIdent.getHost(), CaseSensibility.HOST.getCaseSensibility());
+        User user = new User();
+        user.setAnyHost(userIdent.getHost().equals(ANY_HOST));

Review Comment:
   How about create a constructor to do these `set` method?



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserProperty.java:
##########
@@ -604,7 +579,9 @@ public void readFields(DataInput in) throws IOException {
         }
 
         // whiteList
-        whiteList.readFields(in);
+        if (Env.getCurrentEnvJournalVersion() < FeMetaVersion.VERSION_116) {
+            whiteList.readFields(in);
+        }

Review Comment:
   else {
       whiteList = new WhiteList();
   }



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java:
##########
@@ -236,9 +170,9 @@ public static PrivTable read(DataInput in) throws IOException {
         try {
             Class<? extends PrivTable> derivedClass = (Class<? extends PrivTable>) Class.forName(className);
             privTable = derivedClass.newInstance();
-            Class[] paramTypes = { DataInput.class };
+            Class[] paramTypes = {DataInput.class};

Review Comment:
   This read method is also deprecated



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr closed pull request #16091: [wip](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr closed pull request #16091: [wip](auth)Implementing privilege management with rbac model
URL: https://github.com/apache/doris/pull/16091


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1098304753


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java:
##########
@@ -72,37 +61,29 @@ public boolean hasPriv(String host, String user, PrivPredicate wanted) {
      * NOTICE, this method does not set password for the newly added entry if this is a user priv table, the caller
      * need to set password later.
      */
-    public PrivEntry addEntry(PrivEntry newEntry, boolean errOnExist, boolean errOnNonExist) throws DdlException {
+    public PrivEntry addEntry(PrivEntry newEntry,
+            boolean errOnExist, boolean errOnNonExist) throws DdlException {
         PrivEntry existingEntry = getExistingEntry(newEntry);
         if (existingEntry == null) {
             if (errOnNonExist) {
-                throw new DdlException("User " + newEntry.getUserIdent() + " does not exist");
+                throw new DdlException("entry does not exist");
             }
             entries.add(newEntry);
             Collections.sort(entries);
             LOG.info("add priv entry: {}", newEntry);
             return newEntry;
         } else {
             if (errOnExist) {
-                throw new DdlException("User already exist");
+                throw new DdlException("entry already exist");
             } else {
-                checkOperationAllowed(existingEntry, newEntry, "ADD ENTRY");
-                // if existing entry is set by domain resolver, just replace it with the new entry.
-                // if existing entry is not set by domain resolver, merge the 2 entries.
-                if (existingEntry.isSetByDomainResolver()) {
-                    existingEntry.setPrivSet(newEntry.getPrivSet());
-                    existingEntry.setSetByDomainResolver(newEntry.isSetByDomainResolver());
-                    LOG.debug("reset priv entry: {}", existingEntry);
-                } else if (!newEntry.isSetByDomainResolver()) {
-                    mergePriv(existingEntry, newEntry);
-                    existingEntry.setSetByDomainResolver(false);
-                    LOG.debug("merge priv entry: {}", existingEntry);
-                }
+                mergePriv(existingEntry, newEntry);
+                LOG.debug("merge priv entry: {}", existingEntry);

Review Comment:
   "checkOperationAllowed" ===>now,PrivTable only related with 'role',do not exist this logic



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1098129744


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java:
##########
@@ -40,19 +44,25 @@
 import java.util.stream.Stream;
 
 public class RoleManager implements Writable {
-    private Map<String, PaloRole> roles = Maps.newHashMap();
+    private static final Logger LOG = LogManager.getLogger(RoleManager.class);
+    //prefix of each user default role
+    public static String DEFAULT_ROLE_PREFIX = "default_role_rbac_";
+
+    private Map<String, Role> roles = Maps.newHashMap();

Review Comment:
   Concurrency control is delegated by Auth, so not concurrentMap



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] morningman merged pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "morningman (via GitHub)" <gi...@apache.org>.
morningman merged PR #16091:
URL: https://github.com/apache/doris/pull/16091


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1098308500


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java:
##########
@@ -72,37 +61,29 @@ public boolean hasPriv(String host, String user, PrivPredicate wanted) {
      * NOTICE, this method does not set password for the newly added entry if this is a user priv table, the caller
      * need to set password later.
      */
-    public PrivEntry addEntry(PrivEntry newEntry, boolean errOnExist, boolean errOnNonExist) throws DdlException {
+    public PrivEntry addEntry(PrivEntry newEntry,
+            boolean errOnExist, boolean errOnNonExist) throws DdlException {
         PrivEntry existingEntry = getExistingEntry(newEntry);
         if (existingEntry == null) {
             if (errOnNonExist) {
-                throw new DdlException("User " + newEntry.getUserIdent() + " does not exist");
+                throw new DdlException("entry does not exist");
             }
             entries.add(newEntry);
             Collections.sort(entries);
             LOG.info("add priv entry: {}", newEntry);
             return newEntry;
         } else {
             if (errOnExist) {
-                throw new DdlException("User already exist");
+                throw new DdlException("entry already exist");
             } else {
-                checkOperationAllowed(existingEntry, newEntry, "ADD ENTRY");
-                // if existing entry is set by domain resolver, just replace it with the new entry.
-                // if existing entry is not set by domain resolver, merge the 2 entries.
-                if (existingEntry.isSetByDomainResolver()) {
-                    existingEntry.setPrivSet(newEntry.getPrivSet());
-                    existingEntry.setSetByDomainResolver(newEntry.isSetByDomainResolver());
-                    LOG.debug("reset priv entry: {}", existingEntry);
-                } else if (!newEntry.isSetByDomainResolver()) {
-                    mergePriv(existingEntry, newEntry);
-                    existingEntry.setSetByDomainResolver(false);
-                    LOG.debug("merge priv entry: {}", existingEntry);
-                }
+                mergePriv(existingEntry, newEntry);
+                LOG.debug("merge priv entry: {}", existingEntry);

Review Comment:
   How to handle domain now?
   =====================>
   now,domainUser,ipUser,and resolveUserByDomain all manage By userManager,The logic of scheduled tasks is the same as before
   
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] morningman commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "morningman (via GitHub)" <gi...@apache.org>.
morningman commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1098886413


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java:
##########
@@ -0,0 +1,1644 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.AlterUserStmt;
+import org.apache.doris.analysis.AlterUserStmt.OpType;
+import org.apache.doris.analysis.CreateRoleStmt;
+import org.apache.doris.analysis.CreateUserStmt;
+import org.apache.doris.analysis.DropRoleStmt;
+import org.apache.doris.analysis.DropUserStmt;
+import org.apache.doris.analysis.GrantStmt;
+import org.apache.doris.analysis.PasswordOptions;
+import org.apache.doris.analysis.ResourcePattern;
+import org.apache.doris.analysis.RevokeStmt;
+import org.apache.doris.analysis.SetLdapPassVar;
+import org.apache.doris.analysis.SetPassVar;
+import org.apache.doris.analysis.SetUserPropertyStmt;
+import org.apache.doris.analysis.TableName;
+import org.apache.doris.analysis.TablePattern;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.AuthorizationInfo;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.InfoSchemaDb;
+import org.apache.doris.cluster.ClusterNamespace;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.common.FeConstants;
+import org.apache.doris.common.FeMetaVersion;
+import org.apache.doris.common.LdapConfig;
+import org.apache.doris.common.Pair;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.UserException;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.datasource.InternalCatalog;
+import org.apache.doris.ldap.LdapManager;
+import org.apache.doris.ldap.LdapPrivsChecker;
+import org.apache.doris.load.DppConfig;
+import org.apache.doris.persist.AlterUserOperationLog;
+import org.apache.doris.persist.LdapInfo;
+import org.apache.doris.persist.PrivInfo;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.resource.Tag;
+import org.apache.doris.system.SystemInfoService;
+import org.apache.doris.thrift.TFetchResourceResult;
+import org.apache.doris.thrift.TPrivilegeStatus;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.stream.Collectors;
+
+public class Auth implements Writable {
+    private static final Logger LOG = LogManager.getLogger(Auth.class);
+
+    // root user's role is operator.
+    // each Palo system has only one root user.
+    public static final String ROOT_USER = "root";
+    public static final String ADMIN_USER = "admin";
+    // unknown user does not have any privilege, this is just to be compatible with old version.
+    public static final String UNKNOWN_USER = "unknown";
+    public static final String DEFAULT_CATALOG = InternalCatalog.INTERNAL_CATALOG_NAME;
+
+    //There is no concurrency control logic inside roleManager,userManager,userRoleManage and rpropertyMgr,
+    // and it is completely managed by Auth.
+    // Therefore, their methods cannot be directly called outside, and should be called indirectly through Auth.
+    private RoleManager roleManager = new RoleManager();
+    private UserManager userManager = new UserManager();
+    private UserRoleManager userRoleManager = new UserRoleManager();
+    private UserPropertyMgr propertyMgr = new UserPropertyMgr();
+
+
+    private LdapInfo ldapInfo = new LdapInfo();
+
+    private LdapManager ldapManager = new LdapManager();
+
+    private PasswordPolicyManager passwdPolicyManager = new PasswordPolicyManager();
+
+    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+
+    private void readLock() {
+        lock.readLock().lock();
+    }
+
+    private void readUnlock() {
+        lock.readLock().unlock();
+    }
+
+    private void writeLock() {
+        lock.writeLock().lock();
+    }
+
+    private void writeUnlock() {
+        lock.writeLock().unlock();
+    }
+
+    public enum PrivLevel {
+        GLOBAL, CATALOG, DATABASE, TABLE, RESOURCE
+    }
+
+    public Auth() {
+        initUser();
+    }
+
+    public LdapInfo getLdapInfo() {
+        return ldapInfo;
+    }
+
+    public void setLdapInfo(LdapInfo ldapInfo) {
+        this.ldapInfo = ldapInfo;
+    }
+
+    public LdapManager getLdapManager() {
+        return ldapManager;
+    }
+
+    public PasswordPolicyManager getPasswdPolicyManager() {
+        return passwdPolicyManager;
+    }
+
+    public boolean doesRoleExist(String qualifiedRole) {
+        return roleManager.getRole(qualifiedRole) != null;
+    }
+
+    public void mergeRolesNoCheckName(List<String> roles, Role savedRole) throws DdlException {
+        readLock();
+        try {
+            for (String roleName : roles) {
+                if (doesRoleExist(roleName)) {
+                    Role role = roleManager.getRole(roleName);
+                    savedRole.mergeNotCheck(role);
+                }
+            }
+        } finally {
+            readUnlock();
+        }
+    }
+
+    /*
+     * check password, if matched, save the userIdentity in matched entry.
+     * the following auth checking should use userIdentity saved in currentUser.
+     */
+    public void checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        if ((remoteUser.equals(ROOT_USER) || remoteUser.equals(ADMIN_USER)) && remoteHost.equals("127.0.0.1")) {
+            // root and admin user is allowed to login from 127.0.0.1, in case user forget password.
+            if (remoteUser.equals(ROOT_USER)) {
+                currentUser.add(UserIdentity.ROOT);
+            } else {
+                currentUser.add(UserIdentity.ADMIN);
+            }
+            return;
+        }
+        readLock();
+        try {
+            userManager.checkPassword(remoteUser, remoteHost, remotePasswd, randomString, currentUser);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    // For unit test only, wrapper of "void checkPlainPassword"
+    public boolean checkPlainPasswordForTest(String remoteUser, String remoteHost, String remotePasswd,
+            List<UserIdentity> currentUser) {
+        try {
+            checkPlainPassword(remoteUser, remoteHost, remotePasswd, currentUser);
+            return true;
+        } catch (AuthenticationException e) {
+            return false;
+        }
+    }
+
+    public void checkPlainPassword(String remoteUser, String remoteHost, String remotePasswd,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        // Check the LDAP password when the user exists in the LDAP service.
+        if (ldapManager.doesUserExist(remoteUser)) {
+            if (!ldapManager.checkUserPasswd(remoteUser, remotePasswd, remoteHost, currentUser)) {
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        Strings.isNullOrEmpty(remotePasswd) ? "NO" : "YES");
+            }
+            return;
+        }
+        readLock();
+        try {
+            userManager.checkPlainPassword(remoteUser, remoteHost, remotePasswd, currentUser);
+        } finally {
+            readUnlock();
+        }
+    }
+
+    public boolean checkGlobalPriv(ConnectContext ctx, PrivPredicate wanted) {
+        return checkGlobalPriv(ctx.getCurrentUserIdentity(), wanted);
+    }
+
+    public boolean checkGlobalPriv(UserIdentity currentUser, PrivPredicate wanted) {
+        if (isLdapAuthEnabled() && LdapPrivsChecker.hasGlobalPrivFromLdap(currentUser, wanted)) {
+            return true;
+        }
+        readLock();
+        try {
+            Set<String> roles = userRoleManager.getRolesByUser(currentUser);
+            for (String roleName : roles) {
+                if (roleManager.getRole(roleName).checkGlobalPriv(wanted)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            readUnlock();
+        }
+
+    }
+
+    public boolean checkCtlPriv(ConnectContext ctx, String ctl, PrivPredicate wanted) {
+        return checkCtlPriv(ctx.getCurrentUserIdentity(), ctl, wanted);
+    }
+
+    public boolean checkCtlPriv(UserIdentity currentUser, String ctl, PrivPredicate wanted) {
+        if (wanted.getPrivs().containsNodePriv()) {
+            LOG.debug("should not check NODE priv in catalog level. user: {}, catalog: {}",
+                    currentUser, ctl);
+            return false;
+        }
+        //ldap(before change to rbac)
+        readLock();
+        try {
+            Set<String> roles = userRoleManager.getRolesByUser(currentUser);
+            for (String roleName : roles) {
+                if (roleManager.getRole(roleName).checkCtlPriv(ctl, wanted)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            readUnlock();
+        }
+
+    }
+
+    public boolean checkDbPriv(ConnectContext ctx, String qualifiedDb, PrivPredicate wanted) {
+        return checkDbPriv(ctx.getCurrentUserIdentity(), qualifiedDb, wanted);
+    }
+
+    public boolean checkDbPriv(UserIdentity currentUser, String db, PrivPredicate wanted) {
+        return checkDbPriv(currentUser, DEFAULT_CATALOG, db, wanted);
+    }
+
+    public boolean checkDbPriv(ConnectContext ctx, String ctl, String db, PrivPredicate wanted) {
+        return checkDbPriv(ctx.getCurrentUserIdentity(), ctl, db, wanted);
+    }
+
+    /*
+     * Check if 'user'@'host' on 'db' has 'wanted' priv.
+     * If the given db is null, which means it will no check if database name is matched.
+     */
+    public boolean checkDbPriv(UserIdentity currentUser, String ctl, String db, PrivPredicate wanted) {
+        if (isLdapAuthEnabled() && (LdapPrivsChecker.hasDbPrivFromLdap(currentUser, wanted) || LdapPrivsChecker
+                .hasPrivsOfDb(currentUser, db))) {
+            return true;
+        }
+        if (wanted.getPrivs().containsNodePriv()) {
+            LOG.debug("should not check NODE priv in Database level. user: {}, db: {}",
+                    currentUser, db);
+            return false;
+        }
+        readLock();
+        try {
+            Set<String> roles = userRoleManager.getRolesByUser(currentUser);
+            for (String roleName : roles) {
+                if (roleManager.getRole(roleName).checkDbPriv(ctl, db, wanted)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            readUnlock();
+        }
+
+    }
+
+    public boolean checkTblPriv(ConnectContext ctx, String qualifiedCtl,
+            String qualifiedDb, String tbl, PrivPredicate wanted) {
+        return checkTblPriv(ctx.getCurrentUserIdentity(), qualifiedCtl, qualifiedDb, tbl, wanted);
+    }
+
+    public boolean checkTblPriv(ConnectContext ctx, String qualifiedDb, String tbl, PrivPredicate wanted) {
+        return checkTblPriv(ctx, DEFAULT_CATALOG, qualifiedDb, tbl, wanted);
+    }
+
+    public boolean checkTblPriv(ConnectContext ctx, TableName tableName, PrivPredicate wanted) {
+        Preconditions.checkState(tableName.isFullyQualified());
+        return checkTblPriv(ctx, tableName.getDb(), tableName.getTbl(), wanted);

Review Comment:
   ```suggestion
           return checkTblPriv(ctx, tableName.getCtl(), tableName.getDb(), tableName.getTbl(), wanted);
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1098155028


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+    public static final String ANY_HOST = "%";
+    private static final Logger LOG = LogManager.getLogger(UserManager.class);
+    //    Set<User> users = new HashSet<>();
+    //One name may have multiple User,because host can be different
+    private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+    public boolean userIdentityExist(UserIdentity userIdentity, boolean includeByDomain) {
+        List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+        if (CollectionUtils.isEmpty(users)) {
+            return false;
+        }
+        for (User user : users) {
+            if (user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+                if (includeByDomain || !user.isSetByDomainResolver()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public List<User> getUserByName(String name) {
+        List<User> users = nameToUsers.get(name);
+        return users == null ? Collections.EMPTY_LIST : users;
+    }
+
+    public void checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        LOG.debug("check password for user: {} from {}, password: {}, random string: {}",
+                remoteUser, remoteHost, remotePasswd, randomString);
+
+        PasswordPolicyManager passwdPolicyMgr = Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+        List<User> users = nameToUsers.get(remoteUser);
+        if (CollectionUtils.isEmpty(users)) {
+            return;
+        }
+
+        for (User user : users) {
+            if (user.getUserIdentity().isDomain()) {
+                continue;
+            }
+            // check host
+            if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost)) {
+                continue;
+            }
+            UserIdentity curUser = user.getDomainUserIdentity();
+            // check password
+            byte[] saltPassword = MysqlPassword.getSaltFromPassword(user.getPassword().getPassword());
+            // when the length of password is zero, the user has no password
+            if ((remotePasswd.length == saltPassword.length)
+                    && (remotePasswd.length == 0
+                    || MysqlPassword.checkScramble(remotePasswd, randomString, saltPassword))) {
+                passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+                // found the matched entry
+                if (currentUser != null) {
+                    currentUser.add(curUser);
+                }
+                return;
+            } else {
+                // case A. this means we already matched a entry by user@host, but password is incorrect.
+                // return false, NOT continue matching other entries.
+                // For example, there are 2 entries in order:
+                // 1. cmy@"192.168.%" identified by '123';
+                // 2. cmy@"%" identified by 'abc';
+                // if user cmy@'192.168.1.1' try to login with password 'abc', it will be denied.
+                passwdPolicyMgr.onFailedLogin(curUser);
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        remotePasswd.length == 0 ? "NO" : "YES");
+            }
+        }
+
+    }
+
+    public void checkPlainPassword(String remoteUser, String remoteHost, String remotePasswd,
+            List<UserIdentity> currentUser) throws AuthenticationException {
+        PasswordPolicyManager passwdPolicyMgr = Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+        List<User> users = nameToUsers.get(remoteUser);
+        if (CollectionUtils.isEmpty(users)) {
+            throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                    "YES");
+        }
+        for (User user : users) {
+            if (user.getUserIdentity().isDomain()) {
+                continue;
+            }
+            // check host
+            if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost)) {
+                continue;
+            }
+            UserIdentity curUser = user.getDomainUserIdentity();
+            if (MysqlPassword.checkPlainPass(user.getPassword().getPassword(), remotePasswd)) {
+                passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+                if (currentUser != null) {
+                    currentUser.add(curUser);
+                }
+                return;
+            } else {
+                // set case A. in checkPassword()
+                passwdPolicyMgr.onFailedLogin(curUser);
+                throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                        "YES");
+            }
+        }
+        throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost,
+                "YES");
+    }
+
+    public void clearEntriesSetByResolver() {
+        Iterator<Entry<String, List<User>>> iterator = nameToUsers.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Entry<String, List<User>> next = iterator.next();
+            Iterator<User> iter = next.getValue().iterator();
+            while (iter.hasNext()) {
+                User user = iter.next();
+                if (user.isSetByDomainResolver()) {
+                    iter.remove();
+                }
+            }
+            if (CollectionUtils.isEmpty(next.getValue())) {
+                iterator.remove();
+            } else {
+                Collections.sort(next.getValue());
+            }
+        }
+
+    }
+
+    public User createUser(UserIdentity userIdent, byte[] pwd, UserIdentity domainUserIdent, boolean setByResolver)
+            throws PatternMatcherException {
+        if (userIdentityExist(userIdent, true)) {
+            User userByUserIdentity = getUserByUserIdentity(userIdent);
+            userByUserIdentity.setPassword(pwd);
+            userByUserIdentity.setSetByDomainResolver(setByResolver);
+            return userByUserIdentity;
+        }
+
+        PatternMatcher hostPattern = PatternMatcher
+                .createMysqlPattern(userIdent.getHost(), CaseSensibility.HOST.getCaseSensibility());
+        User user = new User();
+        user.setAnyHost(userIdent.getHost().equals(ANY_HOST));
+        user.setUserIdentity(userIdent);
+        Password password = new Password();
+        password.setPassword(pwd);
+        user.setPassword(password);
+        user.setHostPattern(hostPattern);
+        user.setSetByDomainResolver(setByResolver);
+        if (setByResolver) {
+            Preconditions.checkNotNull(domainUserIdent);
+            user.setDomainUserIdentity(domainUserIdent);
+        }
+        List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            nameToLists = Lists.newArrayList(user);
+            nameToUsers.put(userIdent.getQualifiedUser(), nameToLists);
+        } else {
+            nameToLists.add(user);
+            Collections.sort(nameToLists);
+        }
+        return user;
+    }
+
+    public User getUserByUserIdentity(UserIdentity userIdent) {
+        List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            return null;
+        }
+        Iterator<User> iter = nameToLists.iterator();
+        while (iter.hasNext()) {
+            User user = iter.next();
+            if (user.getUserIdentity().equals(userIdent)) {
+                return user;
+            }
+        }
+        return null;
+    }
+
+    public void removeUser(UserIdentity userIdent) {
+        List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            return;
+        }
+        Iterator<User> iter = nameToLists.iterator();
+        while (iter.hasNext()) {
+            User user = iter.next();
+            if (user.getUserIdentity().equals(userIdent)) {
+                iter.remove();
+            }
+        }
+        if (CollectionUtils.isEmpty(nameToLists)) {
+            nameToUsers.remove(userIdent.getQualifiedUser());
+        } else {
+            Collections.sort(nameToLists);
+        }
+    }
+
+    public Map<String, List<User>> getNameToUsers() {
+        return nameToUsers;

Review Comment:
   Solve security issues by limiting Auth exposure to UserManager



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1098123841


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivEntry.java:
##########
@@ -82,108 +112,7 @@ protected PrivEntry(PatternMatcher hostPattern, String origHost, PatternMatcher
         }
     }
 
-    public PatternMatcher getHostPattern() {
-        return hostPattern;
-    }
-
-    public String getOrigHost() {
-        return origHost;
-    }
-
-    public boolean isAnyHost() {
-        return isAnyHost;
-    }
-
-    public PatternMatcher getUserPattern() {
-        return userPattern;
-    }
-
-    public String getOrigUser() {
-        return origUser;
-    }
-
-    public boolean isAnyUser() {
-        return isAnyUser;
-    }
-
-    public PrivBitSet getPrivSet() {
-        return privSet;
-    }
-
-    public void setPrivSet(PrivBitSet privSet) {
-        this.privSet = privSet;
-    }
-
-    public boolean isSetByDomainResolver() {
-        return isSetByDomainResolver;
-    }
-
-    public void setSetByDomainResolver(boolean isSetByDomainResolver) {
-        this.isSetByDomainResolver = isSetByDomainResolver;
-    }
-
-    public UserIdentity getUserIdent() {
-        return userIdentity;
-    }
-
-    public boolean match(UserIdentity userIdent, boolean exactMatch) {
-        if (exactMatch) {
-            return origUser.equals(userIdent.getQualifiedUser()) && origHost.equals(userIdent.getHost());
-        } else {
-            return origUser.equals(userIdent.getQualifiedUser()) && hostPattern.match(userIdent.getHost());
-        }
-    }
-
-    public abstract boolean keyMatch(PrivEntry other);
-
-    /*
-     * It's a bit complicated when persisting instance which its class has derived classes.
-     * eg: A (top class) -> B (derived) -> C (derived)
-     *
-     * Write process:
-     * C.write()
-     *      |
-     *      --- write class name
-     *      |
-     *      --- super.write()    -----> B.write()
-     *      |                               |
-     *      --- write C's self members      --- write class name (if not write before)
-     *                                      |
-     *                                      --- super.write()    -----> A.write()
-     *                                      |                               |
-     *                                      --- write B's self members      --- write class name (if not write before)
-     *                                                                      |
-     *                                                                      --- write A's self members
-     *
-     * So the final write order is:
-     *      1. C's class name
-     *      2. A's self members
-     *      3. B's self members
-     *      4. C's self members
-     *
-     * In case that class name should only be wrote once, we use isClassNameWrote flag.
-     *
-     * Read process:
-     * static A.read()
-     *      |
-     *      --- read class name and instantiated the class instance (eg. C class)
-     *      |
-     *      --- C.readFields()
-     *          |
-     *          --- super.readFields() --> B.readFields()
-     *          |                           |
-     *          --- read C's self members   --- super.readFields() --> A.readFields()
-     *                                      |                           |
-     *                                      --- read B's self members   --- read A's self members
-     *
-     *  So the final read order is:
-     *      1. C's class name
-     *      2. A's self members
-     *      3. B's self members
-     *      4. C's self members
-     *
-     *  Which is same as Write order.
-     */
+    @Deprecated
     public static PrivEntry read(DataInput in) throws IOException {
         String className = Text.readString(in);
         if (className.startsWith("com.baidu.palo")) {

Review Comment:
   and remove same in PrivTable



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1098111817


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/GlobalPrivEntry.java:
##########
@@ -18,144 +18,52 @@
 package org.apache.doris.mysql.privilege;
 
 import org.apache.doris.analysis.UserIdentity;
-import org.apache.doris.common.AnalysisException;
-import org.apache.doris.common.CaseSensibility;
-import org.apache.doris.common.PatternMatcher;
-import org.apache.doris.common.PatternMatcherWrapper;
-import org.apache.doris.common.io.Text;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.io.DataInput;
-import java.io.DataOutput;
 import java.io.IOException;
 
 public class GlobalPrivEntry extends PrivEntry {
     private static final Logger LOG = LogManager.getLogger(GlobalPrivEntry.class);
 
-    private byte[] password;
-    // set domainUserIdent when this a password entry and is set by domain resolver.
-    // so that when user checking password with user@'IP' and match a entry set by the resolver,
-    // it should return this domainUserIdent as "current user". And user can use this user ident to get privileges
-    // further.
-    private UserIdentity domainUserIdent;
+    @Deprecated
+    protected byte[] password;
+    @Deprecated
+    protected UserIdentity domainUserIdent;

Review Comment:
   There is also privSet inherited from the parent class. The reason why the parent class cannot be used directly is that 1. The parent class is abstract, 2. It is not intuitive



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] morningman commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "morningman (via GitHub)" <gi...@apache.org>.
morningman commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1098774168


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java:
##########
@@ -0,0 +1,605 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.ResourcePattern;
+import org.apache.doris.analysis.TablePattern;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.FeMetaVersion;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.privilege.Auth.PrivLevel;
+import org.apache.doris.persist.gson.GsonPostProcessable;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.gson.annotations.SerializedName;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class Role implements Writable, GsonPostProcessable {
+    private static final Logger LOG = LogManager.getLogger(Role.class);
+
+    // operator is responsible for operating cluster, such as add/drop node
+    public static String OPERATOR_ROLE = "operator";
+    // admin is like DBA, who has all privileges except for NODE privilege held by operator
+    public static String ADMIN_ROLE = "admin";
+
+    public static Role OPERATOR = new Role(OPERATOR_ROLE,
+            TablePattern.ALL, PrivBitSet.of(Privilege.NODE_PRIV, Privilege.ADMIN_PRIV),
+            ResourcePattern.ALL, PrivBitSet.of(Privilege.NODE_PRIV, Privilege.ADMIN_PRIV));
+    public static Role ADMIN = new Role(ADMIN_ROLE,
+            TablePattern.ALL, PrivBitSet.of(Privilege.ADMIN_PRIV),
+            ResourcePattern.ALL, PrivBitSet.of(Privilege.ADMIN_PRIV));
+
+    @SerializedName(value = "roleName")
+    private String roleName;
+    // Will be persisted
+    @SerializedName(value = "tblPatternToPrivs")
+    private Map<TablePattern, PrivBitSet> tblPatternToPrivs = Maps.newConcurrentMap();
+    @SerializedName(value = "resourcePatternToPrivs")
+    private Map<ResourcePattern, PrivBitSet> resourcePatternToPrivs = Maps.newConcurrentMap();
+
+    // Will not be persisted,generage by tblPatternToPrivs and resourcePatternToPrivs
+    private GlobalPrivTable globalPrivTable = new GlobalPrivTable();
+    private CatalogPrivTable catalogPrivTable = new CatalogPrivTable();
+    private DbPrivTable dbPrivTable = new DbPrivTable();
+    private TablePrivTable tablePrivTable = new TablePrivTable();
+    private ResourcePrivTable resourcePrivTable = new ResourcePrivTable();
+
+    @Deprecated
+    private Set<UserIdentity> users = Sets.newConcurrentHashSet();
+
+    @Deprecated
+    public Set<UserIdentity> getUsers() {
+        return users;
+    }
+
+    private Role() {
+
+    }
+
+    public Role(String roleName) {
+        this.roleName = roleName;
+    }
+
+    public Role(String roleName, TablePattern tablePattern, PrivBitSet privs) throws DdlException {
+        this.roleName = roleName;
+        this.tblPatternToPrivs.put(tablePattern, privs);
+        grantPrivs(tablePattern, privs.copy());
+    }
+
+    public Role(String roleName, ResourcePattern resourcePattern, PrivBitSet privs) throws DdlException {
+        this.roleName = roleName;
+        this.resourcePatternToPrivs.put(resourcePattern, privs);
+        grantPrivs(resourcePattern, privs.copy());
+    }
+
+    public Role(String roleName, TablePattern tablePattern, PrivBitSet tablePrivs,
+            ResourcePattern resourcePattern, PrivBitSet resourcePrivs) {
+        this.roleName = roleName;
+        this.tblPatternToPrivs.put(tablePattern, tablePrivs);
+        this.resourcePatternToPrivs.put(resourcePattern, resourcePrivs);
+        //for init admin role,will not generate exception
+        try {
+            grantPrivs(tablePattern, tablePrivs.copy());
+            grantPrivs(resourcePattern, resourcePrivs.copy());
+        } catch (DdlException e) {
+            LOG.warn("grant failed,", e);
+        }
+
+    }
+
+    public String getRoleName() {
+        return roleName;
+    }
+
+    public Map<TablePattern, PrivBitSet> getTblPatternToPrivs() {
+        return tblPatternToPrivs;
+    }
+
+    public Map<ResourcePattern, PrivBitSet> getResourcePatternToPrivs() {
+        return resourcePatternToPrivs;
+    }
+
+    // merge role not check role name.
+    public void mergeNotCheck(Role other) throws DdlException {
+        for (Map.Entry<TablePattern, PrivBitSet> entry : other.getTblPatternToPrivs().entrySet()) {
+            if (tblPatternToPrivs.containsKey(entry.getKey())) {
+                PrivBitSet existPrivs = tblPatternToPrivs.get(entry.getKey());
+                existPrivs.or(entry.getValue());
+            } else {
+                tblPatternToPrivs.put(entry.getKey(), entry.getValue());
+            }
+            grantPrivs(entry.getKey(), entry.getValue().copy());
+        }
+        for (Map.Entry<ResourcePattern, PrivBitSet> entry : other.resourcePatternToPrivs.entrySet()) {
+            if (resourcePatternToPrivs.containsKey(entry.getKey())) {
+                PrivBitSet existPrivs = resourcePatternToPrivs.get(entry.getKey());
+                existPrivs.or(entry.getValue());
+            } else {
+                resourcePatternToPrivs.put(entry.getKey(), entry.getValue());
+            }
+            grantPrivs(entry.getKey(), entry.getValue().copy());
+        }
+    }
+
+    public void merge(Role other) throws DdlException {
+        Preconditions.checkState(roleName.equalsIgnoreCase(other.getRoleName()));
+        mergeNotCheck(other);
+    }
+
+    public boolean checkGlobalPriv(PrivPredicate wanted) {
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        return checkGlobalInternal(wanted, savedPrivs);
+    }
+
+    public boolean checkCtlPriv(String ctl, PrivPredicate wanted) {
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        if (checkGlobalInternal(wanted, savedPrivs)
+                || checkCatalogInternal(ctl, wanted, savedPrivs)) {
+            return true;
+        }
+        // if user has any privs of databases or tables in this catalog, and the wanted priv is SHOW, return true
+        if (ctl != null && wanted == PrivPredicate.SHOW && checkAnyPrivWithinCatalog(ctl)) {
+            return true;
+        }
+        LOG.debug("failed to get wanted privs: {}, granted: {}", wanted, savedPrivs);
+        return false;
+    }
+
+    private boolean checkGlobalInternal(PrivPredicate wanted, PrivBitSet savedPrivs) {
+        globalPrivTable.getPrivs(savedPrivs);
+        if (Privilege.satisfy(savedPrivs, wanted)) {
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     * User may not have privs on a catalog, but have privs of databases or tables in this catalog.
+     * So we have to check if user has any privs of databases or tables in this catalog.
+     * if so, the catalog should be visible to this user.
+     */
+    private boolean checkAnyPrivWithinCatalog(String ctl) {
+        return dbPrivTable.hasPrivsOfCatalog(ctl)
+                || tablePrivTable.hasPrivsOfCatalog(ctl);
+
+    }
+
+    private boolean checkCatalogInternal(String ctl, PrivPredicate wanted, PrivBitSet savedPrivs) {
+        catalogPrivTable.getPrivs(ctl, savedPrivs);
+        if (Privilege.satisfy(savedPrivs, wanted)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public boolean checkDbPriv(String ctl, String db, PrivPredicate wanted) {
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        if (checkGlobalInternal(wanted, savedPrivs)
+                || checkCatalogInternal(ctl, wanted, savedPrivs)
+                || checkDbInternal(ctl, db, wanted, savedPrivs)) {
+            return true;
+        }
+
+        // if user has any privs of table in this db, and the wanted priv is SHOW, return true
+        if (ctl != null && db != null && wanted == PrivPredicate.SHOW && checkAnyPrivWithinDb(ctl, db)) {
+            return true;
+        }
+
+        LOG.debug("failed to get wanted privs: {}, granted: {}", wanted, savedPrivs);
+        return false;
+    }
+
+    /*
+     * User may not have privs on a database, but have privs of tables in this database.
+     * So we have to check if user has any privs of tables in this database.
+     * if so, the database should be visible to this user.
+     */
+    private boolean checkAnyPrivWithinDb(String ctl, String db) {
+        return tablePrivTable.hasPrivsOfDb(ctl, db);
+
+    }
+
+    private boolean checkDbInternal(String ctl, String db, PrivPredicate wanted,
+            PrivBitSet savedPrivs) {
+        dbPrivTable.getPrivs(ctl, db, savedPrivs);
+        if (Privilege.satisfy(savedPrivs, wanted)) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean checkTblPriv(String ctl, String db, String tbl, PrivPredicate wanted) {
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        if (checkGlobalInternal(wanted, savedPrivs)
+                || checkCatalogInternal(ctl, wanted, savedPrivs)
+                || checkDbInternal(ctl, db, wanted, savedPrivs)
+                || checkTblInternal(ctl, db, tbl, wanted, savedPrivs)) {
+            return true;
+        }
+        LOG.debug("failed to get wanted privs: {}, granted: {}", wanted, savedPrivs);
+        return false;
+    }
+
+    private boolean checkTblInternal(String ctl, String db, String tbl, PrivPredicate wanted, PrivBitSet savedPrivs) {
+        tablePrivTable.getPrivs(ctl, db, tbl, savedPrivs);
+        return Privilege.satisfy(savedPrivs, wanted);
+    }
+
+    public boolean checkResourcePriv(String resourceName, PrivPredicate wanted) {
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        if (checkGlobalInternal(wanted, savedPrivs)
+                || checkResourceInternal(resourceName, wanted, savedPrivs)) {
+            return true;
+        }
+
+        LOG.debug("failed to get wanted privs: {}, granted: {}", wanted, savedPrivs);
+        return false;
+    }
+
+    private boolean checkResourceInternal(String resourceName, PrivPredicate wanted, PrivBitSet savedPrivs) {
+        resourcePrivTable.getPrivs(resourceName, savedPrivs);
+        return Privilege.satisfy(savedPrivs, wanted);
+    }
+
+    public boolean checkHasPriv(PrivPredicate priv, PrivLevel[] levels) {
+        for (PrivLevel privLevel : levels) {
+            switch (privLevel) {
+                case GLOBAL:
+                    if (globalPrivTable.hasPriv(priv)) {
+                        return true;
+                    }
+                    break;
+                case DATABASE:
+                    if (dbPrivTable.hasPriv(priv)) {
+                        return true;
+                    }
+                    break;
+                case TABLE:
+                    if (tablePrivTable.hasPriv(priv)) {
+                        return true;
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+        return false;
+    }
+
+    public GlobalPrivTable getGlobalPrivTable() {
+        return globalPrivTable;
+    }
+
+    public CatalogPrivTable getCatalogPrivTable() {
+        return catalogPrivTable;
+    }
+
+    public DbPrivTable getDbPrivTable() {
+        return dbPrivTable;
+    }
+
+    public TablePrivTable getTablePrivTable() {
+        return tablePrivTable;
+    }
+
+    public ResourcePrivTable getResourcePrivTable() {
+        return resourcePrivTable;
+    }
+
+    public boolean checkCanEnterCluster(String clusterName) {
+        if (checkGlobalPriv(PrivPredicate.ALL)) {
+            return true;
+        }
+
+        if (dbPrivTable.hasClusterPriv(clusterName)) {
+            return true;
+        }
+
+        if (tablePrivTable.hasClusterPriv(clusterName)) {
+            return true;
+        }
+        return false;
+    }
+
+
+    private void grantPrivs(ResourcePattern resourcePattern, PrivBitSet privs) throws DdlException {
+
+        // grant privs to user
+        switch (resourcePattern.getPrivLevel()) {
+            case GLOBAL:
+                grantGlobalPrivs(privs);
+                break;
+            case RESOURCE:
+                grantResourcePrivs(resourcePattern.getResourceName(), privs);
+                break;
+            default:
+                Preconditions.checkNotNull(null, resourcePattern.getPrivLevel());
+        }
+
+    }
+
+    private void grantPrivs(TablePattern tblPattern, PrivBitSet privs) throws DdlException {
+        // grant privs to user
+        switch (tblPattern.getPrivLevel()) {
+            case GLOBAL:
+                grantGlobalPrivs(privs);
+                break;
+            case CATALOG:
+                grantCatalogPrivs(tblPattern.getQualifiedCtl(),
+                        privs);
+                break;
+            case DATABASE:
+                grantDbPrivs(tblPattern.getQualifiedCtl(),
+                        tblPattern.getQualifiedDb(),
+                        privs);
+                break;
+            case TABLE:
+                grantTblPrivs(tblPattern.getQualifiedCtl(),
+                        tblPattern.getQualifiedDb(),
+                        tblPattern.getTbl(),
+                        privs);
+                break;
+            default:
+                Preconditions.checkNotNull(null, tblPattern.getPrivLevel());
+        }
+
+    }
+
+    private GlobalPrivEntry grantGlobalPrivs(PrivBitSet privs) throws DdlException {
+        GlobalPrivEntry entry = GlobalPrivEntry.create(privs);
+        globalPrivTable.addEntry(entry, false, false);
+        return entry;
+    }
+
+    private void grantResourcePrivs(String resourceName, PrivBitSet privs) throws DdlException {
+        ResourcePrivEntry entry;
+        try {
+            entry = ResourcePrivEntry.create(resourceName, privs);
+        } catch (AnalysisException | PatternMatcherException e) {
+            throw new DdlException(e.getMessage());
+        }
+        resourcePrivTable.addEntry(entry, false, false);
+    }
+
+    private void grantCatalogPrivs(String ctl, PrivBitSet privs) throws DdlException {
+        CatalogPrivEntry entry;
+        try {
+            entry = CatalogPrivEntry.create(ctl, privs);
+        } catch (AnalysisException e) {
+            throw new DdlException(e.getMessage());
+        }
+        catalogPrivTable.addEntry(entry, false, false);
+    }
+
+    private void grantDbPrivs(String ctl, String db,
+            PrivBitSet privs) throws DdlException {
+        DbPrivEntry entry;
+        try {
+            entry = DbPrivEntry.create(ctl, db, privs);
+        } catch (AnalysisException e) {
+            throw new DdlException(e.getMessage());
+        }
+        dbPrivTable.addEntry(entry, false, false);
+    }
+
+    private void grantTblPrivs(String ctl, String db, String tbl, PrivBitSet privs) throws DdlException {
+        TablePrivEntry entry;
+        try {
+            entry = TablePrivEntry.create(ctl, db, tbl, privs);
+        } catch (AnalysisException e) {
+            throw new DdlException(e.getMessage());
+        }
+        tablePrivTable.addEntry(entry, false, false);
+    }
+
+    public void revokePrivs(TablePattern tblPattern, PrivBitSet privs, boolean errOnNonExist) throws DdlException {
+        PrivBitSet existingPriv = tblPatternToPrivs.get(tblPattern);
+        if (existingPriv == null) {
+            if (errOnNonExist) {
+                throw new DdlException(tblPattern + " does not exist in role " + roleName);
+            }
+            return;
+        }
+        existingPriv.remove(privs);
+        revokePrivs(tblPattern, privs);
+    }
+
+    public void revokePrivs(ResourcePattern resourcePattern, PrivBitSet privs, boolean errOnNonExist)
+            throws DdlException {
+        PrivBitSet existingPriv = resourcePatternToPrivs.get(resourcePattern);
+        if (existingPriv == null) {
+            if (errOnNonExist) {
+                throw new DdlException(resourcePattern + " does not exist in role " + roleName);
+            }
+            return;
+        }
+        existingPriv.remove(privs);
+        revokePrivs(resourcePattern, privs);
+    }
+
+    private void revokePrivs(ResourcePattern resourcePattern, PrivBitSet privs) throws DdlException {
+        switch (resourcePattern.getPrivLevel()) { // CHECKSTYLE IGNORE THIS LINE: missing switch default
+            case GLOBAL:
+                revokeGlobalPrivs(privs);
+                break;
+            case RESOURCE:
+                revokeResourcePrivs(resourcePattern.getResourceName(), privs);
+                break;
+        }
+    }
+
+    private void revokeResourcePrivs(String resourceName, PrivBitSet privs) throws DdlException {
+        ResourcePrivEntry entry;
+        try {
+            entry = ResourcePrivEntry.create(resourceName, privs);
+        } catch (AnalysisException | PatternMatcherException e) {
+            throw new DdlException(e.getMessage());
+        }
+        resourcePrivTable.revoke(entry, false, true);
+    }
+
+    private void revokePrivs(TablePattern tblPattern, PrivBitSet privs) throws DdlException {
+        switch (tblPattern.getPrivLevel()) {
+            case GLOBAL:
+                revokeGlobalPrivs(privs);
+                break;
+            case CATALOG:
+                revokeCatalogPrivs(tblPattern.getQualifiedCtl(), privs);
+                break;
+            case DATABASE:
+                revokeDbPrivs(tblPattern.getQualifiedCtl(),
+                        tblPattern.getQualifiedDb(), privs);
+                break;
+            case TABLE:
+                revokeTblPrivs(tblPattern.getQualifiedCtl(), tblPattern.getQualifiedDb(),
+                        tblPattern.getTbl(), privs);
+                break;
+            default:
+                Preconditions.checkNotNull(null, tblPattern.getPrivLevel());
+        }
+
+    }
+
+    private void revokeGlobalPrivs(PrivBitSet privs)
+            throws DdlException {
+        GlobalPrivEntry entry = GlobalPrivEntry.create(privs);
+        globalPrivTable.revoke(entry, false, false);
+    }
+
+    private void revokeCatalogPrivs(String ctl,
+            PrivBitSet privs) throws DdlException {
+        CatalogPrivEntry entry;
+        try {
+            entry = CatalogPrivEntry.create(
+                    ctl, privs);
+        } catch (AnalysisException e) {
+            throw new DdlException(e.getMessage());
+        }
+        catalogPrivTable.revoke(entry, false, true);
+    }
+
+    private void revokeDbPrivs(String ctl, String db, PrivBitSet privs) throws DdlException {
+        DbPrivEntry entry;
+        try {
+            entry = DbPrivEntry.create(ctl, db, privs);
+        } catch (AnalysisException e) {
+            throw new DdlException(e.getMessage());
+        }
+
+        dbPrivTable.revoke(entry, false, true);
+    }
+
+    private void revokeTblPrivs(String ctl, String db, String tbl,
+            PrivBitSet privs) throws DdlException {
+        TablePrivEntry entry;
+        try {
+            entry = TablePrivEntry.create(ctl, db, tbl, privs);
+        } catch (AnalysisException e) {
+            throw new DdlException(e.getMessage());
+        }
+        tablePrivTable.revoke(entry, false, true);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("role: ").append(roleName).append(", db table privs: ").append(tblPatternToPrivs);
+        sb.append(", resource privs: ").append(resourcePatternToPrivs);
+        return sb.toString();
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        Text.writeString(out, GsonUtils.GSON.toJson(this));
+    }
+
+    public static Role read(DataInput in) throws IOException {
+        if (Env.getCurrentEnvJournalVersion() < FeMetaVersion.VERSION_116) {
+            Role role = new Role();
+            try {
+                role.readFields(in);
+            } catch (DdlException e) {
+                LOG.warn("grant failed,", e);
+            }
+            return role;
+        } else {
+            String json = Text.readString(in);
+            return GsonUtils.GSON.fromJson(json, Role.class);
+        }
+    }
+
+
+    public void readFields(DataInput in) throws IOException, DdlException {

Review Comment:
   use `private` and mark it as @deprecated



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserRoleManager.java:
##########
@@ -0,0 +1,133 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.persist.gson.GsonPostProcessable;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.gson.annotations.SerializedName;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserRoleManager implements Writable, GsonPostProcessable {
+    // Concurrency control is delegated by Auth, so not concurrentMap
+    @SerializedName(value = "userToRoles")
+    private Map<UserIdentity, Set<String>> userToRoles = Maps.newHashMap();
+    // Will not be persisted,generage by userToRoles
+    private Map<String, Set<UserIdentity>> roleToUsers = Maps.newHashMap();
+
+    public void addUserRole(UserIdentity userIdentity, String roleName) {
+        Set<String> roles = userToRoles.get(userIdentity);
+        if (CollectionUtils.isEmpty(roles)) {
+            roles = Sets.newHashSet();
+        }
+        roles.add(roleName);
+        userToRoles.put(userIdentity, roles);
+        Set<UserIdentity> userIdentities = roleToUsers.get(roleName);
+        if (CollectionUtils.isEmpty(userIdentities)) {
+            userIdentities = Sets.newHashSet();
+        }
+        userIdentities.add(userIdentity);
+        roleToUsers.put(roleName, userIdentities);
+    }
+
+    public void dropUser(UserIdentity userIdentity) {
+        if (!userToRoles.containsKey(userIdentity)) {
+            return;
+        }
+        Set<String> roles = userToRoles.remove(userIdentity);
+        for (String roleName : roles) {
+            Set<UserIdentity> userIdentities = roleToUsers.get(roleName);
+            if (CollectionUtils.isEmpty(userIdentities)) {
+                continue;
+            }
+            userIdentities.remove(userIdentity);
+            if (CollectionUtils.isEmpty(userIdentities)) {
+                roleToUsers.remove(roleName);
+            }
+        }
+    }
+
+    public void dropRole(String roleName) {
+        if (!roleToUsers.containsKey(roleName)) {
+            return;
+        }
+        Set<UserIdentity> remove = roleToUsers.remove(roleName);
+        for (UserIdentity userIdentity : remove) {
+            Set<String> roles = userToRoles.get(userIdentity);
+            if (CollectionUtils.isEmpty(roles)) {
+                continue;
+            }
+            roles.remove(roleName);
+            if (CollectionUtils.isEmpty(roles)) {
+                userToRoles.remove(userIdentity);
+            }
+        }
+    }
+
+    public Set<String> getRolesByUser(UserIdentity user) {
+        Set<String> roles = userToRoles.get(user);
+        return roles == null ? Collections.EMPTY_SET : roles;
+    }
+
+    public Set<UserIdentity> getUsersByRole(String roleName) {
+        Set<UserIdentity> userIdentities = roleToUsers.get(roleName);
+        return userIdentities == null ? Collections.EMPTY_SET : userIdentities;
+    }
+
+    @Override
+    public String toString() {
+        return userToRoles.toString();
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        Text.writeString(out, GsonUtils.GSON.toJson(this));
+    }
+
+    public static UserRoleManager read(DataInput in) throws IOException {
+        String json = Text.readString(in);
+        return GsonUtils.GSON.fromJson(json, UserRoleManager.class);
+    }
+
+    @Override
+    public void gsonPostProcess() throws IOException {
+        for (Entry<UserIdentity, Set<String>> entry : userToRoles.entrySet()) {
+            for (String roleName : entry.getValue()) {
+                Set<UserIdentity> userIdentities = roleToUsers.get(roleName);

Review Comment:
   The `roleToUsers` maybe null here



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Privilege.java:
##########
@@ -44,8 +44,8 @@ public enum PaloPrivilege {
             USAGE_PRIV
     };
 
-    public static Map<PaloPrivilege, String> privInPaloToMysql =
-            ImmutableMap.<PaloPrivilege, String>builder() // No NODE_PRIV and ADMIN_PRIV in the mysql
+    public static Map<Privilege, String> privInPaloToMysql =

Review Comment:
   ```suggestion
       public static Map<Privilege, String> privInDorisToMysql =
   ```



##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java:
##########
@@ -184,8 +167,8 @@ public static RoleManager read(DataInput in) throws IOException {
     public void write(DataOutput out) throws IOException {

Review Comment:
   Change to Gson to, use meta version to make it compatible



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1098130582


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+    public static final String ANY_HOST = "%";
+    private static final Logger LOG = LogManager.getLogger(UserManager.class);
+    //    Set<User> users = new HashSet<>();
+    //One name may have multiple User,because host can be different
+    private Map<String, List<User>> nameToUsers = new HashMap<>();

Review Comment:
       // Concurrency control is delegated by Auth, so not concurrentMap
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] morningman commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "morningman (via GitHub)" <gi...@apache.org>.
morningman commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1097548021


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivEntry.java:
##########
@@ -82,108 +112,7 @@ protected PrivEntry(PatternMatcher hostPattern, String origHost, PatternMatcher
         }
     }
 
-    public PatternMatcher getHostPattern() {
-        return hostPattern;
-    }
-
-    public String getOrigHost() {
-        return origHost;
-    }
-
-    public boolean isAnyHost() {
-        return isAnyHost;
-    }
-
-    public PatternMatcher getUserPattern() {
-        return userPattern;
-    }
-
-    public String getOrigUser() {
-        return origUser;
-    }
-
-    public boolean isAnyUser() {
-        return isAnyUser;
-    }
-
-    public PrivBitSet getPrivSet() {
-        return privSet;
-    }
-
-    public void setPrivSet(PrivBitSet privSet) {
-        this.privSet = privSet;
-    }
-
-    public boolean isSetByDomainResolver() {
-        return isSetByDomainResolver;
-    }
-
-    public void setSetByDomainResolver(boolean isSetByDomainResolver) {
-        this.isSetByDomainResolver = isSetByDomainResolver;
-    }
-
-    public UserIdentity getUserIdent() {
-        return userIdentity;
-    }
-
-    public boolean match(UserIdentity userIdent, boolean exactMatch) {
-        if (exactMatch) {
-            return origUser.equals(userIdent.getQualifiedUser()) && origHost.equals(userIdent.getHost());
-        } else {
-            return origUser.equals(userIdent.getQualifiedUser()) && hostPattern.match(userIdent.getHost());
-        }
-    }
-
-    public abstract boolean keyMatch(PrivEntry other);
-
-    /*
-     * It's a bit complicated when persisting instance which its class has derived classes.
-     * eg: A (top class) -> B (derived) -> C (derived)
-     *
-     * Write process:
-     * C.write()
-     *      |
-     *      --- write class name
-     *      |
-     *      --- super.write()    -----> B.write()
-     *      |                               |
-     *      --- write C's self members      --- write class name (if not write before)
-     *                                      |
-     *                                      --- super.write()    -----> A.write()
-     *                                      |                               |
-     *                                      --- write B's self members      --- write class name (if not write before)
-     *                                                                      |
-     *                                                                      --- write A's self members
-     *
-     * So the final write order is:
-     *      1. C's class name
-     *      2. A's self members
-     *      3. B's self members
-     *      4. C's self members
-     *
-     * In case that class name should only be wrote once, we use isClassNameWrote flag.
-     *
-     * Read process:
-     * static A.read()
-     *      |
-     *      --- read class name and instantiated the class instance (eg. C class)
-     *      |
-     *      --- C.readFields()
-     *          |
-     *          --- super.readFields() --> B.readFields()
-     *          |                           |
-     *          --- read C's self members   --- super.readFields() --> A.readFields()
-     *                                      |                           |
-     *                                      --- read B's self members   --- read A's self members
-     *
-     *  So the final read order is:
-     *      1. C's class name
-     *      2. A's self members
-     *      3. B's self members
-     *      4. C's self members
-     *
-     *  Which is same as Write order.
-     */
+    @Deprecated
     public static PrivEntry read(DataInput in) throws IOException {

Review Comment:
   I think we can refactor this read method.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr closed pull request #16091: [wip](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr closed pull request #16091: [wip](auth)Implementing privilege management with rbac model
URL: https://github.com/apache/doris/pull/16091


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1098155911


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserRoleManager.java:
##########
@@ -0,0 +1,137 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.mysql.privilege;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserRoleManager implements Writable {
+    private Map<UserIdentity, Set<String>> userToRoles = Maps.newConcurrentMap();

Review Comment:
   Concurrency control is delegated by Auth, so not concurrentMap



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr closed pull request #16091: [wip](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr closed pull request #16091: [wip](auth)Implementing privilege management with rbac model
URL: https://github.com/apache/doris/pull/16091


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr commented on a diff in pull request #16091: [feature](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1098306625


##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java:
##########
@@ -72,37 +61,29 @@ public boolean hasPriv(String host, String user, PrivPredicate wanted) {
      * NOTICE, this method does not set password for the newly added entry if this is a user priv table, the caller
      * need to set password later.
      */
-    public PrivEntry addEntry(PrivEntry newEntry, boolean errOnExist, boolean errOnNonExist) throws DdlException {
+    public PrivEntry addEntry(PrivEntry newEntry,
+            boolean errOnExist, boolean errOnNonExist) throws DdlException {
         PrivEntry existingEntry = getExistingEntry(newEntry);
         if (existingEntry == null) {
             if (errOnNonExist) {
-                throw new DdlException("User " + newEntry.getUserIdent() + " does not exist");
+                throw new DdlException("entry does not exist");
             }
             entries.add(newEntry);
             Collections.sort(entries);
             LOG.info("add priv entry: {}", newEntry);
             return newEntry;
         } else {
             if (errOnExist) {
-                throw new DdlException("User already exist");
+                throw new DdlException("entry already exist");
             } else {
-                checkOperationAllowed(existingEntry, newEntry, "ADD ENTRY");
-                // if existing entry is set by domain resolver, just replace it with the new entry.
-                // if existing entry is not set by domain resolver, merge the 2 entries.
-                if (existingEntry.isSetByDomainResolver()) {
-                    existingEntry.setPrivSet(newEntry.getPrivSet());
-                    existingEntry.setSetByDomainResolver(newEntry.isSetByDomainResolver());
-                    LOG.debug("reset priv entry: {}", existingEntry);
-                } else if (!newEntry.isSetByDomainResolver()) {
-                    mergePriv(existingEntry, newEntry);
-                    existingEntry.setSetByDomainResolver(false);
-                    LOG.debug("merge priv entry: {}", existingEntry);
-                }
+                mergePriv(existingEntry, newEntry);
+                LOG.debug("merge priv entry: {}", existingEntry);

Review Comment:
   create user user1@domain -> domain(ip1, ip2)
   create user user1@ip1
   How to handle it?
   ===================>
   update user1@ip1.setByResolver to false



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


[GitHub] [doris] zddr closed pull request #16091: [wip](auth)Implementing privilege management with rbac model

Posted by "zddr (via GitHub)" <gi...@apache.org>.
zddr closed pull request #16091: [wip](auth)Implementing privilege management with rbac model
URL: https://github.com/apache/doris/pull/16091


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org