Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[enhance](auth)when assigning permissions, the current user must have corresponding permissions (#32825) #33948

Merged
merged 2 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading