This is an automated email from the ASF dual-hosted git repository. kxiao pushed a commit to branch branch-2.0 in repository https://gitbox.apache.org/repos/asf/doris.git
commit d3280b20ef2ee598f18a6febce4dd87cc51c35c0 Author: zhangdong <[email protected]> AuthorDate: Tue Aug 15 15:32:51 2023 +0800 [feature](auth)support Col auth (#22629) support GRANT privilege[(col1,col2...)] [, privilege] ON db.tbl TO user_identity [ROLE 'role']; --- fe/fe-core/src/main/cup/sql_parser.cup | 14 +- .../java/org/apache/doris/analysis/GrantStmt.java | 100 ++++++++--- .../java/org/apache/doris/analysis/RevokeStmt.java | 55 ++++--- .../org/apache/doris/catalog/AccessPrivilege.java | 32 ++-- .../doris/catalog/AccessPrivilegeWithCols.java | 87 ++++++++++ .../org/apache/doris/common/proc/AuthProcDir.java | 3 +- .../org/apache/doris/mysql/privilege/Auth.java | 71 ++++++-- .../doris/mysql/privilege/ColPrivilegeKey.java | 98 +++++++++++ .../apache/doris/mysql/privilege/PrivBitSet.java | 3 +- .../doris/mysql/privilege/PrivPredicate.java | 16 +- .../org/apache/doris/mysql/privilege/Role.java | 134 ++++++++++++++- .../apache/doris/mysql/privilege/RoleManager.java | 6 +- .../java/org/apache/doris/persist/PrivInfo.java | 12 +- .../org/apache/doris/analysis/GrantStmtTest.java | 9 +- .../doris/cooldown/CooldownConfHandlerTest.java | 3 +- .../org/apache/doris/mysql/privilege/AuthTest.java | 182 +++++++++++++++++---- .../nereids/rules/analysis/CheckRowPolicyTest.java | 6 +- .../org/apache/doris/persist/PrivInfoTest.java | 2 +- .../java/org/apache/doris/policy/PolicyTest.java | 7 +- 19 files changed, 713 insertions(+), 127 deletions(-) diff --git a/fe/fe-core/src/main/cup/sql_parser.cup b/fe/fe-core/src/main/cup/sql_parser.cup index a15bbc5a3c..7c96ca0cb4 100644 --- a/fe/fe-core/src/main/cup/sql_parser.cup +++ b/fe/fe-core/src/main/cup/sql_parser.cup @@ -35,6 +35,7 @@ import org.apache.doris.analysis.SetOperationStmt.Operation; import org.apache.doris.analysis.SetOperationStmt.SetOperand; import org.apache.doris.analysis.LoadType; import org.apache.doris.catalog.AccessPrivilege; +import org.apache.doris.catalog.AccessPrivilegeWithCols; import org.apache.doris.catalog.AggregateType; import org.apache.doris.catalog.KeysType; import org.apache.doris.catalog.PrimitiveType; @@ -838,10 +839,10 @@ nonterminal List<AllPartitionDesc> all_partition_desc_list; nonterminal PartitionKeyDesc fixed_multi_partition_key_desc; nonterminal MultiPartitionDesc multi_partition_desc; -nonterminal List<AccessPrivilege> privilege_list; +nonterminal List<AccessPrivilegeWithCols> privilege_list; nonterminal List<String> string_list; nonterminal List<Long> integer_list, cancel_rollup_job_id_list; -nonterminal AccessPrivilege privilege_type; +nonterminal AccessPrivilegeWithCols privilege_type; nonterminal DataDescription data_desc, mysql_data_desc; nonterminal List<DataDescription> data_desc_list; @@ -7004,16 +7005,17 @@ column_slice ::= ; privilege_type ::= - ident:name + ident:name opt_col_list:cols {: - RESULT = AccessPrivilege.fromName(name); - if (RESULT == null) { + AccessPrivilege accessPrivilege = AccessPrivilege.fromName(name); + if (accessPrivilege == null) { throw new AnalysisException("Unknown privilege type " + name); } + RESULT = new AccessPrivilegeWithCols(accessPrivilege, cols); :} | KW_ALL:id {: - RESULT = AccessPrivilege.ALL; + RESULT = new AccessPrivilegeWithCols(AccessPrivilege.ALL); :} ; diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java index 7608218397..aa0c21f0f0 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java @@ -17,7 +17,7 @@ package org.apache.doris.analysis; -import org.apache.doris.catalog.AccessPrivilege; +import org.apache.doris.catalog.AccessPrivilegeWithCols; import org.apache.doris.catalog.Env; import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.AnalysisException; @@ -26,19 +26,26 @@ import org.apache.doris.common.ErrorReport; import org.apache.doris.common.FeNameFormat; import org.apache.doris.common.UserException; import org.apache.doris.mysql.privilege.Auth.PrivLevel; -import org.apache.doris.mysql.privilege.PrivBitSet; +import org.apache.doris.mysql.privilege.ColPrivilegeKey; 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.Maps; +import com.google.common.collect.Sets; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; // GRANT STMT -// GRANT privilege [, privilege] ON db.tbl TO user_identity [ROLE 'role']; +// GRANT privilege[(col1,col2...)] [, privilege] ON db.tbl TO user_identity [ROLE 'role']; // GRANT privilege [, privilege] ON RESOURCE 'resource' TO user_identity [ROLE 'role']; // GRANT role [, role] TO user_identity public class GrantStmt extends DdlStmt { @@ -48,21 +55,27 @@ public class GrantStmt extends DdlStmt { private TablePattern tblPattern; private ResourcePattern resourcePattern; private WorkloadGroupPattern workloadGroupPattern; - private List<Privilege> privileges; + private Set<Privilege> privileges = Sets.newHashSet(); + //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, + // with the column permissions section placed in "colPrivileges" and the others in "privileges" + private List<AccessPrivilegeWithCols> accessPrivileges; - public GrantStmt(UserIdentity userIdent, String role, TablePattern tblPattern, List<AccessPrivilege> privileges) { + public GrantStmt(UserIdentity userIdent, String role, TablePattern tblPattern, + List<AccessPrivilegeWithCols> privileges) { this(userIdent, role, tblPattern, null, null, privileges); } public GrantStmt(UserIdentity userIdent, String role, - ResourcePattern resourcePattern, List<AccessPrivilege> privileges) { + ResourcePattern resourcePattern, List<AccessPrivilegeWithCols> privileges) { this(userIdent, role, null, resourcePattern, null, privileges); } public GrantStmt(UserIdentity userIdent, String role, - WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilege> privileges) { + WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilegeWithCols> privileges) { this(userIdent, role, null, null, workloadGroupPattern, privileges); } @@ -72,17 +85,13 @@ public class GrantStmt extends DdlStmt { } private GrantStmt(UserIdentity userIdent, String role, TablePattern tblPattern, ResourcePattern resourcePattern, - WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilege> privileges) { + WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilegeWithCols> accessPrivileges) { this.userIdent = userIdent; this.role = role; this.tblPattern = tblPattern; this.resourcePattern = resourcePattern; this.workloadGroupPattern = workloadGroupPattern; - PrivBitSet privs = PrivBitSet.of(); - for (AccessPrivilege accessPrivilege : privileges) { - privs.or(accessPrivilege.toPaloPrivilege()); - } - this.privileges = privs.toPrivilegeList(); + this.accessPrivileges = accessPrivileges; } public UserIdentity getUserIdent() { @@ -109,7 +118,7 @@ public class GrantStmt extends DdlStmt { return role; } - public List<Privilege> getPrivileges() { + public Set<Privilege> getPrivileges() { return privileges; } @@ -117,6 +126,10 @@ public class GrantStmt extends DdlStmt { return roles; } + public Map<ColPrivilegeKey, Set<String>> getColPrivileges() { + return colPrivileges; + } + @Override public void analyze(Analyzer analyzer) throws UserException { super.analyze(analyzer); @@ -141,12 +154,21 @@ public class GrantStmt extends DdlStmt { } } - if (CollectionUtils.isEmpty(privileges) && CollectionUtils.isEmpty(roles)) { + + if (!CollectionUtils.isEmpty(accessPrivileges)) { + checkAccessPrivileges(accessPrivileges); + + for (AccessPrivilegeWithCols accessPrivilegeWithCols : accessPrivileges) { + accessPrivilegeWithCols.transferAccessPrivilegeToDoris(privileges, colPrivileges, tblPattern); + } + } + + if (CollectionUtils.isEmpty(privileges) && CollectionUtils.isEmpty(roles) && MapUtils.isEmpty(colPrivileges)) { throw new AnalysisException("No privileges or roles in grant statement."); } if (tblPattern != null) { - checkTablePrivileges(privileges, role, tblPattern); + checkTablePrivileges(privileges, role, tblPattern, colPrivileges); } else if (resourcePattern != null) { checkResourcePrivileges(privileges, role, resourcePattern); } else if (workloadGroupPattern != null) { @@ -156,6 +178,16 @@ public class GrantStmt extends DdlStmt { } } + public static void checkAccessPrivileges( + List<AccessPrivilegeWithCols> accessPrivileges) throws AnalysisException { + for (AccessPrivilegeWithCols access : accessPrivileges) { + if (!access.getAccessPrivilege().canHasColPriv() && !CollectionUtils.isEmpty(access.getCols())) { + throw new AnalysisException( + String.format("%s do not support col auth.", access.getAccessPrivilege().name())); + } + } + } + /** * Rules: * 1. ADMIN_PRIV and NODE_PRIV can only be granted/revoked on GLOBAL level @@ -173,7 +205,8 @@ public class GrantStmt extends DdlStmt { * @param tblPattern * @throws AnalysisException */ - public static void checkTablePrivileges(List<Privilege> privileges, String role, TablePattern tblPattern) + public static void checkTablePrivileges(Collection<Privilege> privileges, String role, TablePattern tblPattern, + Map<ColPrivilegeKey, Set<String>> colPrivileges) throws AnalysisException { // Rule 1 if (tblPattern.getPrivLevel() != PrivLevel.GLOBAL && (privileges.contains(Privilege.ADMIN_PRIV) @@ -223,9 +256,14 @@ public class GrantStmt extends DdlStmt { if (privileges.contains(Privilege.USAGE_PRIV)) { throw new AnalysisException("Can not grant/revoke USAGE_PRIV to/from database or table"); } + + // Rule 7 + if (!MapUtils.isEmpty(colPrivileges) && "*".equals(tblPattern.getTbl())) { + throw new AnalysisException("Col auth must specify specific table"); + } } - public static void checkResourcePrivileges(List<Privilege> privileges, String role, + 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])) { @@ -256,7 +294,7 @@ public class GrantStmt extends DdlStmt { } } - public static void checkWorkloadGroupPrivileges(List<Privilege> privileges, String role, + 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])) { @@ -283,16 +321,34 @@ public class GrantStmt extends DdlStmt { } } + public static String colPrivMapToString(Map<ColPrivilegeKey, Set<String>> colPrivileges) { + if (MapUtils.isEmpty(colPrivileges)) { + return ""; + } + StringBuilder builder = new StringBuilder(); + for (Entry<ColPrivilegeKey, Set<String>> entry : colPrivileges.entrySet()) { + builder.append(entry.getKey().getPrivilege()); + builder.append("("); + builder.append(Joiner.on(", ").join(entry.getValue())); + builder.append(")"); + builder.append(","); + } + return builder.deleteCharAt(builder.length() - 1).toString(); + } + @Override public String toSql() { StringBuilder sb = new StringBuilder(); sb.append("GRANT "); - if (privileges != null) { + if (!CollectionUtils.isEmpty(privileges)) { sb.append(Joiner.on(", ").join(privileges)); - } else { + } + if (!MapUtils.isEmpty(colPrivileges)) { + sb.append(colPrivMapToString(colPrivileges)); + } + if (!CollectionUtils.isEmpty(roles)) { sb.append(Joiner.on(", ").join(roles)); } - if (tblPattern != null) { sb.append(" ON ").append(tblPattern).append(" TO "); } else if (resourcePattern != null) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java index e008e9a8a8..74e9a4de91 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java @@ -17,23 +17,27 @@ package org.apache.doris.analysis; -import org.apache.doris.catalog.AccessPrivilege; +import org.apache.doris.catalog.AccessPrivilegeWithCols; import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.FeNameFormat; -import org.apache.doris.mysql.privilege.PrivBitSet; +import org.apache.doris.mysql.privilege.ColPrivilegeKey; import org.apache.doris.mysql.privilege.Privilege; import com.google.common.base.Joiner; import com.google.common.base.Strings; +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.List; +import java.util.Map; +import java.util.Set; // REVOKE STMT // revoke privilege from some user, this is an administrator operation. -// -// REVOKE privilege [, privilege] ON db.tbl FROM user_identity [ROLE 'role']; +// REVOKE privilege[(col1,col2...)] [, privilege] ON db.tbl FROM user_identity [ROLE 'role']; // REVOKE privilege [, privilege] ON resource 'resource' FROM user_identity [ROLE 'role']; // REVOKE role [, role] FROM user_identity public class RevokeStmt extends DdlStmt { @@ -43,21 +47,24 @@ public class RevokeStmt extends DdlStmt { private TablePattern tblPattern; private ResourcePattern resourcePattern; private WorkloadGroupPattern workloadGroupPattern; - private List<Privilege> privileges; + private Set<Privilege> privileges = Sets.newHashSet(); + private Map<ColPrivilegeKey, Set<String>> colPrivileges = Maps.newHashMap(); // Indicates that these roles are revoked from a user private List<String> roles; + List<AccessPrivilegeWithCols> accessPrivileges; - public RevokeStmt(UserIdentity userIdent, String role, TablePattern tblPattern, List<AccessPrivilege> privileges) { + public RevokeStmt(UserIdentity userIdent, String role, TablePattern tblPattern, + List<AccessPrivilegeWithCols> privileges) { this(userIdent, role, tblPattern, null, null, privileges); } public RevokeStmt(UserIdentity userIdent, String role, - ResourcePattern resourcePattern, List<AccessPrivilege> privileges) { + ResourcePattern resourcePattern, List<AccessPrivilegeWithCols> privileges) { this(userIdent, role, null, resourcePattern, null, privileges); } public RevokeStmt(UserIdentity userIdent, String role, - WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilege> privileges) { + WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilegeWithCols> privileges) { this(userIdent, role, null, null, workloadGroupPattern, privileges); } @@ -67,17 +74,13 @@ public class RevokeStmt extends DdlStmt { } private RevokeStmt(UserIdentity userIdent, String role, TablePattern tblPattern, ResourcePattern resourcePattern, - WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilege> privileges) { + WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilegeWithCols> accessPrivileges) { this.userIdent = userIdent; this.role = role; this.tblPattern = tblPattern; this.resourcePattern = resourcePattern; this.workloadGroupPattern = workloadGroupPattern; - PrivBitSet privs = PrivBitSet.of(); - for (AccessPrivilege accessPrivilege : privileges) { - privs.or(accessPrivilege.toPaloPrivilege()); - } - this.privileges = privs.toPrivilegeList(); + this.accessPrivileges = accessPrivileges; } public UserIdentity getUserIdent() { @@ -100,7 +103,7 @@ public class RevokeStmt extends DdlStmt { return role; } - public List<Privilege> getPrivileges() { + public Set<Privilege> getPrivileges() { return privileges; } @@ -108,6 +111,10 @@ public class RevokeStmt extends DdlStmt { return roles; } + public Map<ColPrivilegeKey, Set<String>> getColPrivileges() { + return colPrivileges; + } + @Override public void analyze(Analyzer analyzer) throws AnalysisException { if (userIdent != null) { @@ -130,14 +137,20 @@ public class RevokeStmt extends DdlStmt { roles.set(i, ClusterNamespace.getFullName(analyzer.getClusterName(), originalRoleName)); } } + if (!CollectionUtils.isEmpty(accessPrivileges)) { + GrantStmt.checkAccessPrivileges(accessPrivileges); - if (CollectionUtils.isEmpty(privileges) && CollectionUtils.isEmpty(roles)) { + for (AccessPrivilegeWithCols accessPrivilegeWithCols : accessPrivileges) { + accessPrivilegeWithCols.transferAccessPrivilegeToDoris(privileges, colPrivileges, tblPattern); + } + } + if (CollectionUtils.isEmpty(privileges) && CollectionUtils.isEmpty(roles) && MapUtils.isEmpty(colPrivileges)) { throw new AnalysisException("No privileges or roles in revoke statement."); } // Revoke operation obey the same rule as Grant operation. reuse the same method if (tblPattern != null) { - GrantStmt.checkTablePrivileges(privileges, role, tblPattern); + GrantStmt.checkTablePrivileges(privileges, role, tblPattern, colPrivileges); } else if (resourcePattern != null) { GrantStmt.checkResourcePrivileges(privileges, role, resourcePattern); } else if (workloadGroupPattern != null) { @@ -151,9 +164,13 @@ public class RevokeStmt extends DdlStmt { public String toSql() { StringBuilder sb = new StringBuilder(); sb.append("REVOKE "); - if (privileges != null) { + if (!CollectionUtils.isEmpty(privileges)) { sb.append(Joiner.on(", ").join(privileges)); - } else { + } + if (!MapUtils.isEmpty(colPrivileges)) { + sb.append(GrantStmt.colPrivMapToString(colPrivileges)); + } + if (!CollectionUtils.isEmpty(roles)) { sb.append(Joiner.on(", ").join(roles)); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilege.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilege.java index 246805f08a..88c6c9649b 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilege.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilege.java @@ -17,10 +17,10 @@ package org.apache.doris.catalog; -import org.apache.doris.mysql.privilege.PrivBitSet; import org.apache.doris.mysql.privilege.Privilege; import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; import java.util.List; @@ -48,39 +48,43 @@ public enum AccessPrivilege { this.desc = desc; } - public PrivBitSet toPaloPrivilege() { + public List<Privilege> toDorisPrivilege() { Preconditions.checkState(flag > 0 && flag < 13); switch (flag) { case 1: - return PrivBitSet.of(Privilege.SELECT_PRIV); + case 6: + return Lists.newArrayList(Privilege.SELECT_PRIV); case 2: case 3: - return PrivBitSet.of(Privilege.SELECT_PRIV, Privilege.LOAD_PRIV, + return Lists.newArrayList(Privilege.SELECT_PRIV, Privilege.LOAD_PRIV, Privilege.ALTER_PRIV, Privilege.CREATE_PRIV, Privilege.DROP_PRIV); case 4: - return PrivBitSet.of(Privilege.NODE_PRIV); + return Lists.newArrayList(Privilege.NODE_PRIV); case 5: - return PrivBitSet.of(Privilege.GRANT_PRIV); - case 6: - return PrivBitSet.of(Privilege.SELECT_PRIV); + return Lists.newArrayList(Privilege.GRANT_PRIV); case 7: - return PrivBitSet.of(Privilege.LOAD_PRIV); + return Lists.newArrayList(Privilege.LOAD_PRIV); case 8: - return PrivBitSet.of(Privilege.ALTER_PRIV); + return Lists.newArrayList(Privilege.ALTER_PRIV); case 9: - return PrivBitSet.of(Privilege.CREATE_PRIV); + return Lists.newArrayList(Privilege.CREATE_PRIV); case 10: - return PrivBitSet.of(Privilege.DROP_PRIV); + return Lists.newArrayList(Privilege.DROP_PRIV); case 11: - return PrivBitSet.of(Privilege.ADMIN_PRIV); + return Lists.newArrayList(Privilege.ADMIN_PRIV); case 12: - return PrivBitSet.of(Privilege.USAGE_PRIV); + return Lists.newArrayList(Privilege.USAGE_PRIV); default: return null; } } + // Used to restrict which permissions support column permissions + public boolean canHasColPriv() { + return this == SELECT_PRIV; + } + public static AccessPrivilege fromName(String privStr) { try { return AccessPrivilege.valueOf(privStr.toUpperCase()); diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilegeWithCols.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilegeWithCols.java new file mode 100644 index 0000000000..9866949ecf --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilegeWithCols.java @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.catalog; + +import org.apache.doris.analysis.TablePattern; +import org.apache.doris.mysql.privilege.ColPrivilegeKey; +import org.apache.doris.mysql.privilege.Privilege; + +import com.google.common.collect.Sets; +import org.apache.commons.collections.CollectionUtils; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class AccessPrivilegeWithCols { + private AccessPrivilege accessPrivilege; + private List<String> cols; + + public AccessPrivilegeWithCols(AccessPrivilege accessPrivilege, List<String> cols) { + this.accessPrivilege = accessPrivilege; + this.cols = cols; + } + + public AccessPrivilegeWithCols(AccessPrivilege accessPrivilege) { + this.accessPrivilege = accessPrivilege; + } + + public AccessPrivilege getAccessPrivilege() { + return accessPrivilege; + } + + public void setAccessPrivilege(AccessPrivilege accessPrivilege) { + this.accessPrivilege = accessPrivilege; + } + + public List<String> getCols() { + return cols; + } + + public void setCols(List<String> cols) { + this.cols = cols; + } + + @Override + public String toString() { + return "AccessPrivilegeWithCols{" + + "accessPrivilege=" + accessPrivilege + + ", cols=" + cols + + '}'; + } + + public void transferAccessPrivilegeToDoris(Set<Privilege> privileges, + Map<ColPrivilegeKey, Set<String>> colPrivileges, TablePattern tblPattern) { + List<Privilege> dorisPrivileges = accessPrivilege.toDorisPrivilege(); + // if has no cols,represents the permissions assigned to the entire table + if (CollectionUtils.isEmpty(cols)) { + privileges.addAll(dorisPrivileges); + } else { + // if has cols, represents the permissions assigned to the cols + for (Privilege privilege : dorisPrivileges) { + ColPrivilegeKey colPrivilegeKey = new ColPrivilegeKey(privilege, tblPattern.getQualifiedCtl(), + tblPattern.getQualifiedDb(), tblPattern.getTbl()); + if (colPrivileges.containsKey(colPrivilegeKey)) { + colPrivileges.get(colPrivilegeKey).addAll(cols); + } else { + colPrivileges.put(colPrivilegeKey, Sets.newHashSet(cols)); + } + } + } + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java b/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java index e4cc0c96ec..6adc86ee26 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java +++ b/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java @@ -32,7 +32,8 @@ import com.google.common.collect.ImmutableList; public class AuthProcDir implements ProcDirInterface { public static final ImmutableList<String> TITLE_NAMES = new ImmutableList.Builder<String>() .add("UserIdentity").add("Password").add("Roles").add("GlobalPrivs").add("CatalogPrivs") - .add("DatabasePrivs").add("TablePrivs").add("ResourcePrivs").add("WorkloadGroupPrivs").build(); + .add("DatabasePrivs").add("TablePrivs").add("ColPrivs").add("ResourcePrivs").add("WorkloadGroupPrivs") + .build(); private Auth auth; 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 274ccb56bf..6e249516f2 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 @@ -67,6 +67,7 @@ import com.google.common.base.Joiner; import com.google.common.base.Preconditions; 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.logging.log4j.LogManager; @@ -332,17 +333,29 @@ public class Auth implements Writable { } // ==== Column ==== + // The reason why this method throws an exception instead of returning a boolean is to + // indicate which col does not have permission public void checkColsPriv(UserIdentity currentUser, String ctl, String db, String tbl, Set<String> cols, PrivPredicate wanted) throws AuthorizationException { - // TODO: Support column priv - // we check if have tbl priv,until internal support col auth. - if (!checkTblPriv(currentUser, ctl, db, tbl, wanted)) { - throw new AuthorizationException(String.format( - "Permission denied: user [%s] does not have privilege for [%s] command on [%s].[%s].[%s]", - currentUser, wanted, ctl, db, tbl)); + Set<Role> roles = getRolesByUserWithLdap(currentUser); + for (String col : cols) { + if (!checkColPriv(ctl, db, tbl, col, wanted, roles)) { + throw new AuthorizationException(String.format( + "Permission denied: user [%s] does not have privilege for [%s] command on [%s].[%s].[%s].[%s]", + currentUser, wanted, ctl, db, tbl, col)); + } } } + private boolean checkColPriv(String ctl, String db, String tbl, + String col, PrivPredicate wanted, Set<Role> roles) { + for (Role role : roles) { + if (role.checkColPriv(ctl, db, tbl, col, wanted)) { + return true; + } + } + return false; + } // ==== Resource ==== public boolean checkResourcePriv(UserIdentity currentUser, String resourceName, PrivPredicate wanted) { @@ -520,7 +533,7 @@ public class Auth implements Writable { if (stmt.getTblPattern() != null) { PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges()); grantInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getTblPattern(), privs, - true /* err on non exist */, false /* not replay */); + stmt.getColPrivileges(), true /* err on non exist */, false /* not replay */); } else if (stmt.getResourcePattern() != null) { PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges()); grantInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getResourcePattern(), privs, @@ -538,7 +551,7 @@ public class Auth implements Writable { try { if (privInfo.getTblPattern() != null) { grantInternal(privInfo.getUserIdent(), privInfo.getRole(), - privInfo.getTblPattern(), privInfo.getPrivs(), + privInfo.getTblPattern(), privInfo.getPrivs(), privInfo.getColPrivileges(), true /* err on non exist */, true /* is replay */); } else if (privInfo.getResourcePattern() != null) { grantInternal(privInfo.getUserIdent(), privInfo.getRole(), @@ -558,8 +571,8 @@ public class Auth implements Writable { // grant for TablePattern //if no role,role is default role of userIdent - private void grantInternal(UserIdentity userIdent, String role, TablePattern tblPattern, - PrivBitSet privs, boolean errOnNonExist, boolean isReplay) + private void grantInternal(UserIdentity userIdent, String role, TablePattern tblPattern, PrivBitSet privs, + Map<ColPrivilegeKey, Set<String>> colPrivileges, boolean errOnNonExist, boolean isReplay) throws DdlException { writeLock(); try { @@ -569,10 +582,10 @@ public class Auth implements Writable { } role = roleManager.getUserDefaultRoleName(userIdent); } - Role newRole = new Role(role, tblPattern, privs); + Role newRole = new Role(role, tblPattern, privs, colPrivileges); roleManager.addOrMergeRole(newRole, false /* err on exist */); if (!isReplay) { - PrivInfo info = new PrivInfo(userIdent, tblPattern, privs, null, role); + PrivInfo info = new PrivInfo(userIdent, tblPattern, privs, null, role, colPrivileges); Env.getCurrentEnv().getEditLog().logGrantPriv(info); } LOG.info("finished to grant privilege. is replay: {}", isReplay); @@ -672,7 +685,7 @@ public class Auth implements Writable { if (stmt.getTblPattern() != null) { PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges()); revokeInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getTblPattern(), privs, - true /* err on non exist */, false /* is replay */); + stmt.getColPrivileges(), true /* err on non exist */, false /* is replay */); } else if (stmt.getResourcePattern() != null) { PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges()); revokeInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getResourcePattern(), privs, @@ -690,7 +703,7 @@ public class Auth implements Writable { try { if (info.getTblPattern() != null) { revokeInternal(info.getUserIdent(), info.getRole(), info.getTblPattern(), info.getPrivs(), - true /* err on non exist */, true /* is replay */); + info.getColPrivileges(), true /* err on non exist */, true /* is replay */); } else if (info.getResourcePattern() != null) { revokeInternal(info.getUserIdent(), info.getRole(), info.getResourcePattern(), info.getPrivs(), true /* err on non exist */, true /* is replay */); @@ -706,17 +719,18 @@ public class Auth implements Writable { } private void revokeInternal(UserIdentity userIdent, String role, TablePattern tblPattern, - PrivBitSet privs, boolean errOnNonExist, boolean isReplay) throws DdlException { + PrivBitSet privs, Map<ColPrivilegeKey, Set<String>> colPrivileges, boolean errOnNonExist, boolean isReplay) + throws DdlException { writeLock(); try { if (role == null) { role = roleManager.getUserDefaultRoleName(userIdent); } // revoke privs from role - roleManager.revokePrivs(role, tblPattern, privs, errOnNonExist); + roleManager.revokePrivs(role, tblPattern, privs, colPrivileges, errOnNonExist); if (!isReplay) { - PrivInfo info = new PrivInfo(userIdent, tblPattern, privs, null, role); + PrivInfo info = new PrivInfo(userIdent, tblPattern, privs, null, role, colPrivileges); Env.getCurrentEnv().getEditLog().logRevokePriv(info); } LOG.info("finished to revoke privilege. is replay: {}", isReplay); @@ -1155,6 +1169,19 @@ public class Auth implements Writable { userAuthInfo.add(Joiner.on("; ").join(tblPrivs)); } + // col + List<String> colPrivs = Lists.newArrayList(); + for (Entry<ColPrivilegeKey, Set<String>> entry : getUserColPrivMap(userIdent).entrySet()) { + colPrivs.add(String.format("%s.%s.%s: %s%s", entry.getKey().getCtl(), entry.getKey().getDb(), + entry.getKey().getTbl(), entry.getKey().getPrivilege(), entry.getValue())); + } + + if (colPrivs.isEmpty()) { + userAuthInfo.add(FeConstants.null_string); + } else { + userAuthInfo.add(Joiner.on("; ").join(colPrivs)); + } + // resource List<String> resourcePrivs = Lists.newArrayList(); for (PrivEntry entry : getUserResourcePrivTable(userIdent).entries) { @@ -1222,6 +1249,16 @@ public class Auth implements Writable { return table; } + private Map<ColPrivilegeKey, Set<String>> getUserColPrivMap(UserIdentity userIdentity) { + Map<ColPrivilegeKey, Set<String>> colPrivMap = Maps.newHashMap(); + Set<Role> roles = getRolesByUserWithLdap(userIdentity); + for (Role role : roles) { + Role.mergeColPrivMap(colPrivMap, role.getColPrivMap()); + } + return colPrivMap; + } + + private ResourcePrivTable getUserResourcePrivTable(UserIdentity userIdentity) { ResourcePrivTable table = new ResourcePrivTable(); Set<Role> roles = getRolesByUserWithLdap(userIdentity); diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/ColPrivilegeKey.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/ColPrivilegeKey.java new file mode 100644 index 0000000000..9d4b791cb0 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/ColPrivilegeKey.java @@ -0,0 +1,98 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.mysql.privilege; + +import org.apache.doris.cluster.ClusterNamespace; + +import com.google.common.base.Objects; +import com.google.gson.annotations.SerializedName; + +public class ColPrivilegeKey { + + @SerializedName(value = "privilegeIdx") + private int privilegeIdx; + @SerializedName(value = "ctl") + private String ctl; + @SerializedName(value = "db") + private String db; + @SerializedName(value = "tbl") + private String tbl; + + public ColPrivilegeKey(Privilege privilege, String ctl, String db, String tbl) { + this.privilegeIdx = privilege.getIdx(); + this.ctl = ClusterNamespace.getNameFromFullName(ctl); + this.db = ClusterNamespace.getNameFromFullName(db); + this.tbl = ClusterNamespace.getNameFromFullName(tbl); + } + + public Privilege getPrivilege() { + return Privilege.getPriv(privilegeIdx); + } + + public int getPrivilegeIdx() { + return privilegeIdx; + } + + public void setPrivilegeIdx(int privilegeIdx) { + this.privilegeIdx = privilegeIdx; + } + + public String getCtl() { + return ctl; + } + + public void setCtl(String ctl) { + this.ctl = ctl; + } + + public String getDb() { + return db; + } + + public void setDb(String db) { + this.db = db; + } + + public String getTbl() { + return tbl; + } + + public void setTbl(String tbl) { + this.tbl = tbl; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ColPrivilegeKey that = (ColPrivilegeKey) o; + return privilegeIdx == that.privilegeIdx + && Objects.equal(ctl, that.ctl) + && Objects.equal(db, that.db) + && Objects.equal(tbl, that.tbl); + } + + @Override + public int hashCode() { + return Objects.hashCode(privilegeIdx, ctl, db, tbl); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java index 940a331fa9..f06646526e 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java @@ -31,6 +31,7 @@ import com.google.gson.annotations.SerializedName; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.util.Collection; import java.util.List; // ....0000000000 @@ -135,7 +136,7 @@ public class PrivBitSet implements Writable { return bitSet; } - public static PrivBitSet of(List<Privilege> privs) { + public static PrivBitSet of(Collection<Privilege> privs) { PrivBitSet bitSet = new PrivBitSet(); for (Privilege priv : privs) { bitSet.set(priv.getIdx()); diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java index 6e8916db2d..61f820f674 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java @@ -19,6 +19,8 @@ package org.apache.doris.mysql.privilege; import org.apache.doris.analysis.CompoundPredicate.Operator; +import java.util.Optional; + public class PrivPredicate { // user can 'see' this meta @@ -34,7 +36,7 @@ public class PrivPredicate { Privilege.USAGE_PRIV), Operator.OR); public static final PrivPredicate SHOW_WORKLOAD_GROUP = PrivPredicate.of(PrivBitSet.of(Privilege.ADMIN_PRIV, - Privilege.USAGE_PRIV), + Privilege.USAGE_PRIV), Operator.OR); // create/drop/alter/show user public static final PrivPredicate GRANT = PrivPredicate.of(PrivBitSet.of(Privilege.ADMIN_PRIV, @@ -110,6 +112,18 @@ public class PrivPredicate { return op; } + // Determine which column Privilege correspond to PrivPredicate + //The current logic is to include a SELECT_ PRIV returns SELECT_ PRIV, if load is included_ PRIV returns LOAD_ PRIV, + // the order cannot be reversed + public Optional<Privilege> getColPrivilege() { + if (privs.get(Privilege.SELECT_PRIV.getIdx())) { + return Optional.of(Privilege.SELECT_PRIV); + } else if (privs.get(Privilege.LOAD_PRIV.getIdx())) { + return Optional.of(Privilege.LOAD_PRIV); + } + return Optional.empty(); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); 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 a4cb457f18..025b86fa63 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 @@ -22,6 +22,7 @@ import org.apache.doris.analysis.TablePattern; import org.apache.doris.analysis.UserIdentity; import org.apache.doris.analysis.WorkloadGroupPattern; import org.apache.doris.catalog.Env; +import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.DdlException; import org.apache.doris.common.FeMetaVersion; @@ -37,6 +38,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.gson.annotations.SerializedName; +import org.apache.commons.collections.CollectionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -45,6 +47,8 @@ import java.io.DataOutput; import java.io.IOException; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; import java.util.Set; public class Role implements Writable, GsonPostProcessable { @@ -80,6 +84,8 @@ public class Role implements Writable, GsonPostProcessable { private Map<ResourcePattern, PrivBitSet> resourcePatternToPrivs = Maps.newConcurrentMap(); @SerializedName(value = "workloadGroupPatternToPrivs") private Map<WorkloadGroupPattern, PrivBitSet> workloadGroupPatternToPrivs = Maps.newConcurrentMap(); + @SerializedName(value = "colPrivMap") + private Map<ColPrivilegeKey, Set<String>> colPrivMap = Maps.newHashMap(); // Will not be persisted, generated by tblPatternToPrivs and resourcePatternToPrivs private GlobalPrivTable globalPrivTable = new GlobalPrivTable(); @@ -89,6 +95,7 @@ public class Role implements Writable, GsonPostProcessable { private ResourcePrivTable resourcePrivTable = new ResourcePrivTable(); private WorkloadGroupPrivTable workloadGroupPrivTable = new WorkloadGroupPrivTable(); + @Deprecated private Set<UserIdentity> users = Sets.newConcurrentHashSet(); @@ -111,6 +118,14 @@ public class Role implements Writable, GsonPostProcessable { grantPrivs(tablePattern, privs.copy()); } + public Role(String roleName, TablePattern tablePattern, PrivBitSet privs, + Map<ColPrivilegeKey, Set<String>> colPrivileges) throws DdlException { + this.roleName = roleName; + this.tblPatternToPrivs.put(tablePattern, privs); + grantPrivs(tablePattern, privs.copy()); + grantCols(colPrivileges); + } + public Role(String roleName, ResourcePattern resourcePattern, PrivBitSet privs) throws DdlException { this.roleName = roleName; this.resourcePatternToPrivs.put(resourcePattern, privs); @@ -186,6 +201,18 @@ public class Role implements Writable, GsonPostProcessable { } grantPrivs(entry.getKey(), entry.getValue().copy()); } + mergeColPrivMap(colPrivMap, other.colPrivMap); + } + + public static void mergeColPrivMap(Map<ColPrivilegeKey, Set<String>> toColPrivMap, + Map<ColPrivilegeKey, Set<String>> fromColPrivMap) { + for (Entry<ColPrivilegeKey, Set<String>> entry : fromColPrivMap.entrySet()) { + if (toColPrivMap.containsKey(entry.getKey())) { + toColPrivMap.get(entry.getKey()).addAll(entry.getValue()); + } else { + toColPrivMap.put(entry.getKey(), entry.getValue()); + } + } } public void merge(Role other) throws DdlException { @@ -227,7 +254,8 @@ public class Role implements Writable, GsonPostProcessable { */ private boolean checkAnyPrivWithinCatalog(String ctl) { return dbPrivTable.hasPrivsOfCatalog(ctl) - || tablePrivTable.hasPrivsOfCatalog(ctl); + || tablePrivTable.hasPrivsOfCatalog(ctl) + || checkAnyColPrivWithinCtl(ctl); } @@ -263,8 +291,42 @@ public class Role implements Writable, GsonPostProcessable { * if so, the database should be visible to this user. */ private boolean checkAnyPrivWithinDb(String ctl, String db) { - return tablePrivTable.hasPrivsOfDb(ctl, db); + return tablePrivTable.hasPrivsOfDb(ctl, db) || checkAnyColPrivWithinDb(ctl, db); + + } + + private boolean checkAnyColPrivWithinCtl(String ctl) { + ctl = ClusterNamespace.getNameFromFullName(ctl); + for (ColPrivilegeKey colPrivilegeKey : colPrivMap.keySet()) { + if (colPrivilegeKey.getCtl().equals(ctl)) { + return true; + } + } + return false; + } + private boolean checkAnyColPrivWithinDb(String ctl, String db) { + ctl = ClusterNamespace.getNameFromFullName(ctl); + db = ClusterNamespace.getNameFromFullName(db); + for (ColPrivilegeKey colPrivilegeKey : colPrivMap.keySet()) { + if (colPrivilegeKey.getCtl().equals(ctl) && colPrivilegeKey.getDb().equals(db)) { + return true; + } + } + return false; + } + + private boolean checkAnyColPrivWithinTbl(String ctl, String db, String tbl) { + ctl = ClusterNamespace.getNameFromFullName(ctl); + db = ClusterNamespace.getNameFromFullName(db); + tbl = ClusterNamespace.getNameFromFullName(tbl); + for (ColPrivilegeKey colPrivilegeKey : colPrivMap.keySet()) { + if (colPrivilegeKey.getCtl().equals(ctl) && colPrivilegeKey.getDb().equals(db) && colPrivilegeKey + .getTbl().equals(tbl)) { + return true; + } + } + return false; } private boolean checkDbInternal(String ctl, String db, PrivPredicate wanted, @@ -284,10 +346,32 @@ public class Role implements Writable, GsonPostProcessable { || checkTblInternal(ctl, db, tbl, wanted, savedPrivs)) { return true; } + + // if user has any privs of col in this table, and the wanted priv is SHOW, return true + if (ctl != null && db != null && tbl != null && wanted == PrivPredicate.SHOW && checkAnyColPrivWithinTbl(ctl, + db, tbl)) { + return true; + } + LOG.debug("failed to get wanted privs: {}, granted: {}", wanted, savedPrivs); return false; } + public boolean checkColPriv(String ctl, String db, String tbl, String col, PrivPredicate wanted) { + Optional<Privilege> colPrivilege = wanted.getColPrivilege(); + Preconditions.checkState(colPrivilege.isPresent(), "this privPredicate should not use checkColPriv:" + wanted); + return checkTblPriv(ctl, db, tbl, wanted) || onlyCheckColPriv(ctl, db, tbl, col, colPrivilege.get()); + } + + private boolean onlyCheckColPriv(String ctl, String db, String tbl, String col, + Privilege colPrivilege) { + ColPrivilegeKey colPrivilegeKey = new ColPrivilegeKey(colPrivilege, ctl, db, tbl); + if (colPrivMap.containsKey(colPrivilegeKey)) { + return colPrivMap.get(colPrivilegeKey).contains(col); + } + return false; + } + private boolean checkTblInternal(String ctl, String db, String tbl, PrivPredicate wanted, PrivBitSet savedPrivs) { tablePrivTable.getPrivs(ctl, db, tbl, savedPrivs); return Privilege.satisfy(savedPrivs, wanted); @@ -371,6 +455,10 @@ public class Role implements Writable, GsonPostProcessable { return tablePrivTable; } + public Map<ColPrivilegeKey, Set<String>> getColPrivMap() { + return colPrivMap; + } + public ResourcePrivTable getResourcePrivTable() { return resourcePrivTable; } @@ -396,7 +484,9 @@ public class Role implements Writable, GsonPostProcessable { private void grantPrivs(ResourcePattern resourcePattern, PrivBitSet privs) throws DdlException { - + if (privs.isEmpty()) { + return; + } // grant privs to user switch (resourcePattern.getPrivLevel()) { case GLOBAL: @@ -412,12 +502,31 @@ public class Role implements Writable, GsonPostProcessable { } private void grantPrivs(WorkloadGroupPattern workloadGroupPattern, PrivBitSet privs) throws DdlException { + if (privs.isEmpty()) { + return; + } if (workloadGroupPattern.getPrivLevel().equals(PrivLevel.WORKLOAD_GROUP)) { grantWorkloadGroupPrivs(workloadGroupPattern.getworkloadGroupName(), privs); } } + private void grantCols(Map<ColPrivilegeKey, Set<String>> colPrivileges) { + if (Objects.isNull(colPrivileges)) { + return; + } + for (Entry<ColPrivilegeKey, Set<String>> entry : colPrivileges.entrySet()) { + if (colPrivMap.containsKey(entry.getKey())) { + colPrivMap.get(entry.getKey()).addAll(entry.getValue()); + } else { + colPrivMap.put(entry.getKey(), Sets.newHashSet(entry.getValue())); + } + } + } + private void grantPrivs(TablePattern tblPattern, PrivBitSet privs) throws DdlException { + if (privs.isEmpty()) { + return; + } // grant privs to user switch (tblPattern.getPrivLevel()) { case GLOBAL: @@ -501,7 +610,9 @@ public class Role implements Writable, GsonPostProcessable { workloadGroupPrivTable.addEntry(entry, false, false); } - public void revokePrivs(TablePattern tblPattern, PrivBitSet privs, boolean errOnNonExist) throws DdlException { + public void revokePrivs(TablePattern tblPattern, PrivBitSet privs, Map<ColPrivilegeKey, Set<String>> colPrivileges, + boolean errOnNonExist) + throws DdlException { PrivBitSet existingPriv = tblPatternToPrivs.get(tblPattern); if (existingPriv == null) { if (errOnNonExist) { @@ -511,6 +622,21 @@ public class Role implements Writable, GsonPostProcessable { } existingPriv.remove(privs); revokePrivs(tblPattern, privs); + revokeCols(colPrivileges); + } + + private void revokeCols(Map<ColPrivilegeKey, Set<String>> colPrivileges) { + if (Objects.isNull(colPrivileges)) { + return; + } + for (Entry<ColPrivilegeKey, Set<String>> entry : colPrivileges.entrySet()) { + if (colPrivMap.containsKey(entry.getKey())) { + colPrivMap.get(entry.getKey()).removeAll(entry.getValue()); + if (CollectionUtils.isEmpty(colPrivMap.get(entry.getKey()))) { + colPrivMap.remove(entry.getKey()); + } + } + } } public void revokePrivs(ResourcePattern resourcePattern, PrivBitSet privs, boolean errOnNonExist) 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 285534d297..7b37b6f40e 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 @@ -51,6 +51,7 @@ import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -101,7 +102,8 @@ public class RoleManager implements Writable { roles.remove(qualifiedRole); } - public Role revokePrivs(String name, TablePattern tblPattern, PrivBitSet privs, boolean errOnNonExist) + public Role revokePrivs(String name, TablePattern tblPattern, PrivBitSet privs, + Map<ColPrivilegeKey, Set<String>> colPrivileges, boolean errOnNonExist) throws DdlException { Role existingRole = roles.get(name); if (existingRole == null) { @@ -110,7 +112,7 @@ public class RoleManager implements Writable { } return null; } - existingRole.revokePrivs(tblPattern, privs, errOnNonExist); + existingRole.revokePrivs(tblPattern, privs, colPrivileges, errOnNonExist); return existingRole; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java b/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java index 1b6e448cc8..377356d85c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java +++ b/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java @@ -26,6 +26,7 @@ import org.apache.doris.catalog.Env; import org.apache.doris.common.FeMetaVersion; import org.apache.doris.common.io.Text; import org.apache.doris.common.io.Writable; +import org.apache.doris.mysql.privilege.ColPrivilegeKey; import org.apache.doris.mysql.privilege.PrivBitSet; import org.apache.doris.persist.gson.GsonUtils; @@ -35,6 +36,8 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.List; +import java.util.Map; +import java.util.Set; public class PrivInfo implements Writable { @SerializedName(value = "userIdent") @@ -51,6 +54,8 @@ public class PrivInfo implements Writable { private byte[] passwd; @SerializedName(value = "role") private String role; + @SerializedName(value = "colPrivileges") + private Map<ColPrivilegeKey, Set<String>> colPrivileges; @SerializedName(value = "passwordOptions") private PasswordOptions passwordOptions; // Indicates that these roles are granted to a user @@ -75,7 +80,7 @@ public class PrivInfo implements Writable { // For grant/revoke public PrivInfo(UserIdentity userIdent, TablePattern tablePattern, PrivBitSet privs, - byte[] passwd, String role) { + byte[] passwd, String role, Map<ColPrivilegeKey, Set<String>> colPrivileges) { this.userIdent = userIdent; this.tblPattern = tablePattern; this.resourcePattern = null; @@ -83,6 +88,7 @@ public class PrivInfo implements Writable { this.privs = privs; this.passwd = passwd; this.role = role; + this.colPrivileges = colPrivileges; } // For grant/revoke resource priv @@ -150,6 +156,10 @@ public class PrivInfo implements Writable { return roles; } + public Map<ColPrivilegeKey, Set<String>> getColPrivileges() { + return colPrivileges; + } + public static PrivInfo read(DataInput in) throws IOException { if (Env.getCurrentEnvJournalVersion() < FeMetaVersion.VERSION_113) { PrivInfo info = new PrivInfo(); diff --git a/fe/fe-core/src/test/java/org/apache/doris/analysis/GrantStmtTest.java b/fe/fe-core/src/test/java/org/apache/doris/analysis/GrantStmtTest.java index bead7dae56..37bf2c8bfb 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/analysis/GrantStmtTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/GrantStmtTest.java @@ -18,6 +18,7 @@ package org.apache.doris.analysis; import org.apache.doris.catalog.AccessPrivilege; +import org.apache.doris.catalog.AccessPrivilegeWithCols; import org.apache.doris.catalog.Env; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.UserException; @@ -83,13 +84,13 @@ public class GrantStmtTest { public void testNormal() throws AnalysisException, UserException { GrantStmt stmt; - List<AccessPrivilege> privileges = Lists.newArrayList(AccessPrivilege.ALL); + List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.ALL)); stmt = new GrantStmt(new UserIdentity("testUser", "%"), null, new TablePattern("testDb", "*"), privileges); stmt.analyze(analyzer); Assert.assertEquals("testCluster:testUser", stmt.getUserIdent().getQualifiedUser()); Assert.assertEquals("testCluster:testDb", stmt.getTblPattern().getQualifiedDb()); - privileges = Lists.newArrayList(AccessPrivilege.READ_ONLY, AccessPrivilege.ALL); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.READ_ONLY), new AccessPrivilegeWithCols(AccessPrivilege.ALL)); stmt = new GrantStmt(new UserIdentity("testUser", "%"), null, new TablePattern("testDb", "*"), privileges); stmt.analyze(analyzer); } @@ -97,7 +98,7 @@ public class GrantStmtTest { @Test public void testResourceNormal() throws UserException { String resourceName = "spark0"; - List<AccessPrivilege> privileges = Lists.newArrayList(AccessPrivilege.USAGE_PRIV); + List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.USAGE_PRIV)); GrantStmt stmt = new GrantStmt(new UserIdentity("testUser", "%"), null, new ResourcePattern(resourceName), privileges); stmt.analyze(analyzer); Assert.assertEquals(resourceName, stmt.getResourcePattern().getResourceName()); @@ -113,7 +114,7 @@ public class GrantStmtTest { public void testUserFail() throws AnalysisException, UserException { GrantStmt stmt; - List<AccessPrivilege> privileges = Lists.newArrayList(AccessPrivilege.ALL); + List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.ALL)); stmt = new GrantStmt(new UserIdentity("", "%"), null, new TablePattern("testDb", "*"), privileges); stmt.analyze(analyzer); Assert.fail("No exception throws."); diff --git a/fe/fe-core/src/test/java/org/apache/doris/cooldown/CooldownConfHandlerTest.java b/fe/fe-core/src/test/java/org/apache/doris/cooldown/CooldownConfHandlerTest.java index ea3e239533..5b35321d91 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/cooldown/CooldownConfHandlerTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/cooldown/CooldownConfHandlerTest.java @@ -23,6 +23,7 @@ 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.AccessPrivilegeWithCols; import org.apache.doris.catalog.Database; import org.apache.doris.catalog.Env; import org.apache.doris.catalog.MaterializedIndex; @@ -69,7 +70,7 @@ public class CooldownConfHandlerTest extends TestWithFeService { user.analyze(SystemInfoService.DEFAULT_CLUSTER); CreateUserStmt createUserStmt = new CreateUserStmt(new UserDesc(user)); Env.getCurrentEnv().getAuth().createUser(createUserStmt); - List<AccessPrivilege> privileges = Lists.newArrayList(AccessPrivilege.ADMIN_PRIV); + List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.ADMIN_PRIV)); TablePattern tablePattern = new TablePattern("*", "*", "*"); tablePattern.analyze(SystemInfoService.DEFAULT_CLUSTER); GrantStmt grantStmt = new GrantStmt(user, null, tablePattern, privileges); 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 417060de97..4978304177 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 @@ -30,6 +30,7 @@ import org.apache.doris.analysis.UserDesc; import org.apache.doris.analysis.UserIdentity; import org.apache.doris.analysis.WorkloadGroupPattern; import org.apache.doris.catalog.AccessPrivilege; +import org.apache.doris.catalog.AccessPrivilegeWithCols; import org.apache.doris.catalog.DomainResolver; import org.apache.doris.catalog.Env; import org.apache.doris.common.AnalysisException; @@ -44,6 +45,7 @@ import org.apache.doris.qe.QueryState; import org.apache.doris.system.SystemInfoService; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import mockit.Expectations; import mockit.Mocked; import org.junit.Assert; @@ -373,7 +375,9 @@ public class AuthTest { // 9. grant for cmy@'%' TablePattern tablePattern = new TablePattern("*", "*"); - List<AccessPrivilege> privileges = Lists.newArrayList(AccessPrivilege.CREATE_PRIV, AccessPrivilege.DROP_PRIV); + List<AccessPrivilegeWithCols> privileges = Lists + .newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.CREATE_PRIV), + new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV)); GrantStmt grantStmt = new GrantStmt(new UserIdentity("cmy", "%"), null, tablePattern, privileges); try { @@ -409,7 +413,8 @@ public class AuthTest { // 10. grant auth for non exist user tablePattern = new TablePattern("*", "*"); - privileges = Lists.newArrayList(AccessPrivilege.CREATE_PRIV, AccessPrivilege.DROP_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.CREATE_PRIV), + new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV)); grantStmt = new GrantStmt(new UserIdentity("nouser", "%"), null, tablePattern, privileges); try { @@ -430,7 +435,8 @@ public class AuthTest { // 11. grant auth for user with non exist host tablePattern = new TablePattern("*", "*"); - privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV, AccessPrivilege.DROP_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV), + new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV)); grantStmt = new GrantStmt(new UserIdentity("zhangsan", "%"), null, tablePattern, privileges); try { @@ -451,7 +457,8 @@ public class AuthTest { // 12. grant db auth to exist user tablePattern = new TablePattern("db1", "*"); - privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV, AccessPrivilege.DROP_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV), + new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV)); grantStmt = new GrantStmt(new UserIdentity("zhangsan", "192.%"), null, tablePattern, privileges); try { @@ -482,7 +489,8 @@ public class AuthTest { // 13. grant tbl auth to exist user tablePattern = new TablePattern("db2", "tbl2"); - privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV, AccessPrivilege.DROP_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.ALTER_PRIV), + new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV)); grantStmt = new GrantStmt(new UserIdentity("zhangsan", "192.%"), null, tablePattern, privileges); try { @@ -513,7 +521,7 @@ public class AuthTest { // 13.1 grant external ctl tbl auth to exist user tablePattern = new TablePattern("ext_ctl", "ext_db1", "ext_tbl1"); - privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV)); grantStmt = new GrantStmt(new UserIdentity("zhangsan", "192.%"), null, tablePattern, privileges); try { @@ -545,7 +553,8 @@ public class AuthTest { // 14. grant db auth to zhangsan@['palo.domain1'] tablePattern = new TablePattern("db3", "*"); - privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV, AccessPrivilege.DROP_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.ALTER_PRIV), + new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV)); grantStmt = new GrantStmt(new UserIdentity("zhangsan", "palo.domain1", true), null, tablePattern, privileges); try { @@ -570,7 +579,7 @@ public class AuthTest { PrivPredicate.ALTER)); // 15. grant new auth to exist priv entry (exist ALTER/DROP, add SELECT) tablePattern = new TablePattern("db3", "*"); - privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV)); grantStmt = new GrantStmt(new UserIdentity("zhangsan", "palo.domain1", true), null, tablePattern, privileges); try { @@ -622,7 +631,7 @@ public class AuthTest { // 16. revoke privs from non exist user tablePattern = new TablePattern("*", "*"); - privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV)); RevokeStmt revokeStmt = new RevokeStmt(new UserIdentity("nouser", "%"), null, tablePattern, privileges); try { @@ -643,7 +652,7 @@ public class AuthTest { // 17. revoke privs from non exist host tablePattern = new TablePattern("*", "*"); - privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV)); revokeStmt = new RevokeStmt(new UserIdentity("cmy", "172.%"), null, tablePattern, privileges); try { @@ -664,7 +673,7 @@ public class AuthTest { // 18. revoke privs from non exist db tablePattern = new TablePattern("nodb", "*"); - privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV)); revokeStmt = new RevokeStmt(new UserIdentity("cmy", "%"), null, tablePattern, privileges); try { @@ -685,7 +694,7 @@ public class AuthTest { // 19. revoke privs from user @ ip tablePattern = new TablePattern("*", "*"); - privileges = Lists.newArrayList(AccessPrivilege.CREATE_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.CREATE_PRIV)); revokeStmt = new RevokeStmt(new UserIdentity("cmy", "%"), null, tablePattern, privileges); try { @@ -713,7 +722,7 @@ public class AuthTest { // 19. revoke tbl privs from user @ ip tablePattern = new TablePattern("db2", "tbl2"); - privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.ALTER_PRIV)); revokeStmt = new RevokeStmt(new UserIdentity("zhangsan", "192.%"), null, tablePattern, privileges); try { @@ -749,7 +758,7 @@ public class AuthTest { // 20. revoke privs from non exist user @ domain tablePattern = new TablePattern("db2", "tbl2"); - privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.ALTER_PRIV)); revokeStmt = new RevokeStmt(new UserIdentity("zhangsan", "nodomain", true), null, tablePattern, privileges); try { @@ -770,7 +779,7 @@ public class AuthTest { // 21. revoke privs from non exist db from user @ domain tablePattern = new TablePattern("nodb", "*"); - privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.ALTER_PRIV)); revokeStmt = new RevokeStmt(new UserIdentity("zhangsan", "palo.domain1", true), null, tablePattern, privileges); try { @@ -791,7 +800,7 @@ public class AuthTest { // 22. revoke privs from exist user @ domain tablePattern = new TablePattern("db3", "*"); - privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV)); revokeStmt = new RevokeStmt(new UserIdentity("zhangsan", "palo.domain1", true), null, tablePattern, privileges); try { @@ -920,7 +929,8 @@ public class AuthTest { Assert.assertTrue(hasException); // 25. grant auth to non exist role, will create this new role - privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV, AccessPrivilege.SELECT_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV), + new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV)); grantStmt = new GrantStmt(null, "role2", new TablePattern("*", "*"), privileges); try { grantStmt.analyze(analyzer); @@ -937,7 +947,8 @@ public class AuthTest { } // 26. grant auth to role - privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV, AccessPrivilege.SELECT_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV), + new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV)); grantStmt = new GrantStmt(null, "role1", new TablePattern("*", "*"), privileges); try { grantStmt.analyze(analyzer); @@ -1008,7 +1019,7 @@ public class AuthTest { PrivPredicate.DROP)); // 29. revoke auth on non exist db from role1 - privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV)); revokeStmt = new RevokeStmt(null, "role1", new TablePattern("nodb", "*"), privileges); try { revokeStmt.analyze(analyzer); @@ -1027,7 +1038,7 @@ public class AuthTest { Assert.assertTrue(hasException); // 30. revoke auth from role1 - privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV)); revokeStmt = new RevokeStmt(null, "role1", new TablePattern("*", "*"), privileges); try { revokeStmt.analyze(analyzer); @@ -1337,7 +1348,7 @@ public class AuthTest { createUserStmt.analyze(analyzer); auth.createUser(createUserStmt); - privileges = Lists.newArrayList(AccessPrivilege.NODE_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.NODE_PRIV)); // 40.1 grant to non-global level, which is not allowed grantStmt = new GrantStmt(opUser, null, new TablePattern("db1", "*"), privileges); try { @@ -1425,7 +1436,7 @@ public class AuthTest { e.printStackTrace(); } // Now, we grant grant_priv to opUser, and check if it can than grant node_priv to other user - privileges = Lists.newArrayList(AccessPrivilege.GRANT_PRIV); + privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.GRANT_PRIV)); grantStmt = new GrantStmt(opUser, null, new TablePattern("*", "*"), privileges); try { new Expectations() { @@ -1459,6 +1470,109 @@ public class AuthTest { } } + @Test + public void testColAuth() { + // create user + UserIdentity userIdentity = new UserIdentity("colUser", "%"); + UserDesc userDesc = new UserDesc(userIdentity, "12345", true); + CreateUserStmt createUserStmt = new CreateUserStmt(false, userDesc, null); + try { + createUserStmt.analyze(analyzer); + auth.createUser(createUserStmt); + } catch (UserException e) { + e.printStackTrace(); + Assert.fail(); + } + + // test table is * + GrantStmt grantStmt = new GrantStmt(userIdentity, null, new TablePattern("db1", "*"), Lists.newArrayList( + new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV, Lists.newArrayList("a", "b")))); + try { + grantStmt.analyze(analyzer); + Assert.fail(); + } catch (UserException e) { + e.printStackTrace(); + } + + // test CREATE_PRIV with col + grantStmt = new GrantStmt(userIdentity, null, new TablePattern("db1", "tbl1"), Lists.newArrayList( + new AccessPrivilegeWithCols(AccessPrivilege.CREATE_PRIV, Lists.newArrayList("a", "b")))); + try { + grantStmt.analyze(analyzer); + Assert.fail(); + } catch (UserException e) { + e.printStackTrace(); + } + + // test Select_PRIV with col + grantStmt = new GrantStmt(userIdentity, null, new TablePattern("db1", "tbl1"), Lists.newArrayList( + new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV, Lists.newArrayList("a", "b")))); + try { + grantStmt.analyze(analyzer); + auth.grant(grantStmt); + } catch (UserException e) { + e.printStackTrace(); + Assert.fail(); + } + // check has select priv of column 'a' + try { + accessManager + .checkColumnsPriv(userIdentity, SystemInfoService.DEFAULT_CLUSTER + ":db1", "tbl1", + Sets.newHashSet("a"), PrivPredicate.SELECT); + } catch (UserException e) { + e.printStackTrace(); + Assert.fail(); + } + // check has select priv of column 'c' + try { + accessManager + .checkColumnsPriv(userIdentity, SystemInfoService.DEFAULT_CLUSTER + ":db1", "tbl1", + Sets.newHashSet("c"), PrivPredicate.SELECT); + Assert.fail(); + } catch (UserException e) { + e.printStackTrace(); + } + // check has load priv of column 'a' + try { + accessManager + .checkColumnsPriv(userIdentity, SystemInfoService.DEFAULT_CLUSTER + ":db1", "tbl1", + Sets.newHashSet("a"), PrivPredicate.LOAD); + Assert.fail(); + } catch (UserException e) { + e.printStackTrace(); + } + // check 'create_priv' use checkColumnsPriv + try { + accessManager + .checkColumnsPriv(userIdentity, SystemInfoService.DEFAULT_CLUSTER + ":db1", "tbl1", + Sets.newHashSet("a"), PrivPredicate.CREATE); + Assert.fail(); + } catch (Exception e) { + e.printStackTrace(); + } + + // check show priv on ctl when has col priv + boolean hasPriv = accessManager.checkCtlPriv(userIdentity, Auth.DEFAULT_CATALOG, PrivPredicate.SHOW); + if (!hasPriv) { + Assert.fail(); + } + // check show priv on db when has col priv + hasPriv = accessManager.checkDbPriv(userIdentity, Auth.DEFAULT_CATALOG, "db1", PrivPredicate.SHOW); + if (!hasPriv) { + Assert.fail(); + } + // check show priv on tbl when has col priv + hasPriv = accessManager.checkTblPriv(userIdentity, Auth.DEFAULT_CATALOG, "db1", "tbl1", PrivPredicate.SHOW); + if (!hasPriv) { + Assert.fail(); + } + // check select priv on tbl when has col priv + hasPriv = accessManager.checkTblPriv(userIdentity, Auth.DEFAULT_CATALOG, "db1", "tbl1", PrivPredicate.SELECT); + if (hasPriv) { + Assert.fail(); + } + } + @Test public void testGrantRole() { UserIdentity userIdentity = new UserIdentity("testUser", "%"); @@ -1475,7 +1589,7 @@ public class AuthTest { } // grant select_priv on db 'db1' to role 'role1' GrantStmt grantStmt = new GrantStmt(null, role, new TablePattern("db1", "*"), - Lists.newArrayList(AccessPrivilege.SELECT_PRIV)); + Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV))); try { grantStmt.analyze(analyzer); auth.grant(grantStmt); @@ -1544,7 +1658,8 @@ public class AuthTest { ResourcePattern resourcePattern = new ResourcePattern(resourceName); String anyResource = "*"; ResourcePattern anyResourcePattern = new ResourcePattern(anyResource); - List<AccessPrivilege> usagePrivileges = Lists.newArrayList(AccessPrivilege.USAGE_PRIV); + List<AccessPrivilegeWithCols> usagePrivileges = Lists + .newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.USAGE_PRIV)); UserDesc userDesc = new UserDesc(userIdentity, "12345", true); // ------ grant|revoke resource to|from user ------ @@ -1583,8 +1698,9 @@ public class AuthTest { Assert.assertFalse(accessManager.checkGlobalPriv(userIdentity, PrivPredicate.USAGE)); // 3.1 grant 'notBelongToResourcePrivileges' on resource 'spark0' to 'testUser'@'%' for (int i = 0; i < Privilege.notBelongToResourcePrivileges.length; i++) { - List<AccessPrivilege> notAllowedPrivileges = Lists - .newArrayList(AccessPrivilege.fromName(Privilege.notBelongToResourcePrivileges[i].getName())); + List<AccessPrivilegeWithCols> notAllowedPrivileges = Lists + .newArrayList(new AccessPrivilegeWithCols( + AccessPrivilege.fromName(Privilege.notBelongToResourcePrivileges[i].getName()))); grantStmt = new GrantStmt(userIdentity, null, resourcePattern, notAllowedPrivileges); try { grantStmt.analyze(analyzer); @@ -1791,7 +1907,8 @@ public class AuthTest { } // 1. grant db table priv to resource - List<AccessPrivilege> privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV); + List<AccessPrivilegeWithCols> privileges = Lists + .newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV)); grantStmt = new GrantStmt(userIdentity, null, resourcePattern, privileges); hasException = false; try { @@ -1824,7 +1941,8 @@ public class AuthTest { WorkloadGroupPattern workloadGroupPattern = new WorkloadGroupPattern(workloadGroupName); String anyWorkloadGroup = "%"; WorkloadGroupPattern anyWorkloadGroupPattern = new WorkloadGroupPattern(anyWorkloadGroup); - List<AccessPrivilege> usagePrivileges = Lists.newArrayList(AccessPrivilege.USAGE_PRIV); + List<AccessPrivilegeWithCols> usagePrivileges = Lists + .newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.USAGE_PRIV)); UserDesc userDesc = new UserDesc(userIdentity, "12345", true); // ------ grant|revoke workload group to|from user ------ @@ -1863,8 +1981,9 @@ public class AuthTest { Assert.assertFalse(accessManager.checkGlobalPriv(userIdentity, PrivPredicate.USAGE)); // 3.1 grant 'notBelongToWorkloadGroupPrivileges' for (int i = 0; i < Privilege.notBelongToWorkloadGroupPrivileges.length; i++) { - List<AccessPrivilege> notAllowedPrivileges = Lists.newArrayList( - AccessPrivilege.fromName(Privilege.notBelongToWorkloadGroupPrivileges[i].getName())); + List<AccessPrivilegeWithCols> notAllowedPrivileges = Lists.newArrayList( + new AccessPrivilegeWithCols( + AccessPrivilege.fromName(Privilege.notBelongToWorkloadGroupPrivileges[i].getName()))); grantStmt = new GrantStmt(userIdentity, null, workloadGroupPattern, notAllowedPrivileges); try { grantStmt.analyze(analyzer); @@ -2072,7 +2191,8 @@ public class AuthTest { } // 1. grant db table priv to workload group - List<AccessPrivilege> privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV); + List<AccessPrivilegeWithCols> privileges = Lists + .newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV)); grantStmt = new GrantStmt(userIdentity, null, workloadGroupPattern, privileges); hasException = false; try { diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckRowPolicyTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckRowPolicyTest.java index 47e672db99..1464de74d8 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckRowPolicyTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckRowPolicyTest.java @@ -17,12 +17,14 @@ package org.apache.doris.nereids.rules.analysis; +import org.apache.doris.analysis.Analyzer; import org.apache.doris.analysis.CreateUserStmt; import org.apache.doris.analysis.GrantStmt; 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.AccessPrivilegeWithCols; import org.apache.doris.catalog.AggregateType; import org.apache.doris.catalog.Column; import org.apache.doris.catalog.Database; @@ -89,10 +91,12 @@ public class CheckRowPolicyTest extends TestWithFeService { user.analyze(SystemInfoService.DEFAULT_CLUSTER); CreateUserStmt createUserStmt = new CreateUserStmt(new UserDesc(user)); Env.getCurrentEnv().getAuth().createUser(createUserStmt); - List<AccessPrivilege> privileges = Lists.newArrayList(AccessPrivilege.ADMIN_PRIV); + List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.ADMIN_PRIV)); TablePattern tablePattern = new TablePattern("*", "*", "*"); tablePattern.analyze(SystemInfoService.DEFAULT_CLUSTER); GrantStmt grantStmt = new GrantStmt(user, null, tablePattern, privileges); + Analyzer analyzer = new Analyzer(connectContext.getEnv(), connectContext); + grantStmt.analyze(analyzer); Env.getCurrentEnv().getAuth().grant(grantStmt); } diff --git a/fe/fe-core/src/test/java/org/apache/doris/persist/PrivInfoTest.java b/fe/fe-core/src/test/java/org/apache/doris/persist/PrivInfoTest.java index d45c1e2ad7..5e02244afa 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/persist/PrivInfoTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/persist/PrivInfoTest.java @@ -74,7 +74,7 @@ public class PrivInfoTest { @Test public void testWithTablePattern() throws IOException { PrivInfo privInfo = new PrivInfo(UserIdentity.ROOT, TablePattern.ALL, PrivBitSet.of(Privilege.DROP_PRIV), - new byte[] {'a', 'b', 'c'}, "role"); + new byte[] {'a', 'b', 'c'}, "role", null); // 1. Write objects to file File file = new File("./privInfo"); diff --git a/fe/fe-core/src/test/java/org/apache/doris/policy/PolicyTest.java b/fe/fe-core/src/test/java/org/apache/doris/policy/PolicyTest.java index 2415e0fb3c..b678f33614 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/policy/PolicyTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/policy/PolicyTest.java @@ -17,6 +17,7 @@ package org.apache.doris.policy; +import org.apache.doris.analysis.Analyzer; import org.apache.doris.analysis.CreateUserStmt; import org.apache.doris.analysis.Expr; import org.apache.doris.analysis.GrantStmt; @@ -25,6 +26,7 @@ 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.AccessPrivilegeWithCols; import org.apache.doris.catalog.Env; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.Config; @@ -67,10 +69,13 @@ public class PolicyTest extends TestWithFeService { user.analyze(SystemInfoService.DEFAULT_CLUSTER); CreateUserStmt createUserStmt = new CreateUserStmt(new UserDesc(user)); Env.getCurrentEnv().getAuth().createUser(createUserStmt); - List<AccessPrivilege> privileges = Lists.newArrayList(AccessPrivilege.ADMIN_PRIV); + List<AccessPrivilegeWithCols> privileges = Lists + .newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.ADMIN_PRIV)); TablePattern tablePattern = new TablePattern("*", "*", "*"); tablePattern.analyze(SystemInfoService.DEFAULT_CLUSTER); GrantStmt grantStmt = new GrantStmt(user, null, tablePattern, privileges); + Analyzer analyzer = new Analyzer(connectContext.getEnv(), connectContext); + grantStmt.analyze(analyzer); Env.getCurrentEnv().getAuth().grant(grantStmt); useUser("test_policy"); } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
