diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 index 359be7ed0f1dd7..0f4d05c8212ad5 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 @@ -407,6 +407,8 @@ supportedShowStatement | SHOW FULL? BUILTIN FUNCTIONS (LIKE STRING_LITERAL)? #showBuiltinFunctions | SHOW ALL? GRANTS #showGrants | SHOW GRANTS FOR userIdentify #showGrantsForUser + | SHOW GRANTS FOR ROLE identifierOrText #showGrantsForRole + | SHOW CREATE USER userIdentify #showCreateUser | SHOW SNAPSHOT ON repo=identifier wildWhere? #showSnapshot | SHOW LOAD PROFILE loadIdPath=STRING_LITERAL? limitClause? #showLoadProfile diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java index d4c3384599ebe9..635a5192e39674 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java @@ -2142,4 +2142,27 @@ public List> getAllUserInfo() { } return userInfos; } + + public List> getRoleAuthInfo(String roleName) { + List> results = Lists.newArrayList(); + Role role = roleManager.getRole(roleName); + if (role == null) { + return results; + } + // get all users belong to this role + Set users = getRoleUsers(roleName); + for (UserIdentity user : users) { + List> userGrants = getAuthInfo(user); + results.addAll(userGrants); + } + return results; + } + + public List> getRoleAuthInfo(String roleName, UserIdentity currentUser) { + List> results = Lists.newArrayList(); + List> userGrants = getAuthInfo(currentUser); + results.addAll(userGrants); + return results; + } + } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java index 4dd320f1aa02e4..5896299f99706e 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java @@ -401,6 +401,7 @@ import org.apache.doris.nereids.DorisParser.ShowFunctionsContext; import org.apache.doris.nereids.DorisParser.ShowGlobalFunctionsContext; import org.apache.doris.nereids.DorisParser.ShowGrantsContext; +import org.apache.doris.nereids.DorisParser.ShowGrantsForRoleContext; import org.apache.doris.nereids.DorisParser.ShowGrantsForUserContext; import org.apache.doris.nereids.DorisParser.ShowIndexAnalyzerContext; import org.apache.doris.nereids.DorisParser.ShowIndexCharFilterContext; @@ -10236,4 +10237,10 @@ private List parseSortItems(List sor return sortFields.build(); } + + @Override + public LogicalPlan visitShowGrantsForRole(ShowGrantsForRoleContext ctx) { + String roleName = visitIdentifierOrText(ctx.identifierOrText()); + return new ShowGrantsCommand(roleName); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowGrantsCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowGrantsCommand.java index 5daf756db96508..6560c79cda6ff7 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowGrantsCommand.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowGrantsCommand.java @@ -37,6 +37,7 @@ import java.util.Comparator; import java.util.List; +import java.util.Set; /** * show grants command @@ -45,6 +46,7 @@ public class ShowGrantsCommand extends ShowCommand { private static final ShowResultSetMetaData META_DATA; private final boolean isAll; private UserIdentity userIdent; // if not given will update with self. + private String roleName; static { ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder(); @@ -54,13 +56,18 @@ public class ShowGrantsCommand extends ShowCommand { META_DATA = builder.build(); } - /** - * constructor - */ public ShowGrantsCommand(UserIdentity userIdent, boolean isAll) { super(PlanType.SHOW_GRANTS_COMMAND); this.userIdent = userIdent; this.isAll = isAll; + this.roleName = null; + } + + public ShowGrantsCommand(String roleName) { + super(PlanType.SHOW_GRANTS_COMMAND); + this.roleName = roleName; + this.isAll = false; + this.userIdent = null; } @Override @@ -82,13 +89,31 @@ public ShowResultSet doRun(ConnectContext ctx, StmtExecutor executor) throws Exc } } boolean isSelf = userIdent != null && ConnectContext.get().getCurrentUserIdentity().equals(userIdent); - Preconditions.checkState(isAll || userIdent != null); - // if show all grants, or show other user's grants, need global GRANT priv. + boolean hasGlobalGrantPriv = Env.getCurrentEnv().getAccessManager() + .checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT); + Preconditions.checkState(isAll || userIdent != null || roleName != null); + // if show all grants, or show other user's grants, or show role's grants, need global GRANT priv. if (isAll || !isSelf) { - if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + if (!hasGlobalGrantPriv) { ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT"); } } + if (roleName != null) { + // check role if exists + if (!Env.getCurrentEnv().getAccessManager().getAuth().doesRoleExist(roleName)) { + throw new AnalysisException(String.format("Role: %s does not exist", roleName)); + } + + // check current user if belongs to this role + Set roleUsers = Env.getCurrentEnv().getAccessManager().getAuth().getRoleUsers(roleName); + boolean hasRole = roleUsers.contains(ConnectContext.get().getCurrentUserIdentity()); + + // only users has admin priv or users belong to this role, that have show priv + if (!hasGlobalGrantPriv && !hasRole) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT"); + } + return getRoleGrants(ctx, roleName, hasGlobalGrantPriv); + } // ldap user not exist in userManager, so should not check if (userIdent != null && !isSelf && !Env.getCurrentEnv().getAccessManager().getAuth() .doesUserExist(userIdent)) { @@ -106,4 +131,19 @@ public R accept(PlanVisitor visitor, C context) { return visitor.visitShowGrantsCommand(this, context); } + private ShowResultSet getRoleGrants(ConnectContext ctx, String roleName, boolean hasGlobalGrantPriv) + throws Exception { + List> roleAuthInfo; + if (hasGlobalGrantPriv) { + // only users have admin priv can show all grants for this role + roleAuthInfo = Env.getCurrentEnv().getAccessManager().getAuth().getRoleAuthInfo(roleName); + } else { + // normal user can only show grant of themselves + UserIdentity currentUser = ConnectContext.get().getCurrentUserIdentity(); + roleAuthInfo = Env.getCurrentEnv().getAccessManager().getAuth().getRoleAuthInfo(roleName, currentUser); + } + roleAuthInfo.sort(Comparator.comparing(list -> list.isEmpty() ? "" : list.get(0))); + return new ShowResultSet(getMetaData(), roleAuthInfo); + } + } diff --git a/regression-test/suites/query_p0/show/test_nereids_show_grants_for_role.groovy b/regression-test/suites/query_p0/show/test_nereids_show_grants_for_role.groovy new file mode 100644 index 00000000000000..78001c69ae4ab0 --- /dev/null +++ b/regression-test/suites/query_p0/show/test_nereids_show_grants_for_role.groovy @@ -0,0 +1,128 @@ +// 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. + +suite("test_show_grants_for_role", "p0,auth") { + def currentTimeMillis = System.currentTimeMillis() + String roleName = "test_show_grants_for_role_${currentTimeMillis}_role" + String noneExistRoleName = "test_show_grants_for_role_${currentTimeMillis}_none_exist_role" + String userName1WithRole = "'test_show_grants_for_role_${currentTimeMillis}_user1_with_role'@'%'" + String userName2WithRole = "'test_show_grants_for_role_${currentTimeMillis}_user2_with_role'@'%'" + String pwd = "123456" + String dbName = "test_show_grants_for_role_${currentTimeMillis}_db" + String tableName = "test_show_grants_for_role_${currentTimeMillis}_table" + String userWithGrantPriv = "test_show_grants_for_role_${currentTimeMillis}_user_with_grant_priv" + + try_sql("DROP ROLE IF EXISTS ${roleName}") + try_sql("DROP USER IF EXISTS ${userName1WithRole}") + try_sql("DROP USER IF EXISTS ${userName2WithRole}") + + // Create role and user + sql """CREATE ROLE ${roleName}""" + sql """CREATE USER ${userName1WithRole} IDENTIFIED BY '${pwd}' default role '${roleName}'""" + sql """CREATE USER ${userName2WithRole} IDENTIFIED BY '${pwd}' default role '${roleName}'""" + sql """CREATE USER ${userWithGrantPriv} IDENTIFIED BY '${pwd}'""" + sql """create database if not exists ${dbName}""" + sql """ + CREATE TABLE if not exists ${dbName}.${tableName}( + id bigint NOT NULL, + name varchar + ) ENGINE=OLAP + unique KEY(id) + DISTRIBUTED BY HASH(id) BUCKETS 1 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1" + ); + """ + sql """insert into ${dbName}.${tableName}(id,name)values(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(6,'f');""" + + // Grant privileges to role + sql """GRANT SELECT_PRIV ON ${dbName}.${tableName} TO ROLE '${roleName}'""" + + // Test show grants for role exists + def roleGrants = sql """SHOW GRANTS FOR ROLE '${roleName}'""" + assertTrue(roleGrants.size() == 2) + logger.info("roleGrants: ${roleGrants}") + + def userTablePrivs = "internal.${dbName}.${tableName}: Select_priv" + for (int i = 0; i < roleGrants.size(); i++) { + def grant = roleGrants.get(i) + logger.info("grant: ${grant}") + def userIdentity = grant.get(0) + def roles = grant.get(4) + def tablePrivs = grant.get(8) + logger.info("Roles: ${roles}") + logger.info("UserIdentity: ${userIdentity}") + logger.info("TablePrivs: ${tablePrivs}") + assertTrue(userName1WithRole == userIdentity || userName2WithRole == userIdentity) + assertTrue(roles == roleName) + assertTrue(tablePrivs == userTablePrivs) + } + + // test show grants for none exist role + test { + sql """SHOW GRANTS FOR ROLE '${noneExistRoleName}'""" + exception "Role: ${noneExistRoleName} does not exist" + } + + // test show grants for root role + def rootUserGrants = sql """show grants for 'root'""" + def rootRoleGrants = sql """show grants for role 'operator'""" + assertTrue(rootUserGrants.size() == rootRoleGrants.size()) + for (int i = 0; i < rootUserGrants.size(); i++) { + def rootUserGrant = rootUserGrants.get(0) + def rootRoleGrant = rootRoleGrants.get(0) + for (int j = 0; j < rootUserGrant.size(); j++) { + assertTrue(rootUserGrant.get(j) == rootRoleGrant.get(j)) + } + } + + // test show grants for admin role + def adminUserGrants = sql """show grants for 'admin'""" + def adminRoleGrants = sql """show grants for role 'admin'""" + assertTrue(adminUserGrants.size() == adminRoleGrants.size()) + for (int i = 0; i < adminUserGrants.size(); i++) { + def adminUserGrant = adminUserGrants.get(0) + def adminRoleGrant = adminRoleGrants.get(0) + for (int j = 0; j < adminUserGrant.size(); j++) { + assertTrue(adminUserGrant.get(j) == adminRoleGrant.get(j)) + } + } + + def tokens = context.config.jdbcUrl.split('/') + def infoSchemaJdbcUrl = tokens[0] + "//" + tokens[2] + "/" + "information_schema" + "?" + connect(userWithGrantPriv, "${pwd}", infoSchemaJdbcUrl) { + test { + sql """SHOW GRANTS FOR ROLE ${roleName}""" + exception "Access denied; you need (at least one of) the (GRANT) privilege(s) for this operation" + } + } + """GRANT GRANT_PRIV ON * TO ${userWithGrantPriv}""" + def globalPrivs = sql """show grants for ${userWithGrantPriv}""" + logger.info("globalPrivs: ${globalPrivs}") + connect(userWithGrantPriv, "${pwd}", infoSchemaJdbcUrl) { + test { + sql """SHOW GRANTS FOR ROLE ${roleName}""" + } + } + + // Cleanup + try_sql("DROP ROLE if exists ${roleName}") + try_sql("DROP ROLE if exists ${noneExistRoleName}") + try_sql("DROP USER if exists ${userName1WithRole}") + try_sql("DROP USER if exists ${userName2WithRole}") + try_sql("DROP USER if exists ${userWithGrantPriv}") +}