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: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
