Skip to content

Commit

Permalink
[enhance](auth)when assigning permissions, the current user must have…
Browse files Browse the repository at this point in the history
… corresponding permissions (#32825) (#33948)

bp #32825
Co-authored-by: zhangdong <[email protected]>
  • Loading branch information
morningman authored Apr 22, 2024
1 parent 6c30695 commit ff5a4cb
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 105 deletions.
183 changes: 89 additions & 94 deletions fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,31 @@

package org.apache.doris.analysis;

import org.apache.doris.analysis.CompoundPredicate.Operator;
import org.apache.doris.catalog.AccessPrivilegeWithCols;
import org.apache.doris.catalog.Env;
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.privilege.AccessControllerManager;
import org.apache.doris.mysql.privilege.Auth.PrivLevel;
import org.apache.doris.mysql.privilege.ColPrivilegeKey;
import org.apache.doris.mysql.privilege.PrivBitSet;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.mysql.privilege.Privilege;
import org.apache.doris.qe.ConnectContext;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
Expand All @@ -55,11 +60,11 @@ public class GrantStmt extends DdlStmt {
private ResourcePattern resourcePattern;
private WorkloadGroupPattern workloadGroupPattern;
private Set<Privilege> privileges = Sets.newHashSet();
//Privilege,ctl,db,table -> cols
// Privilege,ctl,db,table -> cols
private Map<ColPrivilegeKey, Set<String>> colPrivileges = Maps.newHashMap();
// Indicates that these roles are granted to a user
private List<String> roles;
//AccessPrivileges will be parsed into two parts,
// AccessPrivileges will be parsed into two parts,
// with the column permissions section placed in "colPrivileges" and the others in "privileges"
private List<AccessPrivilegeWithCols> accessPrivileges;

Expand Down Expand Up @@ -164,11 +169,11 @@ public void analyze(Analyzer analyzer) throws UserException {
}

if (tblPattern != null) {
checkTablePrivileges(privileges, role, tblPattern, colPrivileges);
checkTablePrivileges(privileges, tblPattern, colPrivileges);
} else if (resourcePattern != null) {
checkResourcePrivileges(privileges, role, resourcePattern);
checkResourcePrivileges(privileges, resourcePattern);
} else if (workloadGroupPattern != null) {
checkWorkloadGroupPrivileges(privileges, role, workloadGroupPattern);
checkWorkloadGroupPrivileges(privileges, workloadGroupPattern);
} else if (roles != null) {
checkRolePrivileges();
}
Expand All @@ -187,134 +192,124 @@ public static void checkAccessPrivileges(

/**
* Rules:
* 1. ADMIN_PRIV and NODE_PRIV can only be granted/revoked on GLOBAL level
* 2. Only the user with NODE_PRIV can grant NODE_PRIV to other user
* 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.
* 5.3 or user should has 'resource' GRANT_PRIV if grant/revoke to/from certain 'resource'
* 5.4 or user should has 'workload group' GRANT_PRIV if grant/revoke to/from certain 'workload group'
* 6. Can not grant USAGE_PRIV to database or table
* 1. some privs in Privilege.notBelongToTablePrivileges can not granted/revoked on table
* 2. ADMIN_PRIV and NODE_PRIV can only be granted/revoked on GLOBAL level
* 3. Only the user with NODE_PRIV can grant NODE_PRIV to other user
* 4. Check that the current user has both grant_priv and the permissions to be assigned to others
* 5. col priv must assign to specific table
*
* @param privileges
* @param role
* @param tblPattern
* @throws AnalysisException
*/
public static void checkTablePrivileges(Collection<Privilege> privileges, String role, TablePattern tblPattern,
public static void checkTablePrivileges(Collection<Privilege> privileges, TablePattern tblPattern,
Map<ColPrivilegeKey, Set<String>> colPrivileges)
throws AnalysisException {
// Rule 1
checkIncorrectPrivilege(Privilege.notBelongToTablePrivileges, privileges);
// Rule 2
if (tblPattern.getPrivLevel() != PrivLevel.GLOBAL && (privileges.contains(Privilege.ADMIN_PRIV)
|| privileges.contains(Privilege.NODE_PRIV))) {
throw new AnalysisException("ADMIN_PRIV and NODE_PRIV can only be granted/revoke on/from *.*.*");
}

// Rule 2
// Rule 3
if (privileges.contains(Privilege.NODE_PRIV) && !Env.getCurrentEnv().getAccessManager()
.checkGlobalPriv(ConnectContext.get(), PrivPredicate.OPERATOR)) {
throw new AnalysisException("Only user with NODE_PRIV can grant/revoke NODE_PRIV to other user");
}

if (role != null) {
// Rule 3 and 4
if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
}
} else {
// Rule 5.1 and 5.2
if (tblPattern.getPrivLevel() == PrivLevel.GLOBAL) {
if (!Env.getCurrentEnv().getAccessManager()
.checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
}
} else if (tblPattern.getPrivLevel() == PrivLevel.CATALOG) {
if (!Env.getCurrentEnv().getAccessManager().checkCtlPriv(ConnectContext.get(),
tblPattern.getQualifiedCtl(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
}
} else if (tblPattern.getPrivLevel() == PrivLevel.DATABASE) {
if (!Env.getCurrentEnv().getAccessManager().checkDbPriv(ConnectContext.get(),
tblPattern.getQualifiedCtl(), tblPattern.getQualifiedDb(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
}
} else {
// table level
if (!Env.getCurrentEnv().getAccessManager()
.checkTblPriv(ConnectContext.get(), tblPattern.getQualifiedCtl(), tblPattern.getQualifiedDb(),
tblPattern.getTbl(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
}
}
}

// Rule 6
if (privileges.contains(Privilege.USAGE_PRIV)) {
throw new AnalysisException("Can not grant/revoke USAGE_PRIV to/from database or table");
// Rule 4
PrivPredicate predicate = getPrivPredicate(privileges);
AccessControllerManager accessManager = Env.getCurrentEnv().getAccessManager();
if (!accessManager.checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)
&& !checkTablePriv(ConnectContext.get(), predicate, tblPattern)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ALL_ACCESS_DENIED_ERROR,
predicate.getPrivs().toPrivilegeList());
}

// Rule 7
// Rule 5
if (!MapUtils.isEmpty(colPrivileges) && "*".equals(tblPattern.getTbl())) {
throw new AnalysisException("Col auth must specify specific table");
}
}

public static void checkResourcePrivileges(Collection<Privilege> privileges, String role,
ResourcePattern resourcePattern) throws AnalysisException {
for (int i = 0; i < Privilege.notBelongToResourcePrivileges.length; i++) {
if (privileges.contains(Privilege.notBelongToResourcePrivileges[i])) {
private static void checkIncorrectPrivilege(Privilege[] incorrectPrivileges,
Collection<Privilege> privileges) throws AnalysisException {
for (int i = 0; i < incorrectPrivileges.length; i++) {
if (privileges.contains(incorrectPrivileges[i])) {
throw new AnalysisException(
String.format("Can not grant/revoke %s on resource to/from any other users or roles",
Privilege.notBelongToResourcePrivileges[i]));
String.format("Can not grant/revoke %s to/from any other users or roles",
incorrectPrivileges[i]));
}
}
}

if (role != null) {
// Rule 3 and 4
if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
}
} else {
// Rule 5.1 and 5.3
if (resourcePattern.getPrivLevel() == PrivLevel.GLOBAL) {
if (!Env.getCurrentEnv().getAccessManager()
.checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
}
} else {
if (!Env.getCurrentEnv().getAccessManager().checkResourcePriv(ConnectContext.get(),
resourcePattern.getResourceName(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
}
}
private static PrivPredicate getPrivPredicate(Collection<Privilege> privileges) {
ArrayList<Privilege> privs = Lists.newArrayList(privileges);
privs.add(Privilege.GRANT_PRIV);
return PrivPredicate.of(PrivBitSet.of(privs), Operator.AND);
}

private static boolean checkTablePriv(ConnectContext ctx, PrivPredicate wanted,
TablePattern tblPattern) {
AccessControllerManager accessManager = Env.getCurrentEnv().getAccessManager();
switch (tblPattern.getPrivLevel()) {
case GLOBAL:
return accessManager.checkGlobalPriv(ctx, wanted);
case CATALOG:
return accessManager.checkCtlPriv(ConnectContext.get(),
tblPattern.getQualifiedCtl(), wanted);
case DATABASE:
return accessManager.checkDbPriv(ConnectContext.get(),
tblPattern.getQualifiedCtl(), tblPattern.getQualifiedDb(), wanted);
default:
return accessManager.checkTblPriv(ConnectContext.get(), tblPattern.getQualifiedCtl(),
tblPattern.getQualifiedDb(), tblPattern.getTbl(), wanted);
}
}

public static void checkWorkloadGroupPrivileges(Collection<Privilege> privileges, String role,
WorkloadGroupPattern workloadGroupPattern) throws AnalysisException {
for (int i = 0; i < Privilege.notBelongToWorkloadGroupPrivileges.length; i++) {
if (privileges.contains(Privilege.notBelongToWorkloadGroupPrivileges[i])) {
throw new AnalysisException(
String.format("Can not grant/revoke %s on workload group to/from any other users or roles",
Privilege.notBelongToWorkloadGroupPrivileges[i]));
}
public static void checkResourcePrivileges(Collection<Privilege> privileges,
ResourcePattern resourcePattern) throws AnalysisException {
checkIncorrectPrivilege(Privilege.notBelongToResourcePrivileges, privileges);

PrivPredicate predicate = getPrivPredicate(privileges);
AccessControllerManager accessManager = Env.getCurrentEnv().getAccessManager();
if (!accessManager.checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)
&& !checkResourcePriv(ConnectContext.get(), resourcePattern, predicate)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ALL_ACCESS_DENIED_ERROR,
predicate.getPrivs().toPrivilegeList());
}

if (role != null) {
// Rule 4
if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
}
} else if (!Env.getCurrentEnv().getAccessManager().checkWorkloadGroupPriv(ConnectContext.get(),
workloadGroupPattern.getworkloadGroupName(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
}

private static boolean checkResourcePriv(ConnectContext ctx, ResourcePattern resourcePattern,
PrivPredicate privPredicate) {
if (resourcePattern.getPrivLevel() == PrivLevel.GLOBAL) {
return Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ctx, privPredicate);
} else {
return Env.getCurrentEnv().getAccessManager()
.checkResourcePriv(ctx, resourcePattern.getResourceName(), privPredicate);
}
}

public static void checkWorkloadGroupPrivileges(Collection<Privilege> privileges,
WorkloadGroupPattern workloadGroupPattern) throws AnalysisException {
checkIncorrectPrivilege(Privilege.notBelongToWorkloadGroupPrivileges, privileges);

PrivPredicate predicate = getPrivPredicate(privileges);
AccessControllerManager accessManager = Env.getCurrentEnv().getAccessManager();
if (!accessManager.checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)
&& !accessManager.checkWorkloadGroupPriv(ConnectContext.get(),
workloadGroupPattern.getworkloadGroupName(), predicate)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ALL_ACCESS_DENIED_ERROR,
predicate.getPrivs().toPrivilegeList());
}
}

public static void checkRolePrivileges() throws AnalysisException {
if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/REVOKE");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,11 @@ public void analyze(Analyzer analyzer) throws AnalysisException {

// Revoke operation obey the same rule as Grant operation. reuse the same method
if (tblPattern != null) {
GrantStmt.checkTablePrivileges(privileges, role, tblPattern, colPrivileges);
GrantStmt.checkTablePrivileges(privileges, tblPattern, colPrivileges);
} else if (resourcePattern != null) {
GrantStmt.checkResourcePrivileges(privileges, role, resourcePattern);
GrantStmt.checkResourcePrivileges(privileges, resourcePattern);
} else if (workloadGroupPattern != null) {
GrantStmt.checkWorkloadGroupPrivileges(privileges, role, workloadGroupPattern);
GrantStmt.checkWorkloadGroupPrivileges(privileges, workloadGroupPattern);
} else if (roles != null) {
GrantStmt.checkRolePrivileges();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public enum ErrorCode {
+ "(current value: %d)"),
ERR_SPECIFIC_ACCESS_DENIED_ERROR(1227, new byte[]{'4', '2', '0', '0', '0'}, "Access denied; you need (at least "
+ "one of) the %s privilege(s) for this operation"),
ERR_SPECIFIC_ALL_ACCESS_DENIED_ERROR(1227, new byte[] {'4', '2', '0', '0', '0'}, "Access denied; you need all "
+ " %s privilege(s) for this operation"),
ERR_LOCAL_VARIABLE(1228, new byte[]{'H', 'Y', '0', '0', '0'}, "Variable '%s' is a SESSION variable and can't be "
+ "used with SET GLOBAL"),
ERR_GLOBAL_VARIABLE(1229, new byte[]{'H', 'Y', '0', '0', '0'}, "Variable '%s' is a GLOBAL variable and should be "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ public enum Privilege {
SHOW_VIEW_PRIV
};

public static final Privilege[] notBelongToTablePrivileges = {
USAGE_PRIV
};

public static Map<Privilege, String> privInDorisToMysql =
ImmutableMap.<Privilege, String>builder() // No NODE_PRIV and ADMIN_PRIV in the mysql
.put(SELECT_PRIV, "SELECT")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,10 +366,6 @@ public void testSwitchCommand() throws Exception {
SwitchStmt switchHive = (SwitchStmt) parseAndAnalyzeStmt("switch hive;", user2Ctx);
env.changeCatalog(user2Ctx, switchHive.getCatalogName());
Assert.assertEquals(user2Ctx.getDefaultCatalog(), "hive");
// user2 can grant select_priv to tpch.customer
GrantStmt user2GrantHiveTable = (GrantStmt) parseAndAnalyzeStmt(
"grant select_priv on tpch.customer to 'user2'@'%';", user2Ctx);
auth.grant(user2GrantHiveTable);

showCatalogSql = "SHOW CATALOGS";
showStmt = (ShowCatalogStmt) parseAndAnalyzeStmt(showCatalogSql);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1953,13 +1953,15 @@ public void testResource() throws UserException {
TablePattern tablePattern = new TablePattern("db1", "*");
GrantStmt grantStmt2 = new GrantStmt(userIdentity, null, tablePattern, usagePrivileges);
ExceptionChecker.expectThrowsWithMsg(AnalysisException.class,
"Can not grant/revoke USAGE_PRIV to/from database or table", () -> grantStmt2.analyze(analyzer));
"Can not grant/revoke Usage_priv to/from any other users or roles",
() -> grantStmt2.analyze(analyzer));

// 3. grant resource prov to role on db.table
tablePattern = new TablePattern("db1", "*");
GrantStmt grantStmt3 = new GrantStmt(userIdentity, "test_role", tablePattern, usagePrivileges);
ExceptionChecker.expectThrowsWithMsg(AnalysisException.class,
"Can not grant/revoke USAGE_PRIV to/from database or table", () -> grantStmt3.analyze(analyzer));
"Can not grant/revoke Usage_priv to/from any other users or roles",
() -> grantStmt3.analyze(analyzer));

// 4.drop user
dropUser(userIdentity);
Expand Down Expand Up @@ -2255,13 +2257,14 @@ public void testWorkloadGroupPriv() throws UserException {
TablePattern tablePattern = new TablePattern("db1", "*");
GrantStmt grantStmt2 = new GrantStmt(userIdentity, null, tablePattern, usagePrivileges);
ExceptionChecker.expectThrowsWithMsg(AnalysisException.class,
"Can not grant/revoke USAGE_PRIV to/from database or table", () -> grantStmt2.analyze(analyzer));
"Can not grant/revoke Usage_priv to/from any other users or roles", () -> grantStmt2.analyze(analyzer));

// 3. grant workload group prov to role on db.table
tablePattern = new TablePattern("db1", "*");
GrantStmt grantStmt3 = new GrantStmt(userIdentity, "test_role", tablePattern, usagePrivileges);
ExceptionChecker.expectThrowsWithMsg(AnalysisException.class,
"Can not grant/revoke USAGE_PRIV to/from database or table", () -> grantStmt3.analyze(analyzer));
"Can not grant/revoke Usage_priv to/from any other users or roles", () -> grantStmt3.analyze(analyzer));

// 4.drop user
dropUser(userIdentity);

Expand Down
Loading

0 comments on commit ff5a4cb

Please sign in to comment.