diff --git a/docs/en/docs/advanced/variables.md b/docs/en/docs/advanced/variables.md index 59e8bf1fac..2d82126091 100644 --- a/docs/en/docs/advanced/variables.md +++ b/docs/en/docs/advanced/variables.md @@ -584,3 +584,9 @@ Translated with www.DeepL.com/Translator (free version) * `drop_table_if_ctas_failed` Controls whether create table as select deletes created tables when a insert error occurs, the default value is true. + +* `show_user_default_role` + + + + Controls whether to show each user's implicit roles in the results of `show roles`. Default is false. diff --git a/docs/zh-CN/docs/advanced/variables.md b/docs/zh-CN/docs/advanced/variables.md index a660683362..f67f02ef90 100644 --- a/docs/zh-CN/docs/advanced/variables.md +++ b/docs/zh-CN/docs/advanced/variables.md @@ -571,3 +571,9 @@ SELECT /*+ SET_VAR(query_timeout = 1, enable_partition_cache=true) */ sleep(3); * `drop_table_if_ctas_failed` 控制create table as select在写入发生错误时是否删除已创建的表,默认为true。 + +* `show_user_default_role` + + + + 控制是否在 `show roles` 的结果里显示每个用户隐式对应的角色。默认为 false。 diff --git a/fe/fe-core/src/main/java/org/apache/doris/PaloFe.java b/fe/fe-core/src/main/java/org/apache/doris/PaloFe.java index 6fb315dced..61095e141d 100755 --- a/fe/fe-core/src/main/java/org/apache/doris/PaloFe.java +++ b/fe/fe-core/src/main/java/org/apache/doris/PaloFe.java @@ -121,7 +121,7 @@ public class PaloFe { // check command line options checkCommandLineOptions(cmdLineOpts); - LOG.info("Palo FE starting..."); + LOG.info("Doris FE starting..."); FrontendOptions.init(); @@ -196,7 +196,7 @@ public class PaloFe { /* * -v --version - * Print the version of Palo Frontend + * Print the version of Doris Frontend * -h --helper * Specify the helper node when joining a bdb je replication group * -i --image @@ -222,7 +222,7 @@ public class PaloFe { private static CommandLineOptions parseArgs(String[] args) { CommandLineParser commandLineParser = new DefaultParser(); Options options = new Options(); - options.addOption("v", "version", false, "Print the version of Palo Frontend"); + options.addOption("v", "version", false, "Print the version of Doris Frontend"); options.addOption("h", "helper", true, "Specify the helper node when joining a bdb je replication group"); options.addOption("i", "image", true, "Check if the specified image is valid"); options.addOption("b", "bdb", false, "Run bdbje debug tools"); diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/UserIdentity.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/UserIdentity.java index 4b50479f66..0dae2ff17a 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/UserIdentity.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/UserIdentity.java @@ -27,6 +27,7 @@ import org.apache.doris.common.PatternMatcherWrapper; import org.apache.doris.common.io.Text; import org.apache.doris.common.io.Writable; import org.apache.doris.mysql.privilege.Auth; +import org.apache.doris.mysql.privilege.RoleManager; import org.apache.doris.persist.gson.GsonPostProcessable; import org.apache.doris.persist.gson.GsonUtils; import org.apache.doris.thrift.TUserIdentity; @@ -210,6 +211,20 @@ public class UserIdentity implements Writable, GsonPostProcessable { return tUserIdent; } + // return default_role_rbac_username@host or default_role_rbac_username@[domain] + public String toDefaultRoleName() { + StringBuilder sb = new StringBuilder( + RoleManager.DEFAULT_ROLE_PREFIX + ClusterNamespace.getNameFromFullName(user) + "@"); + if (isDomain) { + sb.append("["); + } + sb.append(host); + if (isDomain) { + sb.append("]"); + } + return sb.toString(); + } + public static UserIdentity read(DataInput in) throws IOException { // Use Gson in the VERSION_109 if (Env.getCurrentEnvJournalVersion() < FeMetaVersion.VERSION_109) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java index a408663b27..fa4eada867 100755 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java @@ -1351,6 +1351,8 @@ public class Env { } } } + + auth.rectifyPrivs(); } // start all daemon threads only running on Master @@ -1822,10 +1824,10 @@ public class Env { return checksum; } - public long loadPaloAuth(DataInputStream dis, long checksum) throws IOException { + public long loadAuth(DataInputStream dis, long checksum) throws IOException { // CAN NOT use Auth.read(), cause this auth instance is already passed to DomainResolver auth.readFields(dis); - LOG.info("finished replay paloAuth from image"); + LOG.info("finished replay auth from image"); return checksum; } @@ -2096,7 +2098,7 @@ public class Env { return checksum; } - public long savePaloAuth(CountingDataOutputStream dos, long checksum) throws IOException { + public long saveAuth(CountingDataOutputStream dos, long checksum) throws IOException { auth.write(dos); return checksum; } 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 441573dda1..14af84b91e 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 @@ -84,7 +84,7 @@ 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. + // each Doris 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. @@ -1222,13 +1222,13 @@ public class Auth implements Writable { .getNameFromFullName(user.getUserIdentity().getQualifiedUser())) .concat("\'@\'").concat(user.getUserIdentity().getHost()).concat("\'"); String isGrantable = tablePrivEntry.getPrivSet().get(2) ? "YES" : "NO"; // GRANT_PRIV - for (Privilege paloPriv : tablePrivEntry.getPrivSet().toPrivilegeList()) { - if (!Privilege.privInDorisToMysql.containsKey(paloPriv)) { + for (Privilege priv : tablePrivEntry.getPrivSet().toPrivilegeList()) { + if (!Privilege.privInDorisToMysql.containsKey(priv)) { continue; } TPrivilegeStatus status = new TPrivilegeStatus(); status.setTableName(tblName); - status.setPrivilegeType(Privilege.privInDorisToMysql.get(paloPriv)); + status.setPrivilegeType(Privilege.privInDorisToMysql.get(priv)); status.setGrantee(grantee); status.setSchema(dbName); status.setIsGrantable(isGrantable); @@ -1273,12 +1273,12 @@ public class Auth implements Writable { .getNameFromFullName(user.getUserIdentity().getQualifiedUser())) .concat("\'@\'").concat(user.getUserIdentity().getHost()).concat("\'"); String isGrantable = dbPrivEntry.getPrivSet().get(2) ? "YES" : "NO"; // GRANT_PRIV - for (Privilege paloPriv : dbPrivEntry.getPrivSet().toPrivilegeList()) { - if (!Privilege.privInDorisToMysql.containsKey(paloPriv)) { + for (Privilege priv : dbPrivEntry.getPrivSet().toPrivilegeList()) { + if (!Privilege.privInDorisToMysql.containsKey(priv)) { continue; } TPrivilegeStatus status = new TPrivilegeStatus(); - status.setPrivilegeType(Privilege.privInDorisToMysql.get(paloPriv)); + status.setPrivilegeType(Privilege.privInDorisToMysql.get(priv)); status.setGrantee(grantee); status.setSchema(dbName); status.setIsGrantable(isGrantable); @@ -1318,8 +1318,8 @@ public class Auth implements Writable { .concat(ClusterNamespace.getNameFromFullName(user.getUserIdentity().getQualifiedUser())) .concat("\'@\'").concat(user.getUserIdentity().getHost()).concat("\'"); String isGrantable = privEntry.getPrivSet().get(2) ? "YES" : "NO"; // GRANT_PRIV - for (Privilege paloPriv : privEntry.getPrivSet().toPrivilegeList()) { - if (paloPriv == Privilege.ADMIN_PRIV) { + for (Privilege globalPriv : privEntry.getPrivSet().toPrivilegeList()) { + if (globalPriv == Privilege.ADMIN_PRIV) { // ADMIN_PRIV includes all privileges of table and resource. for (String priv : Privilege.privInDorisToMysql.values()) { TPrivilegeStatus status = new TPrivilegeStatus(); @@ -1330,11 +1330,11 @@ public class Auth implements Writable { } break; } - if (!Privilege.privInDorisToMysql.containsKey(paloPriv)) { + if (!Privilege.privInDorisToMysql.containsKey(globalPriv)) { continue; } TPrivilegeStatus status = new TPrivilegeStatus(); - status.setPrivilegeType(Privilege.privInDorisToMysql.get(paloPriv)); + status.setPrivilegeType(Privilege.privInDorisToMysql.get(globalPriv)); status.setGrantee(grantee); status.setIsGrantable(isGrantable); userPrivResult.add(status); @@ -1403,7 +1403,6 @@ public class Auth implements Writable { } } - //tmp for current user can only has one role private void setRoleToUser(UserIdentity userIdent, String role) throws DdlException { // 1. check if role exist @@ -1416,10 +1415,18 @@ public class Auth implements Writable { userRoleManager.addUserRole(userIdent, roleManager.getUserDefaultRoleName(userIdent)); } - public static Auth read(DataInput in) throws IOException { - Auth auth = new Auth(); - auth.readFields(in); - return auth; + + /** + * This is a bug that if created a normal user and grant it with ADMIN_PRIV/RESOURCE_PRIV/NODE_PRIV + * before v1.2, and then upgrade to v1.2, these privileges will be set in catalog level, but it should be + * in global level. + * This method will rectify this bug. And it's logic is same with userPrivTable.degradeToInternalCatalogPriv(), + * but userPrivTable.degradeToInternalCatalogPriv() only handle the info in images, not in edit log. + * This rectifyPrivs() will be called after image and edit log replayed. + * So it will rectify the bug in both images and edit log. + */ + public void rectifyPrivs() { + roleManager.rectifyPrivs(); } @Override @@ -1450,7 +1457,7 @@ public class Auth implements Writable { catalogPrivTable = (CatalogPrivTable) PrivTable.read(in); } else { catalogPrivTable = userPrivTable.degradeToInternalCatalogPriv(); - LOG.info("Load PaloAuth from meta version < {}, degrade UserPrivTable to CatalogPrivTable", + LOG.info("Load auth from meta version < {}, degrade UserPrivTable to CatalogPrivTable", FeMetaVersion.VERSION_111); } DbPrivTable dbPrivTable = (DbPrivTable) PrivTable.read(in); diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java index 91956c1a96..dc7b5a161b 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java @@ -532,6 +532,45 @@ public class Role implements Writable, GsonPostProcessable { tablePrivTable.revoke(entry, false, true); } + /** + * eg: + * Before, tblPatternToPrivs has 1 entry: ctl.*.* -> SELECT_PRIV, ADMIN_PRIV, ALTER_PRIV + * After, tblPatternToPrivs has 2 entries: + * *.*.* -> ADMIN_PRIV + * ctl.*.* -> SELECT_PRIV, ALTER_PRIV + */ + public void rectifyPrivs() { + PrivBitSet modifiedGlobalPrivs = new PrivBitSet(); + for (Map.Entry entry : tblPatternToPrivs.entrySet()) { + TablePattern tblPattern = entry.getKey(); + PrivBitSet privs = entry.getValue(); + if (privs.containsPrivs(Privilege.ADMIN_PRIV, Privilege.NODE_PRIV, Privilege.USAGE_PRIV) + && tblPattern.getPrivLevel() != PrivLevel.GLOBAL) { + LOG.debug("rectify privs {}: {} -> {}", roleName, tblPattern, privs); + PrivBitSet copiedPrivs = privs.copy(); + copiedPrivs.and(PrivBitSet.of(Privilege.ADMIN_PRIV, Privilege.NODE_PRIV, Privilege.USAGE_PRIV)); + modifiedGlobalPrivs.or(copiedPrivs); + // remove these privs from non global table pattern's priv set + privs.unset(Privilege.USAGE_PRIV.getIdx()); + privs.unset(Privilege.NODE_PRIV.getIdx()); + privs.unset(Privilege.ADMIN_PRIV.getIdx()); + LOG.debug("alter rectify privs {}: {} -> {}, modified global priv: {}", + roleName, tblPattern, privs, modifiedGlobalPrivs); + } + } + if (!modifiedGlobalPrivs.isEmpty()) { + PrivBitSet privBitSet = tblPatternToPrivs.get(TablePattern.ALL); + if (privBitSet == null) { + tblPatternToPrivs.put(TablePattern.ALL, modifiedGlobalPrivs); + } else { + privBitSet.or(modifiedGlobalPrivs); + } + } + + // rebuild these priv tables + rebuildPrivTables(); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -586,7 +625,16 @@ public class Role implements Writable, GsonPostProcessable { } @Override - public void gsonPostProcess() throws IOException { + public void gsonPostProcess() { + rebuildPrivTables(); + } + + private void rebuildPrivTables() { + globalPrivTable = new GlobalPrivTable(); + catalogPrivTable = new CatalogPrivTable(); + dbPrivTable = new DbPrivTable(); + tablePrivTable = new TablePrivTable(); + resourcePrivTable = new ResourcePrivTable(); for (Entry entry : tblPatternToPrivs.entrySet()) { try { grantPrivs(entry.getKey(), entry.getValue().copy()); diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java index 851c19b3e0..80fba40814 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java @@ -30,6 +30,7 @@ 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.GsonUtils; +import org.apache.doris.qe.ConnectContext; import org.apache.doris.system.SystemInfoService; import com.google.common.base.Joiner; @@ -122,11 +123,12 @@ public class RoleManager implements Writable { return existingRole; } - public void getRoleInfo(List> results) { for (Role role : roles.values()) { if (role.getRoleName().startsWith(DEFAULT_ROLE_PREFIX)) { - continue; + if (ConnectContext.get() == null || !ConnectContext.get().getSessionVariable().showUserDefaultRole) { + continue; + } } List info = Lists.newArrayList(); info.add(role.getRoleName()); @@ -185,13 +187,19 @@ public class RoleManager implements Writable { } public String getUserDefaultRoleName(UserIdentity userIdentity) { - return DEFAULT_ROLE_PREFIX + userIdentity.toString(); + return userIdentity.toDefaultRoleName(); } public Map getRoles() { return roles; } + public void rectifyPrivs() { + for (Map.Entry entry : roles.entrySet()) { + entry.getValue().rectifyPrivs(); + } + } + @Override public String toString() { StringBuilder sb = new StringBuilder("Roles: "); diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java b/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java index c01b868f02..638df09e67 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java +++ b/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java @@ -129,9 +129,9 @@ public class MetaPersistMethod { break; case "paloAuth": metaPersistMethod.readMethod = - Env.class.getDeclaredMethod("loadPaloAuth", DataInputStream.class, long.class); + Env.class.getDeclaredMethod("loadAuth", DataInputStream.class, long.class); metaPersistMethod.writeMethod = - Env.class.getDeclaredMethod("savePaloAuth", CountingDataOutputStream.class, long.class); + Env.class.getDeclaredMethod("saveAuth", CountingDataOutputStream.class, long.class); break; case "transactionState": metaPersistMethod.readMethod = diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java index b6402d8532..7debdfd7a4 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java @@ -271,6 +271,9 @@ public class SessionVariable implements Serializable, Writable { public static final String DROP_TABLE_IF_CTAS_FAILED = "drop_table_if_ctas_failed"; public static final String MAX_TABLE_COUNT_USE_CASCADES_JOIN_REORDER = "max_table_count_use_cascades_join_reorder"; + public static final int MIN_JOIN_REORDER_TABLE_COUNT = 2; + + public static final String SHOW_USER_DEFAULT_ROLE = "show_user_default_role"; // session origin value public Map sessionOriginValue = new HashMap(); @@ -720,11 +723,12 @@ public class SessionVariable implements Serializable, Writable { @VariableMgr.VarAttr(name = DROP_TABLE_IF_CTAS_FAILED, needForward = true) public boolean dropTableIfCtasFailed = true; - @VariableMgr.VarAttr(name = MAX_TABLE_COUNT_USE_CASCADES_JOIN_REORDER) public int maxTableCountUseCascadesJoinReorder = 10; - public static final int MIN_JOIN_REORDER_TABLE_COUNT = 2; + // If this is true, the result of `show roles` will return all user default role + @VariableMgr.VarAttr(name = SHOW_USER_DEFAULT_ROLE, needForward = true) + public boolean showUserDefaultRole = false; // If this fe is in fuzzy mode, then will use initFuzzyModeVariables to generate some variables, // not the default value set in the code. @@ -1513,6 +1517,10 @@ public class SessionVariable implements Serializable, Writable { : maxTableCountUseCascadesJoinReorder; } + public boolean isShowUserDefaultRole() { + return showUserDefaultRole; + } + /** * Serialize to thrift object. * Used for rest api. diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java index c67083db9d..55248c8df3 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java @@ -168,6 +168,8 @@ public class AuthTest { try { auth.createUser(createUserStmt); + Set roleUsers = auth.getRoleUsers(userIdentity.toDefaultRoleName()); + Assert.assertTrue(roleUsers.contains(userIdentity)); } catch (DdlException e) { Assert.fail(); } @@ -295,6 +297,8 @@ public class AuthTest { } try { auth.createUser(createUserStmt); + Set roleUsers = auth.getRoleUsers(userIdentity.toDefaultRoleName()); + Assert.assertTrue(roleUsers.contains(userIdentity)); } catch (DdlException e) { Assert.fail(); }