diff --git a/docs/documentation/cn/administrator-guide/privilege.md b/docs/documentation/cn/administrator-guide/privilege.md index a59c6754f0eabe..33eaebaf19f0d6 100644 --- a/docs/documentation/cn/administrator-guide/privilege.md +++ b/docs/documentation/cn/administrator-guide/privilege.md @@ -37,9 +37,9 @@ Doris 新的权限管理系统参照了 Mysql 的权限管理机制,做到了 9. 查看已创建的角色:SHOW ROELS 10. 查看用户属性:SHOW PROPERTY -关于以上命令的详细帮助,可以通过 mysql 客户端连接 Doris 后,使用 help + command 获取帮助。如 `help create user`。 +关于以上命令的详细帮助,可以通过 mysql 客户端连接 Doris 后,使用 help + command 获取帮助。如 `HELP CREATE USER`。 -## 权限说明 +## 权限类型 Doris 目前支持以下几种权限 @@ -61,15 +61,53 @@ Doris 目前支持以下几种权限 5. Alter_priv - 对数据库、表的更改权限。包括重命名 库/表、添加/删除/变更 列等操作。 + 对数据库、表的更改权限。包括重命名 库/表、添加/删除/变更 列、添加/删除 分区等操作。 6. Create_priv - 创建数据库、表的权限。 + 创建数据库、表、视图的权限。 7. Drop_priv - 删除数据库、表的权限。 + 删除数据库、表、视图的权限。 + +## 权限层级 + +同时,根据权限适用范围的不同,我们将权限分为以下三个层级: + +1. GLOBAL LEVEL:全局权限。即通过 GRANT 语句授予的 `*.*` 上的权限。被授予的权限适用于任意数据库中的任意表。 +2. DATABASE LEVEL:数据库级权限。即通过 GRANT 语句授予的 `db.*` 上的权限。被授予的权限适用于指定数据库中的任意表。 +3. TABLE LEVEL:表级权限。即通过 GRANT 语句授予的 `db.tbl` 上的权限。被授予的权限适用于指定数据库中的指定表。 + + +## ADMIN/GRANT 权限说明 + +ADMIN\_PRIV 和 GRANT\_PRIV 权限同时拥有**“授予权限”**的权限,较为特殊。这里对和这两个权限相关的操作逐一说明。 + +1. CREATE USER + + * 拥有 ADMIN 权限,或任意层级的 GRANT 权限的用户可以创建新用户。 + +2. DROP USER + + * 只有 ADMIN 权限可以删除用户。 + +3. CREATE/DROP ROLE + + * 只有 ADMIN 权限可以创建角色。 + +4. GRANT/REVOKE + + * 拥有 ADMIN 权限,或者 GLOBAL 层级 GRANT 权限的用户,可以授予或撤销任意用户的权限。 + * 拥有 DATABASE 层级 GRANT 权限的用户,可以授予或撤销任意用户对指定数据库的权限。 + * 拥有 TABLE 层级 GRANT 权限的用户,可以授予或撤销任意用户对指定数据库中指定表的权限。 + +5. SET PASSWORD + + * 拥有 ADMIN 权限,或者 GLOBAL 层级 GRANT 权限的用户,可以设置任意用户的密码。 + * 普通用户可以设置自己对应的 UserIdentity 的密码。自己对应的 UserIdentity 可以通过 `SELECT CURRENT_USER();` 命令查看。 + * 拥有非 GLOBAL 层级 GRANT 权限的用户,不可以设置已存在用户的密码,仅能在创建用户时指定密码。 + ## 一些说明 @@ -116,8 +154,6 @@ Doris 目前支持以下几种权限 CREATE USER cmy@'192.%' IDENTIFIED BY "abcde"; 在优先级上,'192.%' 优先于 '%',因此,当用户 cmy 从 192.168.1.1 这台机器尝试使用密码 '12345' 登陆 Doris 会被拒绝。 - - 3. 权限冲突 5. 忘记密码 @@ -129,6 +165,24 @@ Doris 目前支持以下几种权限 6. 任何用户都不能重置 root 用户的密码,除了 root 用户自己。 -7. 拥有 GRANT 权限的用户可以设置密码。如果没有 GRANT 用户,则用户仅可以设置自己对应的 UserIdentity 的密码。自己对应的 UserIdentity 可以通过 `SELECT CURRENT_USER();` 命令查看。 +7. ADMIN\_PRIV 权限只能在 GLOBAL 层级授予或撤销。 + +8. 拥有 GLOBAL 层级 GRANT_PRIV 其实等同于拥有 ADMIN\_PRIV,因为该层级的 GRANT\_PRIV 有授予任意权限的权限,请谨慎使用。 + +## 最佳实践 + +这里举例一些 Doris 权限系统的使用场景。 + +1. 场景一 -8. ADMIN\_PRIV 和 GRANT\_PRIV 权限只能 `GRANT ON *.*` 或 `REVOKE FROM *.*`。因为对于指定的库和表,这两个权限没有意义。同时,拥有 GRANT_PRIV 其实等同于拥有 ADMIN\_PRIV,因为 GRANT\_PRIV 有授予任意权限的权限,请谨慎使用。 + Doris 集群的使用者分为管理员(Admin)、开发工程师(RD)和用户(Client)。其中管理员拥有整个集群的所有权限,主要负责集群的搭建、节点管理等。开发工程师负责业务建模,包括建库建表、数据的导入和修改等。用户访问不同的数据库和表来获取数据。 + + 在这种场景下,可以为管理员赋予 ADMIN 权限或 GRANT 权限。对 RD 赋予对任意或指定数据库表的 CREATE、DROP、ALTER、LOAD、SELECT 权限。对 Client 赋予对任意或指定数据库表 SELECT 权限。同时,也可以通过创建不同的角色,来简化对多个用户的授权操作。 + +2. 场景二 + + 一个集群内有多个业务,每个业务可能使用一个或多个数据。每个业务需要管理自己的用户。在这种场景下。管理员用户可以为每个数据库创建一个拥有 DATABASE 层级 GRANT 权限的用户。该用户仅可以对用户进行指定的数据库的授权。 + + + + diff --git a/docs/help/Contents/Account Management/help.md b/docs/help/Contents/Account Management/help.md index 4a70ffe89636d9..c3b81a6ee40d6f 100644 --- a/docs/help/Contents/Account Management/help.md +++ b/docs/help/Contents/Account Management/help.md @@ -109,6 +109,7 @@ privilege_list 是需要赋予的权限列表,以逗号分隔。当前 Doris NODE_PRIV:集群节点操作权限,包括节点上下线等操作,只有 root 用户有该权限,不可赋予其他用户。 ADMIN_PRIV:除 NODE_PRIV 以外的所有权限。 + GRANT_PRIV: 操作权限的权限。包括创建删除用户、角色,授权和撤权,设置密码等。 SELECT_PRIV:对指定的库或表的读取权限 LOAD_PRIV:对指定的库或表的导入权限 ALTER_PRIV:对指定的库或表的schema变更权限 diff --git a/fe/src/main/java/org/apache/doris/analysis/CreateRoleStmt.java b/fe/src/main/java/org/apache/doris/analysis/CreateRoleStmt.java index eb87457d6d6b40..a35e10eb5b6db4 100644 --- a/fe/src/main/java/org/apache/doris/analysis/CreateRoleStmt.java +++ b/fe/src/main/java/org/apache/doris/analysis/CreateRoleStmt.java @@ -17,9 +17,14 @@ package org.apache.doris.analysis; +import org.apache.doris.catalog.Catalog; import org.apache.doris.cluster.ClusterNamespace; +import org.apache.doris.common.ErrorCode; +import org.apache.doris.common.ErrorReport; import org.apache.doris.common.FeNameFormat; import org.apache.doris.common.UserException; +import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.qe.ConnectContext; public class CreateRoleStmt extends DdlStmt { @@ -38,6 +43,11 @@ public void analyze(Analyzer analyzer) throws UserException { super.analyze(analyzer); FeNameFormat.checkRoleName(role, false /* can not be admin */, "Can not create role"); role = ClusterNamespace.getFullName(analyzer.getClusterName(), role); + + // check if current user has GRANT priv on GLOBAL level. + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "CREATE USER"); + } } @Override diff --git a/fe/src/main/java/org/apache/doris/analysis/CreateUserStmt.java b/fe/src/main/java/org/apache/doris/analysis/CreateUserStmt.java index fbc0e043c0c825..ed9e1015197e2d 100644 --- a/fe/src/main/java/org/apache/doris/analysis/CreateUserStmt.java +++ b/fe/src/main/java/org/apache/doris/analysis/CreateUserStmt.java @@ -19,12 +19,12 @@ import org.apache.doris.catalog.Catalog; import org.apache.doris.cluster.ClusterNamespace; -import org.apache.doris.common.AnalysisException; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; import org.apache.doris.common.FeNameFormat; import org.apache.doris.common.UserException; import org.apache.doris.mysql.MysqlPassword; +import org.apache.doris.mysql.privilege.PaloAuth.PrivLevel; import org.apache.doris.mysql.privilege.PaloRole; import org.apache.doris.mysql.privilege.PrivPredicate; import org.apache.doris.qe.ConnectContext; @@ -105,7 +105,7 @@ public boolean needAuditEncryption() { } @Override - public void analyze(Analyzer analyzer) throws AnalysisException, UserException { + public void analyze(Analyzer analyzer) throws UserException { super.analyze(analyzer); userIdent.analyze(analyzer.getClusterName()); // convert plain password to hashed password @@ -129,8 +129,9 @@ public void analyze(Analyzer analyzer) throws AnalysisException, UserException { role = ClusterNamespace.getFullName(analyzer.getClusterName(), role); } - if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "CREATE USER"); + // check if current user has GRANT priv on GLOBAL or DATABASE level. + if (!Catalog.getCurrentCatalog().getAuth().checkHasPriv(ConnectContext.get(), PrivPredicate.GRANT, PrivLevel.GLOBAL, PrivLevel.DATABASE)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT"); } } diff --git a/fe/src/main/java/org/apache/doris/analysis/DropRoleStmt.java b/fe/src/main/java/org/apache/doris/analysis/DropRoleStmt.java index 1ff5f425e7cc55..2852b0a2f18f12 100644 --- a/fe/src/main/java/org/apache/doris/analysis/DropRoleStmt.java +++ b/fe/src/main/java/org/apache/doris/analysis/DropRoleStmt.java @@ -17,9 +17,14 @@ package org.apache.doris.analysis; +import org.apache.doris.catalog.Catalog; import org.apache.doris.cluster.ClusterNamespace; +import org.apache.doris.common.ErrorCode; +import org.apache.doris.common.ErrorReport; import org.apache.doris.common.FeNameFormat; import org.apache.doris.common.UserException; +import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.qe.ConnectContext; public class DropRoleStmt extends DdlStmt { @@ -38,6 +43,11 @@ public void analyze(Analyzer analyzer) throws UserException { super.analyze(analyzer); FeNameFormat.checkRoleName(role, false /* can not be superuser */, "Can not drop role"); role = ClusterNamespace.getFullName(analyzer.getClusterName(), role); + + // check if current user has GRANT priv on GLOBAL level. + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "CREATE USER"); + } } @Override diff --git a/fe/src/main/java/org/apache/doris/analysis/DropUserStmt.java b/fe/src/main/java/org/apache/doris/analysis/DropUserStmt.java index a2431ade0cfcb5..29712678d6c1b2 100644 --- a/fe/src/main/java/org/apache/doris/analysis/DropUserStmt.java +++ b/fe/src/main/java/org/apache/doris/analysis/DropUserStmt.java @@ -46,10 +46,9 @@ public void analyze(Analyzer analyzer) throws AnalysisException, UserException { throw new AnalysisException("Can not drop user with specified host: " + userIdent.getHost()); } - // check authenticate + // only user with GLOBAL level's GRANT_PRIV can drop user. if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, - "DROP USER"); + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "DROP USER"); } } diff --git a/fe/src/main/java/org/apache/doris/analysis/GrantStmt.java b/fe/src/main/java/org/apache/doris/analysis/GrantStmt.java index c4e5dcc9b66f1a..be332c31111287 100644 --- a/fe/src/main/java/org/apache/doris/analysis/GrantStmt.java +++ b/fe/src/main/java/org/apache/doris/analysis/GrantStmt.java @@ -37,8 +37,6 @@ import java.util.List; // GRANT STMT -// grant privilege to some user, this is an administrator operation. -// // GRANT privilege [, privilege] ON db.tbl TO user [ROLE 'role']; public class GrantStmt extends DdlStmt { private UserIdentity userIdent; @@ -83,7 +81,8 @@ public void analyze(Analyzer analyzer) throws AnalysisException, UserException { if (userIdent != null) { userIdent.analyze(analyzer.getClusterName()); } else { - FeNameFormat.checkUserName(role); + FeNameFormat.checkRoleName(role, false /* can not be admin */, "Can not grant to role"); + role = ClusterNamespace.getFullName(analyzer.getClusterName(), role); } tblPattern.analyze(analyzer.getClusterName()); @@ -92,32 +91,51 @@ public void analyze(Analyzer analyzer) throws AnalysisException, UserException { throw new AnalysisException("No privileges in grant statement."); } - // can not grant NODE_PRIV to any other user(root has NODE_PRIV, no need to grant) - for (PaloPrivilege paloPrivilege : privileges) { - if (paloPrivilege == PaloPrivilege.NODE_PRIV) { - throw new AnalysisException("Can not grant NODE_PRIV to any other users or roles"); - } - } + checkPrivileges(analyzer, privileges, role, tblPattern); + } - // ADMIN_PRIV and GRANT_PRIV can only be granted as global - if (tblPattern.getPrivLevel() != PrivLevel.GLOBAL) { - for (PaloPrivilege paloPrivilege : privileges) { - if (paloPrivilege == PaloPrivilege.ADMIN_PRIV || paloPrivilege == PaloPrivilege.GRANT_PRIV) { - throw new AnalysisException( - "Can not grant ADMIN_PRIV or GRANT_PRIV to specified database or table. Only support to *.*"); - } - } + /* + * Rules: + * 1. Can not grant/revoke NODE_PRIV to/from any other user. + * 2. ADMIN_PRIV can only be granted/revoked on GLOBAL level + * 3. Privileges can not be granted/revoked to/from ADMIN and OPERATOR role + * 4. Only user with GLOBAL level's GRANT_PRIV can grant/revoke privileges to/from roles. + * 5.1 User should has GLOBAL level GRANT_PRIV + * 5.2 or user has DATABASE/TABLE level GRANT_PRIV if grant/revoke to/from certain database or table. + */ + public static void checkPrivileges(Analyzer analyzer, List privileges, + String role, TablePattern tblPattern) throws AnalysisException { + // Rule 1 + if (privileges.contains(PaloPrivilege.NODE_PRIV)) { + throw new AnalysisException("Can not grant NODE_PRIV to any other users or roles"); } - if (role != null) { - // can not grant to admin or operator role - FeNameFormat.checkRoleName(role, false /* can not be admin */, "Can not grant to role"); - role = ClusterNamespace.getFullName(analyzer.getClusterName(), role); + // Rule 2 + if (tblPattern.getPrivLevel() != PrivLevel.GLOBAL && privileges.contains(PaloPrivilege.ADMIN_PRIV)) { + throw new AnalysisException("ADMIN_PRIV privilege can only be granted on *.*"); } - if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, - "GRANT"); + if (role != null) { + // Rule 3 and 4 + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT"); + } + } else { + // Rule 5.1 and 5.2 + if (tblPattern.getPrivLevel() == PrivLevel.GLOBAL) { + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT"); + } + } else if (tblPattern.getPrivLevel() == PrivLevel.DATABASE){ + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), tblPattern.getQuolifiedDb(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT"); + } + } else { + // table level + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), tblPattern.getQuolifiedDb(), tblPattern.getTbl(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT"); + } + } } } diff --git a/fe/src/main/java/org/apache/doris/analysis/RevokeStmt.java b/fe/src/main/java/org/apache/doris/analysis/RevokeStmt.java index 66b54f78ae2e79..5c7edb341b2173 100644 --- a/fe/src/main/java/org/apache/doris/analysis/RevokeStmt.java +++ b/fe/src/main/java/org/apache/doris/analysis/RevokeStmt.java @@ -18,17 +18,11 @@ package org.apache.doris.analysis; import org.apache.doris.catalog.AccessPrivilege; -import org.apache.doris.catalog.Catalog; import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.AnalysisException; -import org.apache.doris.common.ErrorCode; -import org.apache.doris.common.ErrorReport; import org.apache.doris.common.FeNameFormat; -import org.apache.doris.mysql.privilege.PaloAuth.PrivLevel; import org.apache.doris.mysql.privilege.PaloPrivilege; import org.apache.doris.mysql.privilege.PrivBitSet; -import org.apache.doris.mysql.privilege.PrivPredicate; -import org.apache.doris.qe.ConnectContext; import com.google.common.base.Joiner; import com.google.common.base.Strings; @@ -87,27 +81,8 @@ public void analyze(Analyzer analyzer) throws AnalysisException { throw new AnalysisException("No privileges in revoke statement."); } - // can not revoke NODE_PRIV from any user - for (PaloPrivilege paloPrivilege : privileges) { - if (paloPrivilege == PaloPrivilege.NODE_PRIV) { - throw new AnalysisException("Can not revoke NODE_PRIV from any users or roles"); - } - } - - // ADMIN_PRIV and GRANT_PRIV can only be revoked as global - if (tblPattern.getPrivLevel() != PrivLevel.GLOBAL) { - for (PaloPrivilege paloPrivilege : privileges) { - if (paloPrivilege == PaloPrivilege.ADMIN_PRIV || paloPrivilege == PaloPrivilege.GRANT_PRIV) { - throw new AnalysisException( - "Can not revoke ADMIN_PRIV or GRANT_PRIV from specified database or table. Only support from *.*"); - } - } - } - - if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, - "REVOKE"); - } + // Revoke operation obey the same rule as Grant operation. reuse the same method + GrantStmt.checkPrivileges(analyzer, privileges, role, tblPattern); } @Override diff --git a/fe/src/main/java/org/apache/doris/mysql/privilege/DbPrivEntry.java b/fe/src/main/java/org/apache/doris/mysql/privilege/DbPrivEntry.java index 725042bae966b1..c29eeafd0570ce 100644 --- a/fe/src/main/java/org/apache/doris/mysql/privilege/DbPrivEntry.java +++ b/fe/src/main/java/org/apache/doris/mysql/privilege/DbPrivEntry.java @@ -56,7 +56,7 @@ public static DbPrivEntry create(String host, String db, String user, PrivBitSet PatternMatcher userPattern = PatternMatcher.createMysqlPattern(user, CaseSensibility.USER.getCaseSensibility()); - if (privs.containsNodeOrGrantPriv()) { + if (privs.containsNodePriv()) { throw new AnalysisException("Db privilege can not contains global privileges: " + privs); } diff --git a/fe/src/main/java/org/apache/doris/mysql/privilege/DbPrivTable.java b/fe/src/main/java/org/apache/doris/mysql/privilege/DbPrivTable.java index bbe56e5d8754b7..1beb115a3c4b90 100644 --- a/fe/src/main/java/org/apache/doris/mysql/privilege/DbPrivTable.java +++ b/fe/src/main/java/org/apache/doris/mysql/privilege/DbPrivTable.java @@ -32,6 +32,10 @@ public class DbPrivTable extends PrivTable { private static final Logger LOG = LogManager.getLogger(DbPrivTable.class); + /* + * Return first priv which match the user@host on db.* The returned priv will be + * saved in 'savedPrivs'. + */ public void getPrivs(String host, String db, String user, PrivBitSet savedPrivs) { DbPrivEntry matchedEntry = null; for (PrivEntry entry : entries) { @@ -62,6 +66,28 @@ public void getPrivs(String host, String db, String user, PrivBitSet savedPrivs) savedPrivs.or(matchedEntry.getPrivSet()); } + /* + * Check if user@host has specified privilege on any database + */ + public boolean hasPriv(String host, String user, PrivPredicate wanted) { + for (PrivEntry entry : entries) { + DbPrivEntry dbPrivEntry = (DbPrivEntry) entry; + // check host + if (!dbPrivEntry.isAnyHost() && !dbPrivEntry.getHostPattern().match(host)) { + continue; + } + // check user + if (!dbPrivEntry.isAnyUser() && !dbPrivEntry.getUserPattern().match(user)) { + continue; + } + // check priv + if (dbPrivEntry.privSet.satisfy(wanted)) { + return true; + } + } + return false; + } + public boolean hasClusterPriv(ConnectContext ctx, String clusterName) { for (PrivEntry entry : entries) { DbPrivEntry dbPrivEntry = (DbPrivEntry) entry; diff --git a/fe/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java b/fe/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java index d63f630447d85e..42403cc7c9cec0 100644 --- a/fe/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java +++ b/fe/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java @@ -255,12 +255,16 @@ public boolean checkDbPriv(ConnectContext ctx, String qualifiedDb, PrivPredicate return checkDbPriv(ctx.getRemoteIP(), qualifiedDb, ctx.getQualifiedUser(), 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(String host, String db, String user, PrivPredicate wanted) { if (!Config.enable_auth_check) { return true; } - if (wanted.getPrivs().containsNodeOrGrantPriv()) { - LOG.debug("should be check NODE or GRANT priv in Global level. host: {}, user: {}, db: {}", + if (wanted.getPrivs().containsNodePriv()) { + LOG.debug("should not check NODE priv in Database level. host: {}, user: {}, db: {}", host, user, db); return false; } @@ -272,7 +276,7 @@ public boolean checkDbPriv(String host, String db, String user, PrivPredicate wa } // if user has any privs of table in this db, and the wanted priv is SHOW, return true - if (wanted == PrivPredicate.SHOW && checkTblWithDb(host, db, user)) { + if (db != null && wanted == PrivPredicate.SHOW && checkTblWithDb(host, db, user)) { return true; } @@ -302,8 +306,8 @@ public boolean checkTblPriv(String host, String db, String user, String tbl, Pri if (!Config.enable_auth_check) { return true; } - if (wanted.getPrivs().containsNodeOrGrantPriv()) { - LOG.debug("should be check NODE or GRANT priv in Db level. host: {}, user: {}, db: {}", + if (wanted.getPrivs().containsNodePriv()) { + LOG.debug("should check NODE priv in GLOBAL level. host: {}, user: {}, db: {}", host, user, db); return false; } @@ -338,6 +342,38 @@ public boolean checkPrivByAuthInfo(ConnectContext ctx, AuthorizationInfo authInf 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) { + return checkHasPrivInternal(ctx.getRemoteIP(), ctx.getQualifiedUser(), priv, levels); + } + + private boolean checkHasPrivInternal(String host, String user, PrivPredicate priv, PrivLevel... levels) { + for (PrivLevel privLevel : levels) { + switch (privLevel) { + case GLOBAL: + if (userPrivTable.hasPriv(host, user, priv)) { + return true; + } + case DATABASE: + if (dbPrivTable.hasPriv(host, user, priv)) { + return true; + } + break; + case TABLE: + if (tablePrivTable.hasPriv(host, user, priv)) { + return true; + } + break; + default: + break; + } + } + return false; + } + private boolean checkGlobalInternal(String host, String user, PrivPredicate wanted, PrivBitSet savedPrivs) { readLock(); try { diff --git a/fe/src/main/java/org/apache/doris/mysql/privilege/PaloPrivilege.java b/fe/src/main/java/org/apache/doris/mysql/privilege/PaloPrivilege.java index 78f679e13aed3d..25895c9e3a0d02 100644 --- a/fe/src/main/java/org/apache/doris/mysql/privilege/PaloPrivilege.java +++ b/fe/src/main/java/org/apache/doris/mysql/privilege/PaloPrivilege.java @@ -20,14 +20,13 @@ public enum PaloPrivilege { NODE_PRIV("Node_priv", 0, "Privilege for cluster node operations"), ADMIN_PRIV("Admin_priv", 1, "Privilege for admin user"), - GRANT_PRIV("Grant_priv", 2, "Privilege for granting privlege"), + GRANT_PRIV("Grant_priv", 2, "Privilege for granting privilege"), SELECT_PRIV("Select_priv", 3, "Privilege for select data in tables"), LOAD_PRIV("Load_priv", 4, "Privilege for loading data into tables"), ALTER_PRIV("Alter_priv", 5, "Privilege for alter database or table"), CREATE_PRIV("Create_priv", 6, "Privilege for createing database or table"), DROP_PRIV("Drop_priv", 7, "Privilege for dropping database or table"); - public static PaloPrivilege[] privileges = { NODE_PRIV, ADMIN_PRIV, diff --git a/fe/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java b/fe/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java index 3c9055e3e41c84..e53f843cc0eb3a 100644 --- a/fe/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java +++ b/fe/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java @@ -83,11 +83,10 @@ public boolean satisfy(PrivPredicate wantPrivs) { } else { return (set & wantPrivs.getPrivs().set) != 0; } - } - - public boolean containsNodeOrGrantPriv() { - return containsPrivs(PaloPrivilege.NODE_PRIV, PaloPrivilege.GRANT_PRIV); + + public boolean containsNodePriv() { + return containsPrivs(PaloPrivilege.NODE_PRIV); } public boolean containsPrivs(PaloPrivilege... privs) { diff --git a/fe/src/main/java/org/apache/doris/mysql/privilege/TablePrivEntry.java b/fe/src/main/java/org/apache/doris/mysql/privilege/TablePrivEntry.java index bfe839527e5d66..a9a071a88b16b7 100644 --- a/fe/src/main/java/org/apache/doris/mysql/privilege/TablePrivEntry.java +++ b/fe/src/main/java/org/apache/doris/mysql/privilege/TablePrivEntry.java @@ -56,7 +56,7 @@ public static TablePrivEntry create(String host, String db, String user, String PatternMatcher tblPattern = PatternMatcher.createMysqlPattern(tbl.equals(ANY_TBL) ? "%" : tbl, CaseSensibility.TABLE.getCaseSensibility()); - if (privs.containsNodeOrGrantPriv()) { + if (privs.containsNodePriv()) { throw new AnalysisException("Table privilege can not contains global privileges: " + privs); } diff --git a/fe/src/main/java/org/apache/doris/mysql/privilege/TablePrivTable.java b/fe/src/main/java/org/apache/doris/mysql/privilege/TablePrivTable.java index eaee241527e5b6..5cbfcf83e2e310 100644 --- a/fe/src/main/java/org/apache/doris/mysql/privilege/TablePrivTable.java +++ b/fe/src/main/java/org/apache/doris/mysql/privilege/TablePrivTable.java @@ -30,6 +30,10 @@ */ public class TablePrivTable extends PrivTable { + /* + * Return first priv which match the user@host on db.tbl The returned priv will + * be saved in 'savedPrivs'. + */ public void getPrivs(String host, String db, String user, String tbl, PrivBitSet savedPrivs) { TablePrivEntry matchedEntry = null; for (PrivEntry entry : entries) { @@ -66,6 +70,28 @@ public void getPrivs(String host, String db, String user, String tbl, PrivBitSet savedPrivs.or(matchedEntry.getPrivSet()); } + /* + * Check if user@host has specified privilege on any table + */ + public boolean hasPriv(String host, String user, PrivPredicate wanted) { + for (PrivEntry entry : entries) { + TablePrivEntry tblPrivEntry = (TablePrivEntry) entry; + // check host + if (!tblPrivEntry.isAnyHost() && !tblPrivEntry.getHostPattern().match(host)) { + continue; + } + // check user + if (!tblPrivEntry.isAnyUser() && !tblPrivEntry.getUserPattern().match(user)) { + continue; + } + // check priv + if (tblPrivEntry.privSet.satisfy(wanted)) { + return true; + } + } + return false; + } + public boolean hasPrivsOfDb(String host, String db, String user) { for (PrivEntry entry : entries) { TablePrivEntry tblPrivEntry = (TablePrivEntry) entry; diff --git a/fe/src/main/java/org/apache/doris/mysql/privilege/UserPrivTable.java b/fe/src/main/java/org/apache/doris/mysql/privilege/UserPrivTable.java index cc3da9bfefc9dd..bf7530eeb3f5e7 100644 --- a/fe/src/main/java/org/apache/doris/mysql/privilege/UserPrivTable.java +++ b/fe/src/main/java/org/apache/doris/mysql/privilege/UserPrivTable.java @@ -63,6 +63,27 @@ public void getPrivs(String host, String user, PrivBitSet savedPrivs) { savedPrivs.or(matchedEntry.getPrivSet()); } + /* + * Check if user@host has specified privilege + */ + public boolean hasPriv(String host, String user, PrivPredicate wanted) { + for (PrivEntry entry : entries) { + GlobalPrivEntry globalPrivEntry = (GlobalPrivEntry) entry; + // check host + if (!globalPrivEntry.isAnyHost() && !globalPrivEntry.getHostPattern().match(host)) { + continue; + } + // check user + if (!globalPrivEntry.isAnyUser() && !globalPrivEntry.getUserPattern().match(user)) { + continue; + } + if (globalPrivEntry.getPrivSet().satisfy(wanted)) { + return true; + } + } + return false; + } + // validate the connection by host, user and password. // return true if this connection is valid, and 'savedPrivs' save all global privs got from user table. // if currentUser is not null, save the current user identity diff --git a/fe/src/test/java/org/apache/doris/analysis/CreateUserStmtTest.java b/fe/src/test/java/org/apache/doris/analysis/CreateUserStmtTest.java index c740f1aa78a87b..a21ffde426bf9d 100644 --- a/fe/src/test/java/org/apache/doris/analysis/CreateUserStmtTest.java +++ b/fe/src/test/java/org/apache/doris/analysis/CreateUserStmtTest.java @@ -19,38 +19,38 @@ import org.apache.doris.common.AnalysisException; import org.apache.doris.common.UserException; -import org.apache.doris.mysql.privilege.MockedAuth; -import org.apache.doris.mysql.privilege.PaloAuth; import org.apache.doris.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import mockit.Mocked; -import mockit.internal.startup.Startup; +import mockit.Expectations; +import mockit.Injectable; public class CreateUserStmtTest { - private Analyzer analyzer; - - @Mocked - private PaloAuth auth; - @Mocked - private ConnectContext ctx; - - static { - Startup.initializeIfPossible(); - } @Before public void setUp() { - analyzer = AccessTestUtil.fetchAdminAnalyzer(true); - MockedAuth.mockedAuth(auth); - MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); + ConnectContext ctx = new ConnectContext(null); + ctx.setQualifiedUser("root"); + ctx.setRemoteIP("192.168.1.1"); + UserIdentity currentUserIdentity = new UserIdentity("root", "192.168.1.1"); + currentUserIdentity.setIsAnalyzed(); + ctx.setCurrentUserIdentitfy(currentUserIdentity); + ctx.setThreadLocalInfo(); } @Test - public void testToString() throws UserException, AnalysisException { + public void testToString(@Injectable Analyzer analyzer) throws UserException, AnalysisException { + + new Expectations() { + { + analyzer.getClusterName(); + result = "testCluster"; + } + }; + CreateUserStmt stmt = new CreateUserStmt(new UserDesc(new UserIdentity("user", "%"), "passwd", true)); stmt.analyze(analyzer); @@ -74,14 +74,26 @@ public void testToString() throws UserException, AnalysisException { } @Test(expected = AnalysisException.class) - public void testEmptyUser() throws UserException, AnalysisException { + public void testEmptyUser(@Injectable Analyzer analyzer) throws UserException, AnalysisException { + new Expectations() { + { + analyzer.getClusterName(); + result = "testCluster"; + } + }; CreateUserStmt stmt = new CreateUserStmt(new UserDesc(new UserIdentity("", "%"), "passwd", true)); stmt.analyze(analyzer); Assert.fail("No exception throws."); } @Test(expected = AnalysisException.class) - public void testBadPass() throws UserException, AnalysisException { + public void testBadPass(@Injectable Analyzer analyzer) throws UserException, AnalysisException { + new Expectations() { + { + analyzer.getClusterName(); + result = "testCluster"; + } + }; CreateUserStmt stmt = new CreateUserStmt(new UserDesc(new UserIdentity("", "%"), "passwd", false)); stmt.analyze(analyzer); Assert.fail("No exception throws."); diff --git a/fe/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java b/fe/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java index 2f1245086462f2..2af532ec59e533 100644 --- a/fe/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java +++ b/fe/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java @@ -146,16 +146,16 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo // 1. create cmy@% UserIdentity userIdentity = new UserIdentity("cmy", "%"); UserDesc userDesc = new UserDesc(userIdentity, "12345", true); - CreateUserStmt userStmt = new CreateUserStmt(false, userDesc, null); + CreateUserStmt createUserStmt = new CreateUserStmt(false, userDesc, null); try { - userStmt.analyze(analyzer); + createUserStmt.analyze(analyzer); } catch (UserException e) { e.printStackTrace(); Assert.fail(); } try { - auth.createUser(userStmt); + auth.createUser(createUserStmt); } catch (DdlException e) { Assert.fail(); } @@ -169,16 +169,16 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo // 3. create another user: zhangsan@"192.%" userIdentity = new UserIdentity("zhangsan", "192.%"); userDesc = new UserDesc(userIdentity, "12345", true); - userStmt = new CreateUserStmt(false, userDesc, null); + createUserStmt = new CreateUserStmt(false, userDesc, null); try { - userStmt.analyze(analyzer); + createUserStmt.analyze(analyzer); } catch (UserException e) { e.printStackTrace(); Assert.fail(); } try { - auth.createUser(userStmt); + auth.createUser(createUserStmt); } catch (DdlException e) { Assert.fail(); } @@ -193,9 +193,9 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo // 4.1 check if we can create same user userIdentity = new UserIdentity("zhangsan", "192.%"); userDesc = new UserDesc(userIdentity, "12345", true); - userStmt = new CreateUserStmt(false, userDesc, null); + createUserStmt = new CreateUserStmt(false, userDesc, null); try { - userStmt.analyze(analyzer); + createUserStmt.analyze(analyzer); } catch (UserException e) { e.printStackTrace(); Assert.fail(); @@ -203,7 +203,7 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo boolean hasException = false; try { - auth.createUser(userStmt); + auth.createUser(createUserStmt); } catch (DdlException e) { e.printStackTrace(); hasException = true; @@ -213,16 +213,16 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo // 4.2 check if we can create same user name with different host userIdentity = new UserIdentity("zhangsan", "172.18.1.1"); userDesc = new UserDesc(userIdentity, "12345", true); - userStmt = new CreateUserStmt(false, userDesc, null); + createUserStmt = new CreateUserStmt(false, userDesc, null); try { - userStmt.analyze(analyzer); + createUserStmt.analyze(analyzer); } catch (UserException e) { e.printStackTrace(); Assert.fail(); } try { - auth.createUser(userStmt); + auth.createUser(createUserStmt); } catch (DdlException e) { Assert.fail(); } @@ -232,15 +232,15 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo // 5. create a user with domain [palo.domain] userIdentity = new UserIdentity("zhangsan", "palo.domain1", true); userDesc = new UserDesc(userIdentity, "12345", true); - userStmt = new CreateUserStmt(false, userDesc, null); + createUserStmt = new CreateUserStmt(false, userDesc, null); try { - userStmt.analyze(analyzer); + createUserStmt.analyze(analyzer); } catch (UserException e) { e.printStackTrace(); Assert.fail(); } try { - auth.createUser(userStmt); + auth.createUser(createUserStmt); } catch (DdlException e) { Assert.fail(); } @@ -259,9 +259,9 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo // 7. add duplicated user@['palo.domain1'] userIdentity = new UserIdentity("zhangsan", "palo.domain1", true); userDesc = new UserDesc(userIdentity, "12345", true); - userStmt = new CreateUserStmt(false, userDesc, null); + createUserStmt = new CreateUserStmt(false, userDesc, null); try { - userStmt.analyze(analyzer); + createUserStmt.analyze(analyzer); } catch (UserException e) { e.printStackTrace(); Assert.fail(); @@ -269,7 +269,7 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo hasException = false; try { - auth.createUser(userStmt); + auth.createUser(createUserStmt); } catch (DdlException e) { e.printStackTrace(); hasException = true; @@ -279,16 +279,16 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo // 8. add another user@['palo.domain2'] userIdentity = new UserIdentity("lisi", "palo.domain2", true); userDesc = new UserDesc(userIdentity, "123456", true); - userStmt = new CreateUserStmt(false, userDesc, null); + createUserStmt = new CreateUserStmt(false, userDesc, null); try { - userStmt.analyze(analyzer); + createUserStmt.analyze(analyzer); } catch (UserException e) { e.printStackTrace(); Assert.fail(); } try { - auth.createUser(userStmt); + auth.createUser(createUserStmt); } catch (DdlException e) { e.printStackTrace(); Assert.fail(); @@ -757,7 +757,7 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo Assert.assertTrue(hasException); // 24. create role - roleStmt = new CreateRoleStmt("rolo1"); + roleStmt = new CreateRoleStmt("role1"); try { roleStmt.analyze(analyzer); } catch (UserException e1) { @@ -808,9 +808,9 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo // 27. create user and set it as role1 userIdentity = new UserIdentity("wangwu", "%"); userDesc = new UserDesc(userIdentity, "12345", true); - userStmt = new CreateUserStmt(false, userDesc, "role1"); + createUserStmt = new CreateUserStmt(false, userDesc, "role1"); try { - userStmt.analyze(analyzer); + createUserStmt.analyze(analyzer); } catch (UserException e) { e.printStackTrace(); Assert.fail(); @@ -820,8 +820,9 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo SystemInfoService.DEFAULT_CLUSTER + ":wangwu", PrivPredicate.DROP)); try { - auth.createUser(userStmt); + auth.createUser(createUserStmt); } catch (DdlException e) { + e.printStackTrace(); Assert.fail(); } Assert.assertTrue(auth.checkDbPriv("10.17.2.1", SystemInfoService.DEFAULT_CLUSTER + ":db4", @@ -831,16 +832,16 @@ public void test() throws IllegalAccessException, IllegalArgumentException, Invo // 28. create user@domain and set it as role1 userIdentity = new UserIdentity("chenliu", "palo.domain2", true); userDesc = new UserDesc(userIdentity, "12345", true); - userStmt = new CreateUserStmt(false, userDesc, "role1"); + createUserStmt = new CreateUserStmt(false, userDesc, "role1"); try { - userStmt.analyze(analyzer); + createUserStmt.analyze(analyzer); } catch (UserException e) { e.printStackTrace(); Assert.fail(); } try { - auth.createUser(userStmt); + auth.createUser(createUserStmt); } catch (DdlException e) { e.printStackTrace(); Assert.fail(); diff --git a/fe/src/test/java/org/apache/doris/mysql/privilege/MockedAuth.java b/fe/src/test/java/org/apache/doris/mysql/privilege/MockedAuth.java index fefa0758bbe760..a2555997df07f7 100644 --- a/fe/src/test/java/org/apache/doris/mysql/privilege/MockedAuth.java +++ b/fe/src/test/java/org/apache/doris/mysql/privilege/MockedAuth.java @@ -36,6 +36,8 @@ public static void mockedAuth(PaloAuth auth) { auth.checkTblPriv((ConnectContext) any, anyString, anyString, (PrivPredicate) any); result = true; + + // auth.checkHasPriv((ConnectContext) any,, priv, levels) } }; } diff --git a/fe/src/test/java/org/apache/doris/mysql/privilege/PrivTest.java b/fe/src/test/java/org/apache/doris/mysql/privilege/PrivTest.java index d0b4de07a2c32a..25de048f04db0b 100644 --- a/fe/src/test/java/org/apache/doris/mysql/privilege/PrivTest.java +++ b/fe/src/test/java/org/apache/doris/mysql/privilege/PrivTest.java @@ -17,10 +17,28 @@ package org.apache.doris.mysql.privilege; +import org.apache.doris.analysis.Analyzer; import org.apache.doris.analysis.CompoundPredicate.Operator; +import org.apache.doris.analysis.CreateRoleStmt; +import org.apache.doris.analysis.CreateUserStmt; +import org.apache.doris.analysis.DropRoleStmt; +import org.apache.doris.analysis.GrantStmt; +import org.apache.doris.analysis.SetPassVar; +import org.apache.doris.analysis.TablePattern; +import org.apache.doris.analysis.UserDesc; +import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.catalog.AccessPrivilege; import org.apache.doris.catalog.Catalog; import org.apache.doris.common.DdlException; import org.apache.doris.common.FeMetaVersion; +import org.apache.doris.common.UserException; +import org.apache.doris.mysql.MysqlPassword; +import org.apache.doris.mysql.privilege.PaloAuth.PrivLevel; +import org.apache.doris.persist.EditLog; +import org.apache.doris.persist.PrivInfo; +import org.apache.doris.qe.ConnectContext; + +import com.google.common.collect.Lists; import org.junit.Assert; import org.junit.Before; @@ -34,7 +52,11 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.List; +import mockit.Deencapsulation; +import mockit.Expectations; +import mockit.Injectable; import mockit.Mock; import mockit.MockUp; @@ -46,6 +68,7 @@ public class PrivTest { private Method grantGlobalPrivsM; private Method grantDbPrivsM; private Method grantTblPrivsM; + private Method checkHasPrivM; @Before public void setUp() { @@ -62,6 +85,9 @@ public void setUp() { } else if (method.getName().equals("grantTblPrivs")) { method.setAccessible(true); grantTblPrivsM = method; + } else if (method.getName().equals("checkHasPrivInternal")) { + method.setAccessible(true); + checkHasPrivM = method; } } @@ -88,6 +114,17 @@ public void grantTblPrivs(Object... params) grantTblPrivsM.invoke(auth, params); } + public boolean checkHasPriv(Object... params) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + return (Boolean) checkHasPrivM.invoke(auth, params); + } + + @Test + public void testContainsPriv() { + List privs = Lists.newArrayList(PaloPrivilege.GRANT_PRIV, PaloPrivilege.NODE_PRIV); + Assert.assertFalse(privs.contains(PaloPrivilege.CREATE_PRIV)); + Assert.assertTrue(privs.contains(PaloPrivilege.NODE_PRIV)); + } @Test public void testGlobalPriv() @@ -316,6 +353,314 @@ public void testTblPriv() } + @Test + public void testGrantToUser(@Injectable Analyzer analyzer, @Injectable EditLog editlog) + throws UserException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + // 1. no privs + Assert.assertFalse(checkHasPriv("192.168.1.1", "cmy1", PrivPredicate.GRANT, new PrivLevel[] { PrivLevel.GLOBAL })); + Assert.assertFalse(checkHasPriv("192.168.1.1", "cmy1", PrivPredicate.GRANT, new PrivLevel[] { PrivLevel.DATABASE })); + Assert.assertFalse(checkHasPriv("192.168.1.1", "cmy1", PrivPredicate.GRANT, new PrivLevel[] { PrivLevel.GLOBAL, PrivLevel.DATABASE })); + // 2. grant GRANT priv and check again + grantGlobalPrivs("192.168.1.1", "cmy1", passwd, false, false, false, PrivBitSet.of(PaloPrivilege.GRANT_PRIV)); + Assert.assertTrue(checkHasPriv("192.168.1.1", "cmy1", PrivPredicate.GRANT, new PrivLevel[] { PrivLevel.GLOBAL })); + Assert.assertFalse(checkHasPriv("192.168.1.1", "cmy1", PrivPredicate.GRANT, new PrivLevel[] { PrivLevel.DATABASE })); + Assert.assertTrue(checkHasPriv("192.168.1.1", "cmy1", PrivPredicate.GRANT, new PrivLevel[] { PrivLevel.GLOBAL, PrivLevel.DATABASE })); + // 3. revoke the priv and check again + UserIdentity userIdent = new UserIdentity("cmy1", "192.168.1.1"); + userIdent.setIsAnalyzed(); + auth.revokePrivs(userIdent, TablePattern.ALL, PrivBitSet.of(PaloPrivilege.GRANT_PRIV), false, false, false); + Assert.assertFalse(checkHasPriv("192.168.1.1", "cmy1", PrivPredicate.GRANT, new PrivLevel[] { PrivLevel.GLOBAL })); + Assert.assertFalse(checkHasPriv("192.168.1.1", "cmy1", PrivPredicate.GRANT, new PrivLevel[] { PrivLevel.DATABASE })); + Assert.assertFalse(checkHasPriv("192.168.1.1", "cmy1", PrivPredicate.GRANT, new PrivLevel[] { PrivLevel.GLOBAL, PrivLevel.DATABASE })); + + Catalog catalog = Deencapsulation.newInstance(Catalog.class); + new Expectations(catalog) { + { + Catalog.getCurrentCatalog(); + result = catalog; + + catalog.getEditLog(); + result = editlog; + + catalog.getAuth(); + result = auth; + + editlog.logCreateUser((PrivInfo) any); + minTimes = 0; + } + }; + + new Expectations() { + { + analyzer.getClusterName(); + result = "default_cluster"; + } + }; + + // 1. create user cmy1@%, cmy2@% and cmy3@% + ConnectContext ctx = new ConnectContext(null); + ctx.setRemoteIP("192.168.1.1"); + ctx.setQualifiedUser("root"); + ctx.setThreadLocalInfo(); + + CreateUserStmt createUserStmt = new CreateUserStmt(new UserDesc(new UserIdentity("cmy1", "%"), "123", true)); + createUserStmt.analyze(analyzer); + auth.createUser(createUserStmt); + createUserStmt = new CreateUserStmt(new UserDesc(new UserIdentity("cmy2", "%"), "123", true)); + createUserStmt.analyze(analyzer); + auth.createUser(createUserStmt); + createUserStmt = new CreateUserStmt(new UserDesc(new UserIdentity("cmy3", "%"), "123", true)); + createUserStmt.analyze(analyzer); + auth.createUser(createUserStmt); + + // 2. grant GRANT on *.* to cmy1, grant GRANT on db1.* to cmy2, grant GRANT on db1.tbl1 to cmy3, + GrantStmt grantStmt = new GrantStmt(new UserIdentity("cmy1", "%"), null, TablePattern.ALL, Lists.newArrayList(AccessPrivilege.GRANT_PRIV)); + grantStmt.analyze(analyzer); + auth.grant(grantStmt); + grantStmt = new GrantStmt(new UserIdentity("cmy2", "%"), null, new TablePattern("db1", "*"), Lists.newArrayList(AccessPrivilege.GRANT_PRIV)); + grantStmt.analyze(analyzer); + auth.grant(grantStmt); + grantStmt = new GrantStmt(new UserIdentity("cmy3", "%"), null, new TablePattern("db1", "tbl1"), Lists.newArrayList(AccessPrivilege.GRANT_PRIV)); + grantStmt.analyze(analyzer); + auth.grant(grantStmt); + + // 3. use cmy2 to grant CREATE priv on db1 and db2 to cmy4 (cmy4 does not exist, but we just test GrantStmt in analyze phase, so it is ok) + ctx = new ConnectContext(null); + ctx.setRemoteIP("192.168.1.1"); + ctx.setQualifiedUser("default_cluster:cmy2"); + ctx.setThreadLocalInfo(); + // db1 should be ok + grantStmt = new GrantStmt(new UserIdentity("cmy4", "%"), null, new TablePattern("db1", "*"), + Lists.newArrayList(AccessPrivilege.CREATE_PRIV)); + try { + grantStmt.analyze(analyzer); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + // db2 should not be allowed + boolean hasException = false; + grantStmt = new GrantStmt(new UserIdentity("cmy4", "%"), null, new TablePattern("db2", "*"), + Lists.newArrayList(AccessPrivilege.CREATE_PRIV)); + try { + grantStmt.analyze(analyzer); + } catch (Exception e) { + hasException = true; + } + Assert.assertTrue(hasException); + + // 4. use cmy2 to create cmy4 + createUserStmt = new CreateUserStmt(new UserDesc(new UserIdentity("cmy4", "%"), "123", true)); + try { + createUserStmt.analyze(analyzer); + auth.createUser(createUserStmt); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + + // 5. use cmy3 to grant CREATE priv on db1.* and db1.tbl1 to cmy5 (cmy5 does not exist, but we just test GrantStmt in analyze phase, so it is ok) + ctx = new ConnectContext(null); + ctx.setRemoteIP("192.168.1.1"); + ctx.setQualifiedUser("default_cluster:cmy3"); + ctx.setThreadLocalInfo(); + // db1.* should not be allowed + hasException = false; + grantStmt = new GrantStmt(new UserIdentity("cmy5", "%"), null, new TablePattern("db1", "*"), + Lists.newArrayList(AccessPrivilege.CREATE_PRIV)); + try { + grantStmt.analyze(analyzer); + } catch (Exception e) { + hasException = true; + } + Assert.assertTrue(hasException); + // db1.tbl1 should be ok + grantStmt = new GrantStmt(new UserIdentity("cmy5", "%"), null, new TablePattern("db1", "tbl1"), + Lists.newArrayList(AccessPrivilege.CREATE_PRIV)); + try { + grantStmt.analyze(analyzer); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + } + + @Test + public void testGrantToRole(@Injectable Analyzer analyzer, @Injectable EditLog editlog) throws UserException { + Catalog catalog = Deencapsulation.newInstance(Catalog.class); + new Expectations(catalog) { + { + Catalog.getCurrentCatalog(); + result = catalog; + + catalog.getEditLog(); + result = editlog; + + catalog.getAuth(); + result = auth; + + editlog.logCreateUser((PrivInfo) any); + minTimes = 0; + } + }; + + new Expectations() { + { + analyzer.getClusterName(); + result = "default_cluster"; + } + }; + + // 1. create user cmy1@%, cmy2@% + ConnectContext ctx = new ConnectContext(null); + ctx.setRemoteIP("192.168.1.1"); + ctx.setQualifiedUser("root"); + ctx.setThreadLocalInfo(); + + CreateUserStmt createUserStmt = new CreateUserStmt(new UserDesc(new UserIdentity("cmy1", "%"), "123", true)); + createUserStmt.analyze(analyzer); + auth.createUser(createUserStmt); + createUserStmt = new CreateUserStmt(new UserDesc(new UserIdentity("cmy2", "%"), "123", true)); + createUserStmt.analyze(analyzer); + auth.createUser(createUserStmt); + + // 2. grant GRANT on *.* to cmy1, grant GRANT on db1.* to cmy2 + GrantStmt grantStmt = new GrantStmt(new UserIdentity("cmy1", "%"), null, TablePattern.ALL, + Lists.newArrayList(AccessPrivilege.GRANT_PRIV)); + grantStmt.analyze(analyzer); + auth.grant(grantStmt); + grantStmt = new GrantStmt(new UserIdentity("cmy2", "%"), null, new TablePattern("db1", "*"), + Lists.newArrayList(AccessPrivilege.GRANT_PRIV)); + grantStmt.analyze(analyzer); + auth.grant(grantStmt); + + // 3. use cmy1 to create role1 + ctx = new ConnectContext(null); + ctx.setRemoteIP("192.168.1.1"); + ctx.setQualifiedUser("default_cluster:cmy1"); + ctx.setThreadLocalInfo(); + CreateRoleStmt createRoleStmt = new CreateRoleStmt("role1"); + try { + createRoleStmt.analyze(analyzer); + auth.createRole(createRoleStmt); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + + // 4. use cmy2 to create role2, which should not be allowed + ctx = new ConnectContext(null); + ctx.setRemoteIP("192.168.1.1"); + ctx.setQualifiedUser("default_cluster:cmy2"); + ctx.setThreadLocalInfo(); + boolean hasException = false; + createRoleStmt = new CreateRoleStmt("role2"); + try { + createRoleStmt.analyze(analyzer); + } catch (Exception e) { + hasException = true; + } + Assert.assertTrue(hasException); + + // 5. use cmy2 to grant CREATE priv on db1.* to role, which should not be + // allowed + hasException = false; + grantStmt = new GrantStmt(null, "role1", new TablePattern("db1", "*"), Lists.newArrayList(AccessPrivilege.CREATE_PRIV)); + try { + grantStmt.analyze(analyzer); + } catch (Exception e) { + hasException = true; + } + Assert.assertTrue(hasException); + + // 6. use cmy2 to drop role1, which should not be allowed + hasException = false; + DropRoleStmt dropRoleStmt = new DropRoleStmt("role1"); + try { + dropRoleStmt.analyze(analyzer); + } catch (Exception e) { + hasException = true; + } + Assert.assertTrue(hasException); + } + + @Test + public void testSetPassword(@Injectable Analyzer analyzer, @Injectable EditLog editlog) throws UserException { + Catalog catalog = Deencapsulation.newInstance(Catalog.class); + new Expectations(catalog) { + { + Catalog.getCurrentCatalog(); + result = catalog; + + catalog.getEditLog(); + result = editlog; + + catalog.getAuth(); + result = auth; + + editlog.logCreateUser((PrivInfo) any); + minTimes = 0; + } + }; + + new Expectations() { + { + analyzer.getClusterName(); + result = "default_cluster"; + } + }; + + new Expectations(MysqlPassword.class) { + { + MysqlPassword.checkPassword(anyString); + result = new byte[2]; + minTimes = 0; + } + }; + + // 1. create user cmy1@%, cmy2@% + ConnectContext ctx = new ConnectContext(null); + ctx.setRemoteIP("192.168.1.1"); + ctx.setQualifiedUser("root"); + ctx.setThreadLocalInfo(); + + CreateUserStmt createUserStmt = new CreateUserStmt(new UserDesc(new UserIdentity("cmy1", "%"), "123", true)); + createUserStmt.analyze(analyzer); + auth.createUser(createUserStmt); + createUserStmt = new CreateUserStmt(new UserDesc(new UserIdentity("cmy2", "%"), "123", true)); + createUserStmt.analyze(analyzer); + auth.createUser(createUserStmt); + + // 2. use cmy1 to set password for itself + UserIdentity currentUserIdentity = new UserIdentity("cmy1", "%"); + currentUserIdentity.analyze("default_cluster"); + ctx = new ConnectContext(null); + ctx.setRemoteIP("192.168.1.1"); + ctx.setQualifiedUser("default_cluster:cmy1"); + ctx.setCurrentUserIdentitfy(currentUserIdentity); + ctx.setThreadLocalInfo(); + SetPassVar setPassVar = new SetPassVar(null, "12345"); + try { + setPassVar.analyze(analyzer); + } catch (Exception e) { + Assert.fail(); + } + + // 2. use cmy1 to set password for cmy1@"%" + setPassVar = new SetPassVar(new UserIdentity("cmy1", "%"), "12345"); + try { + setPassVar.analyze(analyzer); + } catch (Exception e) { + Assert.fail(); + } + + // 3. use cmy1 to set password for cmy1@"192.168.1.1", which is not allowed + boolean hasException = false; + setPassVar = new SetPassVar(new UserIdentity("cmy1", "192.168.1.1"), "12345"); + try { + setPassVar.analyze(analyzer); + } catch (Exception e) { + hasException = true; + } + Assert.assertTrue(hasException); + } + private PaloAuth testPersist(PaloAuth auth) { // 1. Write objects to file File file = new File("./paloAuth");