This is an automated email from the ASF dual-hosted git repository. morningman pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push: new 41cb4c8f9c [feature-wip](multi-catalog) add CatalogPrivTable to support unified authority management of datalake (#10246) 41cb4c8f9c is described below commit 41cb4c8f9cf1b58fb33a1e46d2b7db803a15a59f Author: Ashin Gau <ashin...@users.noreply.github.com> AuthorDate: Tue Jun 21 10:26:50 2022 +0800 [feature-wip](multi-catalog) add CatalogPrivTable to support unified authority management of datalake (#10246) Supported: 1. Change FeMetaVersion to 111, compatible with upgrade from 110. 2. Add catalog level privileges, and degrade global level privileges to catalog level if FeMetaVersion < 111. 3. Support 'show all grants', 'show roles' statement. 4. Previous version of SQL syntax. Todo: 1. three-segment format catalog.database.table in SQL syntax. 2. User document for the unified authority management of datalake. 3. LDAP services to provide authentication. --- .../org/apache/doris/common/FeMetaVersion.java | 4 +- .../java/org/apache/doris/analysis/Analyzer.java | 4 + .../java/org/apache/doris/analysis/GrantStmt.java | 4 +- .../java/org/apache/doris/analysis/RevokeStmt.java | 2 +- .../org/apache/doris/analysis/ShowRolesStmt.java | 1 + .../java/org/apache/doris/analysis/TableName.java | 79 ++++++--- .../org/apache/doris/analysis/TablePattern.java | 91 +++++++--- .../org/apache/doris/common/CaseSensibility.java | 1 + .../java/org/apache/doris/common/ErrorCode.java | 2 +- .../java/org/apache/doris/common/FeNameFormat.java | 8 + .../org/apache/doris/common/proc/AuthProcDir.java | 4 +- .../doris/mysql/privilege/CatalogPrivEntry.java | 136 +++++++++++++++ .../doris/mysql/privilege/CatalogPrivTable.java | 73 ++++++++ .../apache/doris/mysql/privilege/DbPrivEntry.java | 52 +++--- .../apache/doris/mysql/privilege/DbPrivTable.java | 31 +--- .../org/apache/doris/mysql/privilege/PaloAuth.java | 188 ++++++++++++++++----- .../apache/doris/mysql/privilege/PrivEntry.java | 16 ++ .../apache/doris/mysql/privilege/PrivTable.java | 21 +++ .../apache/doris/mysql/privilege/RoleManager.java | 78 +++------ .../doris/mysql/privilege/TablePrivEntry.java | 60 +++---- .../doris/mysql/privilege/TablePrivTable.java | 37 ++-- .../doris/mysql/privilege/UserPrivTable.java | 52 +++--- .../java/org/apache/doris/qe/ConnectContext.java | 11 ++ .../org/apache/doris/mysql/privilege/AuthTest.java | 7 +- .../doris/mysql/privilege/PrivEntryTest.java | 6 +- 25 files changed, 677 insertions(+), 291 deletions(-) diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/FeMetaVersion.java b/fe/fe-common/src/main/java/org/apache/doris/common/FeMetaVersion.java index 70cacd7a1c..1a5d6da4ce 100644 --- a/fe/fe-common/src/main/java/org/apache/doris/common/FeMetaVersion.java +++ b/fe/fe-common/src/main/java/org/apache/doris/common/FeMetaVersion.java @@ -40,8 +40,10 @@ public final class FeMetaVersion { public static final int VERSION_109 = 109; // For routine load user info public static final int VERSION_110 = 110; + // add catalog PrivTable in PaloAuth to support unified privilege management + public static final int VERSION_111 = 111; // note: when increment meta version, should assign the latest version to VERSION_CURRENT - public static final int VERSION_CURRENT = VERSION_110; + public static final int VERSION_CURRENT = VERSION_111; // all logs meta version should >= the minimum version, so that we could remove many if clause, for example // if (FE_METAVERSION < VERSION_94) ... diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java index 997891253f..8b35987710 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java @@ -1935,6 +1935,10 @@ public class Analyzer { return globalState.context.getConnectionId(); } + public String getDefaultCatalog() { + return globalState.context.getDefaultCatalog(); + } + public String getDefaultDb() { return globalState.context.getDatabase(); } 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 4e849e7c61..4ad8fe8073 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 @@ -107,7 +107,7 @@ public class GrantStmt extends DdlStmt { } if (tblPattern != null) { - tblPattern.analyze(analyzer.getClusterName()); + tblPattern.analyze(analyzer); } else { // TODO(wyb): spark-load if (!Config.enable_spark_load) { @@ -148,7 +148,7 @@ public class GrantStmt extends DdlStmt { // Rule 1 if (tblPattern.getPrivLevel() != PrivLevel.GLOBAL && (privileges.contains(PaloPrivilege.ADMIN_PRIV) || privileges.contains(PaloPrivilege.NODE_PRIV))) { - throw new AnalysisException("ADMIN_PRIV and NODE_PRIV can only be granted on *.*"); + throw new AnalysisException("ADMIN_PRIV and NODE_PRIV can only be granted on *.*.*"); } // Rule 2 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 c84f490ddf..98b36b4968 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 @@ -97,7 +97,7 @@ public class RevokeStmt extends DdlStmt { } if (tblPattern != null) { - tblPattern.analyze(analyzer.getClusterName()); + tblPattern.analyze(analyzer); } else { // TODO(wyb): spark-load if (!Config.enable_spark_load) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java index 47b9bacda2..4e8c96e2b9 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java @@ -36,6 +36,7 @@ public class ShowRolesStmt extends ShowStmt { builder.addColumn(new Column("Name", ScalarType.createVarchar(100))); builder.addColumn(new Column("Users", ScalarType.createVarchar(100))); builder.addColumn(new Column("GlobalPrivs", ScalarType.createVarchar(300))); + builder.addColumn(new Column("CatalogPrivs", ScalarType.createVarchar(300))); builder.addColumn(new Column("DatabasePrivs", ScalarType.createVarchar(300))); builder.addColumn(new Column("TablePrivs", ScalarType.createVarchar(300))); builder.addColumn(new Column("ResourcePrivs", ScalarType.createVarchar(300))); diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/TableName.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/TableName.java index 047d518e16..ff1bb7bc09 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/TableName.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/TableName.java @@ -25,32 +25,54 @@ import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; +import org.apache.doris.common.FeMetaVersion; import org.apache.doris.common.io.Text; import org.apache.doris.common.io.Writable; +import org.apache.doris.datasource.InternalDataSource; +import org.apache.doris.persist.gson.GsonUtils; import com.google.common.base.Strings; +import com.google.gson.annotations.SerializedName; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class TableName implements Writable { + @SerializedName(value = "ctl") + private String ctl; + @SerializedName(value = "tbl") private String tbl; + @SerializedName(value = "db") private String db; public TableName() { } - public TableName(String db, String tbl) { + public TableName(String ctl, String db, String tbl) { if (Catalog.isStoredTableNamesLowerCase() && !Strings.isNullOrEmpty(tbl)) { tbl = tbl.toLowerCase(); } + this.ctl = ctl; this.db = db; this.tbl = tbl; } + /** + * Initialize catalog in analyze. + */ + public TableName(String db, String tbl) { + this(null, db, tbl); + } + public void analyze(Analyzer analyzer) throws AnalysisException { + if (Strings.isNullOrEmpty(ctl)) { + ctl = analyzer.getDefaultCatalog(); + } if (Strings.isNullOrEmpty(db)) { db = analyzer.getDefaultDb(); if (Strings.isNullOrEmpty(db)) { @@ -68,6 +90,14 @@ public class TableName implements Writable { } } + public String getCtl() { + return ctl; + } + + public void setCtl(String ctl) { + this.ctl = ctl; + } + public String getDb() { return db; } @@ -85,33 +115,22 @@ public class TableName implements Writable { } /** - * Returns true if this name has a non-empty database field and a non-empty - * table name. + * Returns true if this name has a non-empty catalog and a non-empty database field + * and a non-empty table name. */ public boolean isFullyQualified() { - return db != null && !db.isEmpty() && !tbl.isEmpty(); + return ctl != null && !ctl.isEmpty() && db != null && !db.isEmpty() && !tbl.isEmpty(); } public String getNoClusterString() { - if (db == null) { - return tbl; - } else { - String dbName = ClusterNamespace.getNameFromFullName(db); - if (dbName == null) { - return db + "." + tbl; - } else { - return dbName + "." + tbl; - } - } + return Stream.of(ctl, ClusterNamespace.getNameFromFullName(db), tbl) + .filter(Objects::nonNull) + .collect(Collectors.joining(".")); } @Override public String toString() { - if (db == null) { - return tbl; - } else { - return db + "." + tbl; - } + return Stream.of(ctl, db, tbl).filter(Objects::nonNull).collect(Collectors.joining(".")); } @Override @@ -127,6 +146,9 @@ public class TableName implements Writable { public String toSql() { StringBuilder stringBuilder = new StringBuilder(); + if (ctl != null) { + stringBuilder.append("`").append(ctl).append("`."); + } if (db != null) { stringBuilder.append("`").append(db).append("`."); } @@ -136,17 +158,24 @@ public class TableName implements Writable { @Override public void write(DataOutput out) throws IOException { - Text.writeString(out, db); - Text.writeString(out, tbl); + String json = GsonUtils.GSON.toJson(this); + Text.writeString(out, json); } public void readFields(DataInput in) throws IOException { - db = Text.readString(in); - tbl = Text.readString(in); + if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_111) { + TableName fromJson = GsonUtils.GSON.fromJson(Text.readString(in), TableName.class); + ctl = fromJson.ctl; + db = fromJson.db; + tbl = fromJson.tbl; + } else { + ctl = InternalDataSource.INTERNAL_DS_NAME; + db = Text.readString(in); + tbl = Text.readString(in); + } } public TableName cloneWithoutAnalyze() { - TableName tableName = new TableName(this.db, this.tbl); - return tableName; + return new TableName(this.ctl, this.db, this.tbl); } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/TablePattern.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/TablePattern.java index 6f6a3d393a..7d1287dd48 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/TablePattern.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/TablePattern.java @@ -17,33 +17,43 @@ package org.apache.doris.analysis; +import org.apache.doris.catalog.Catalog; import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.AnalysisException; +import org.apache.doris.common.FeMetaVersion; import org.apache.doris.common.FeNameFormat; import org.apache.doris.common.io.Text; import org.apache.doris.common.io.Writable; +import org.apache.doris.datasource.InternalDataSource; import org.apache.doris.mysql.privilege.PaloAuth.PrivLevel; +import org.apache.doris.persist.gson.GsonUtils; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import com.google.gson.annotations.SerializedName; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; -// only the following 3 formats are allowed -// db.tbl -// *.* -// db.* +/** + * Three-segment-format: catalog.database.table. If the lower segment is specific, + * the higher segment can't be a wildcard. The following examples are not allowed: + * "ctl1.*.table1", "*.*.table2", "*.db1.*", ... + */ public class TablePattern implements Writable { + @SerializedName(value = "ctl") + private String ctl; + @SerializedName(value = "db") private String db; + @SerializedName(value = "tbl") private String tbl; boolean isAnalyzed = false; public static TablePattern ALL; static { - ALL = new TablePattern("*", "*"); + ALL = new TablePattern("*", "*", "*"); try { ALL.analyze(""); } catch (AnalysisException e) { @@ -54,11 +64,23 @@ public class TablePattern implements Writable { private TablePattern() { } + public TablePattern(String ctl, String db, String tbl) { + this.ctl = Strings.isNullOrEmpty(ctl) ? "*" : ctl; + this.db = Strings.isNullOrEmpty(db) ? "*" : db; + this.tbl = Strings.isNullOrEmpty(tbl) ? "*" : tbl; + } + public TablePattern(String db, String tbl) { + this.ctl = null; this.db = Strings.isNullOrEmpty(db) ? "*" : db; this.tbl = Strings.isNullOrEmpty(tbl) ? "*" : tbl; } + public String getQualifiedCtl() { + Preconditions.checkState(isAnalyzed); + return ctl; + } + public String getQualifiedDb() { Preconditions.checkState(isAnalyzed); return db; @@ -70,23 +92,39 @@ public class TablePattern implements Writable { public PrivLevel getPrivLevel() { Preconditions.checkState(isAnalyzed); - if (db.equals("*")) { + if (ctl.equals("*")) { return PrivLevel.GLOBAL; - } else if (!tbl.equals("*")) { + } else if (db.equals("*")) { + return PrivLevel.CATALOG; + } else if (tbl.equals("*")) { + return PrivLevel.DATABASE; + } else { return PrivLevel.TABLE; + } + } + + public void analyze(Analyzer analyzer) throws AnalysisException { + if (ctl == null) { + analyze(analyzer.getDefaultCatalog(), analyzer.getClusterName()); } else { - return PrivLevel.DATABASE; + analyze(analyzer.getClusterName()); } } - public void analyze(String clusterName) throws AnalysisException { + private void analyze(String catalogName, String clusterName) throws AnalysisException { if (isAnalyzed) { return; } - if (db.equals("*") && !tbl.equals("*")) { + this.ctl = Strings.isNullOrEmpty(catalogName) ? InternalDataSource.INTERNAL_DS_NAME : catalogName; + if ((!tbl.equals("*") && (db.equals("*") || ctl.equals("*"))) + || (!db.equals("*") && ctl.equals("*"))) { throw new AnalysisException("Do not support format: " + toString()); } + if (!ctl.equals("*")) { + FeNameFormat.checkCatalogName(ctl); + } + if (!db.equals("*")) { FeNameFormat.checkDbName(db); db = ClusterNamespace.getFullName(clusterName, db); @@ -98,9 +136,21 @@ public class TablePattern implements Writable { isAnalyzed = true; } + public void analyze(String clusterName) throws AnalysisException { + analyze(ctl, clusterName); + } + public static TablePattern read(DataInput in) throws IOException { - TablePattern tablePattern = new TablePattern(); - tablePattern.readFields(in); + TablePattern tablePattern; + if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_111) { + tablePattern = GsonUtils.GSON.fromJson(Text.readString(in), TablePattern.class); + } else { + String ctl = InternalDataSource.INTERNAL_DS_NAME; + String db = Text.readString(in); + String tbl = Text.readString(in); + tablePattern = new TablePattern(ctl, db, tbl); + } + tablePattern.isAnalyzed = true; return tablePattern; } @@ -110,12 +160,13 @@ public class TablePattern implements Writable { return false; } TablePattern other = (TablePattern) obj; - return db.equals(other.getQualifiedDb()) && tbl.equals(other.getTbl()); + return ctl.equals(other.getQualifiedCtl()) && db.equals(other.getQualifiedDb()) && tbl.equals(other.getTbl()); } @Override public int hashCode() { int result = 17; + result = 31 * result + ctl.hashCode(); result = 31 * result + db.hashCode(); result = 31 * result + tbl.hashCode(); return result; @@ -123,21 +174,13 @@ public class TablePattern implements Writable { @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(db).append(".").append(tbl); - return sb.toString(); + return String.format("%s.%s.%s", ctl, db, tbl); } @Override public void write(DataOutput out) throws IOException { Preconditions.checkState(isAnalyzed); - Text.writeString(out, db); - Text.writeString(out, tbl); - } - - public void readFields(DataInput in) throws IOException { - db = Text.readString(in); - tbl = Text.readString(in); - isAnalyzed = true; + String json = GsonUtils.GSON.toJson(this); + Text.writeString(out, json); } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/CaseSensibility.java b/fe/fe-core/src/main/java/org/apache/doris/common/CaseSensibility.java index 651581a3c6..6d5da6e65f 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/common/CaseSensibility.java +++ b/fe/fe-core/src/main/java/org/apache/doris/common/CaseSensibility.java @@ -22,6 +22,7 @@ package org.apache.doris.common; **/ public enum CaseSensibility { CLUSTER(true), + CATALOG(true), DATABASE(true), TABLE(true), ROLLUP(true), diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/ErrorCode.java b/fe/fe-core/src/main/java/org/apache/doris/common/ErrorCode.java index 35dedadc30..a8f73b9773 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/common/ErrorCode.java +++ b/fe/fe-core/src/main/java/org/apache/doris/common/ErrorCode.java @@ -1686,7 +1686,7 @@ public enum ErrorCode { + "Use `SHOW PARTITIONS FROM %s` to see the currently partitions of this table. "), ERROR_SQL_AND_LIMITATIONS_SET_IN_ONE_RULE(5084, new byte[]{'4', '2', '0', '0', '0'}, "sql/sqlHash and partition_num/tablet_num/cardinality cannot be set in one rule."), - ; + ERR_WRONG_CATALOG_NAME(5085, new byte[]{'4', '2', '0', '0', '0'}, "Incorrect catalog name '%s'"); // This is error code private final int code; diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/FeNameFormat.java b/fe/fe-core/src/main/java/org/apache/doris/common/FeNameFormat.java index 1f3db6a4a6..ea12ba5758 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/common/FeNameFormat.java +++ b/fe/fe-core/src/main/java/org/apache/doris/common/FeNameFormat.java @@ -18,6 +18,7 @@ package org.apache.doris.common; import org.apache.doris.alter.SchemaChangeHandler; +import org.apache.doris.datasource.InternalDataSource; import org.apache.doris.mysql.privilege.PaloRole; import org.apache.doris.system.SystemInfoService; @@ -42,6 +43,13 @@ public class FeNameFormat { } } + public static void checkCatalogName(String catalogName) throws AnalysisException { + if (!InternalDataSource.INTERNAL_DS_NAME.equals(catalogName) + && (Strings.isNullOrEmpty(catalogName) || !catalogName.matches(COMMON_NAME_REGEX))) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_CATALOG_NAME, catalogName); + } + } + public static void checkDbName(String dbName) throws AnalysisException { if (Strings.isNullOrEmpty(dbName) || !dbName.matches(COMMON_NAME_REGEX)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_DB_NAME, dbName); 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 33e3b2854c..6f11b4dd4e 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 @@ -31,8 +31,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("GlobalPrivs").add("DatabasePrivs") - .add("TablePrivs").add("ResourcePrivs").build(); + .add("UserIdentity").add("Password").add("GlobalPrivs").add("CatalogPrivs") + .add("DatabasePrivs").add("TablePrivs").add("ResourcePrivs").build(); private PaloAuth auth; diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogPrivEntry.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogPrivEntry.java new file mode 100644 index 0000000000..855c67e14c --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogPrivEntry.java @@ -0,0 +1,136 @@ +// 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.common.AnalysisException; +import org.apache.doris.common.CaseSensibility; +import org.apache.doris.common.PatternMatcher; +import org.apache.doris.common.io.Text; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class CatalogPrivEntry extends PrivEntry { + protected static final String ANY_CTL = "*"; + + protected PatternMatcher ctlPattern; + protected String origCtl; + protected boolean isAnyCtl; + + protected CatalogPrivEntry() { + } + + protected CatalogPrivEntry(PatternMatcher userPattern, String user, + PatternMatcher hostPattern, String origHost, + PatternMatcher ctlPattern, String origCtl, + boolean isDomain, PrivBitSet privSet) { + super(hostPattern, origHost, userPattern, user, isDomain, privSet); + this.ctlPattern = ctlPattern; + this.origCtl = origCtl; + if (origCtl.equals(ANY_CTL)) { + isAnyCtl = true; + } + } + + public static CatalogPrivEntry create(String user, String host, String ctl, boolean isDomain, PrivBitSet privs) + throws AnalysisException { + PatternMatcher hostPattern = PatternMatcher.createMysqlPattern(host, CaseSensibility.HOST.getCaseSensibility()); + + PatternMatcher ctlPattern = createCtlPatternMatcher(ctl); + + PatternMatcher userPattern = PatternMatcher.createFlatPattern(user, CaseSensibility.USER.getCaseSensibility()); + + if (privs.containsNodePriv() || privs.containsResourcePriv()) { + throw new AnalysisException("Datasource privilege can not contains node or resource privileges: " + privs); + } + + return new CatalogPrivEntry(userPattern, user, hostPattern, host, ctlPattern, ctl, isDomain, privs); + } + + private static PatternMatcher createCtlPatternMatcher(String ctl) throws AnalysisException { + boolean ctlCaseSensibility = CaseSensibility.CATALOG.getCaseSensibility(); + return PatternMatcher.createFlatPattern(ctl, ctlCaseSensibility, ctl.equals(ANY_CTL)); + } + + public PatternMatcher getCtlPattern() { + return ctlPattern; + } + + public String getOrigCtl() { + return origCtl; + } + + public boolean isAnyCtl() { + return isAnyCtl; + } + + @Override + public int compareTo(PrivEntry other) { + if (!(other instanceof CatalogPrivEntry)) { + throw new ClassCastException("cannot cast " + other.getClass().toString() + " to " + this.getClass()); + } + + CatalogPrivEntry otherEntry = (CatalogPrivEntry) other; + return compareAssist(origUser, otherEntry.origUser, + origHost, otherEntry.origHost, + origCtl, otherEntry.origCtl); + } + + @Override + public boolean keyMatch(PrivEntry other) { + if (!(other instanceof CatalogPrivEntry)) { + return false; + } + + CatalogPrivEntry otherEntry = (CatalogPrivEntry) other; + return origUser.equals(otherEntry.origUser) && origHost.equals(otherEntry.origHost) + && origCtl.equals(otherEntry.origCtl) && isDomain == otherEntry.isDomain; + } + + @Override + public String toString() { + return String.format("catalog privilege. user: %s, host: %s, ctl: %s, priv: %s, set by resolver: %b", + origUser, origHost, origCtl, privSet.toString(), isSetByDomainResolver); + } + + @Override + public void write(DataOutput out) throws IOException { + if (!isClassNameWrote) { + String className = CatalogPrivEntry.class.getCanonicalName(); + Text.writeString(out, className); + isClassNameWrote = true; + } + super.write(out); + Text.writeString(out, origCtl); + isClassNameWrote = false; + } + + public void readFields(DataInput in) throws IOException { + super.readFields(in); + + origCtl = Text.readString(in); + try { + ctlPattern = createCtlPatternMatcher(origCtl); + } catch (AnalysisException e) { + throw new IOException(e); + } + isAnyCtl = origCtl.equals(ANY_CTL); + } + +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogPrivTable.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogPrivTable.java new file mode 100644 index 0000000000..a1febfbf37 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogPrivTable.java @@ -0,0 +1,73 @@ +// 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.analysis.UserIdentity; +import org.apache.doris.common.io.Text; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataOutput; +import java.io.IOException; + +/* + * CatalogPrivTable saves all catalog level privs + */ +public class CatalogPrivTable extends PrivTable { + private static final Logger LOG = LogManager.getLogger(CatalogPrivTable.class); + + /* + * Return first priv which match the user@host on ctl.* The returned priv will be + * saved in 'savedPrivs'. + */ + public void getPrivs(UserIdentity currentUser, String ctl, PrivBitSet savedPrivs) { + CatalogPrivEntry matchedEntry = null; + for (PrivEntry entry : entries) { + CatalogPrivEntry dsPrivEntry = (CatalogPrivEntry) entry; + + if (!dsPrivEntry.match(currentUser, true)) { + continue; + } + + // check catalog + if (!dsPrivEntry.isAnyCtl() && !dsPrivEntry.getCtlPattern().match(ctl)) { + continue; + } + + matchedEntry = dsPrivEntry; + break; + } + if (matchedEntry == null) { + return; + } + + savedPrivs.or(matchedEntry.getPrivSet()); + } + + @Override + public void write(DataOutput out) throws IOException { + if (!isClassNameWrote) { + String className = CatalogPrivTable.class.getCanonicalName(); + Text.writeString(out, className); + isClassNameWrote = true; + } + + super.write(out); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivEntry.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivEntry.java index a8f1337df7..826bbe2a98 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivEntry.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivEntry.java @@ -28,7 +28,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; -public class DbPrivEntry extends PrivEntry { +public class DbPrivEntry extends CatalogPrivEntry { protected static final String ANY_DB = "*"; protected PatternMatcher dbPattern; @@ -38,9 +38,12 @@ public class DbPrivEntry extends PrivEntry { protected DbPrivEntry() { } - protected DbPrivEntry(PatternMatcher hostPattern, String origHost, PatternMatcher dbPattern, String origDb, - PatternMatcher userPattern, String user, boolean isDomain, PrivBitSet privSet) { - super(hostPattern, origHost, userPattern, user, isDomain, privSet); + protected DbPrivEntry(PatternMatcher userPattern, String user, + PatternMatcher hostPattern, String origHost, + PatternMatcher ctlPattern, String origCtl, + PatternMatcher dbPattern, String origDb, + boolean isDomain, PrivBitSet privSet) { + super(userPattern, user, hostPattern, origHost, ctlPattern, origCtl, isDomain, privSet); this.dbPattern = dbPattern; this.origDb = origDb; if (origDb.equals(ANY_DB)) { @@ -48,10 +51,15 @@ public class DbPrivEntry extends PrivEntry { } } - public static DbPrivEntry create(String host, String db, String user, boolean isDomain, PrivBitSet privs) - throws AnalysisException { + public static DbPrivEntry create( + String user, String host, + String ctl, String db, + boolean isDomain, PrivBitSet privs) throws AnalysisException { PatternMatcher hostPattern = PatternMatcher.createMysqlPattern(host, CaseSensibility.HOST.getCaseSensibility()); + PatternMatcher ctlPattern = PatternMatcher.createFlatPattern( + ctl, CaseSensibility.CATALOG.getCaseSensibility(), ctl.equals(ANY_CTL)); + PatternMatcher dbPattern = createDbPatternMatcher(db); PatternMatcher userPattern = PatternMatcher.createFlatPattern(user, CaseSensibility.USER.getCaseSensibility()); @@ -60,7 +68,7 @@ public class DbPrivEntry extends PrivEntry { throw new AnalysisException("Db privilege can not contains global or resource privileges: " + privs); } - return new DbPrivEntry(hostPattern, host, dbPattern, db, userPattern, user, isDomain, privs); + return new DbPrivEntry(userPattern, user, hostPattern, host, ctlPattern, ctl, dbPattern, db, isDomain, privs); } private static PatternMatcher createDbPatternMatcher(String db) throws AnalysisException { @@ -92,17 +100,10 @@ public class DbPrivEntry extends PrivEntry { } DbPrivEntry otherEntry = (DbPrivEntry) other; - int res = origHost.compareTo(otherEntry.origHost); - if (res != 0) { - return -res; - } - - res = origDb.compareTo(otherEntry.origDb); - if (res != 0) { - return -res; - } - - return -origUser.compareTo(otherEntry.origUser); + return compareAssist(origUser, otherEntry.origUser, + origHost, otherEntry.origHost, + origCtl, otherEntry.origCtl, + origDb, otherEntry.origDb); } @Override @@ -112,20 +113,15 @@ public class DbPrivEntry extends PrivEntry { } DbPrivEntry otherEntry = (DbPrivEntry) other; - if (origHost.equals(otherEntry.origHost) && origUser.equals(otherEntry.origUser) - && origDb.equals(otherEntry.origDb) && isDomain == otherEntry.isDomain) { - return true; - } - return false; + return origUser.equals(otherEntry.origUser) && origHost.equals(otherEntry.origHost) + && origCtl.equals(otherEntry.origCtl) && origDb.equals(otherEntry.origDb) + && isDomain == otherEntry.isDomain; } @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("db priv. host: ").append(origHost).append(", db: ").append(origDb); - sb.append(", user: ").append(origUser); - sb.append(", priv: ").append(privSet).append(", set by resolver: ").append(isSetByDomainResolver); - return sb.toString(); + return String.format("database privilege. user: %s, host: %s, ctl: %s, db: %s, priv: %s, set by resolver: %b", + origUser, origHost, origCtl, origDb, privSet.toString(), isSetByDomainResolver); } @Override diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivTable.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivTable.java index a16c8dab9f..87ef9ad50e 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivTable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivTable.java @@ -34,10 +34,10 @@ public class DbPrivTable extends PrivTable { private static final Logger LOG = LogManager.getLogger(DbPrivTable.class); /* - * Return first priv which match the user@host on db.* The returned priv will be + * Return first priv which match the user@host on ctl.db.* The returned priv will be * saved in 'savedPrivs'. */ - public void getPrivs(UserIdentity currentUser, String db, PrivBitSet savedPrivs) { + public void getPrivs(UserIdentity currentUser, String ctl, String db, PrivBitSet savedPrivs) { DbPrivEntry matchedEntry = null; for (PrivEntry entry : entries) { DbPrivEntry dbPrivEntry = (DbPrivEntry) entry; @@ -46,6 +46,11 @@ public class DbPrivTable extends PrivTable { continue; } + // check catalog + if (!dbPrivEntry.isAnyCtl() && !dbPrivEntry.getCtlPattern().match(ctl)) { + continue; + } + // check db if (!dbPrivEntry.isAnyDb() && !dbPrivEntry.getDbPattern().match(db)) { continue; @@ -61,28 +66,6 @@ public class DbPrivTable extends PrivTable { savedPrivs.or(matchedEntry.getPrivSet()); } - /* - * Check if user@host has specified privilege on any database - */ - public boolean hasPriv(String host, String user, PrivPredicate wanted) { - for (PrivEntry entry : entries) { - DbPrivEntry dbPrivEntry = (DbPrivEntry) entry; - // check host - if (!dbPrivEntry.isAnyHost() && !dbPrivEntry.getHostPattern().match(host)) { - continue; - } - // check user - if (!dbPrivEntry.isAnyUser() && !dbPrivEntry.getUserPattern().match(user)) { - continue; - } - // check priv - if (dbPrivEntry.privSet.satisfy(wanted)) { - return true; - } - } - return false; - } - public boolean hasClusterPriv(ConnectContext ctx, String clusterName) { for (PrivEntry entry : entries) { DbPrivEntry dbPrivEntry = (DbPrivEntry) entry; diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java index 2dcb317ff8..9459f14ed9 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java @@ -27,6 +27,7 @@ import org.apache.doris.analysis.RevokeStmt; import org.apache.doris.analysis.SetLdapPassVar; import org.apache.doris.analysis.SetPassVar; import org.apache.doris.analysis.SetUserPropertyStmt; +import org.apache.doris.analysis.TableName; import org.apache.doris.analysis.TablePattern; import org.apache.doris.analysis.UserIdentity; import org.apache.doris.catalog.AuthorizationInfo; @@ -42,6 +43,7 @@ import org.apache.doris.common.LdapConfig; import org.apache.doris.common.Pair; import org.apache.doris.common.UserException; import org.apache.doris.common.io.Writable; +import org.apache.doris.datasource.InternalDataSource; import org.apache.doris.ldap.LdapPrivsChecker; import org.apache.doris.load.DppConfig; import org.apache.doris.persist.LdapInfo; @@ -53,6 +55,7 @@ import org.apache.doris.thrift.TPrivilegeStatus; 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.Sets; import org.apache.logging.log4j.LogManager; @@ -65,6 +68,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; public class PaloAuth implements Writable { private static final Logger LOG = LogManager.getLogger(PaloAuth.class); @@ -75,8 +79,10 @@ public class PaloAuth implements Writable { public static final String ADMIN_USER = "admin"; // unknown user does not have any privilege, this is just to be compatible with old version. public static final String UNKNOWN_USER = "unknown"; + private static final String DEFAULT_CATALOG = InternalDataSource.INTERNAL_DS_NAME; private UserPrivTable userPrivTable = new UserPrivTable(); + private CatalogPrivTable catalogPrivTable = new CatalogPrivTable(); private DbPrivTable dbPrivTable = new DbPrivTable(); private TablePrivTable tablePrivTable = new TablePrivTable(); private ResourcePrivTable resourcePrivTable = new ResourcePrivTable(); @@ -105,7 +111,7 @@ public class PaloAuth implements Writable { } public enum PrivLevel { - GLOBAL, DATABASE, TABLE, RESOURCE + GLOBAL, CATALOG, DATABASE, TABLE, RESOURCE } public PaloAuth() { @@ -165,12 +171,39 @@ public class PaloAuth implements Writable { false /* not delete entry if priv is empty, because global priv entry has password */); } - private void grantDbPrivs(UserIdentity userIdentity, String db, boolean errOnExist, boolean errOnNonExist, - PrivBitSet privs) throws DdlException { + private void grantCatalogPrivs(UserIdentity userIdentity, String ctl, + boolean errOnExist, boolean errOnNonExist, PrivBitSet privs) throws DdlException { + CatalogPrivEntry entry; + try { + entry = CatalogPrivEntry.create(userIdentity.getQualifiedUser(), userIdentity.getHost(), + ctl, userIdentity.isDomain(), privs); + entry.setSetByDomainResolver(false); + } catch (AnalysisException e) { + throw new DdlException(e.getMessage()); + } + catalogPrivTable.addEntry(entry, errOnExist, errOnNonExist); + } + + private void revokeCatalogPrivs(UserIdentity userIdentity, String ctl, + PrivBitSet privs, boolean errOnNonExist) throws DdlException { + CatalogPrivEntry entry; + try { + entry = CatalogPrivEntry.create(userIdentity.getQualifiedUser(), userIdentity.getHost(), + ctl, userIdentity.isDomain(), privs); + entry.setSetByDomainResolver(false); + } catch (AnalysisException e) { + throw new DdlException(e.getMessage()); + } + + catalogPrivTable.revoke(entry, errOnNonExist, true /* delete entry when empty */); + } + + private void grantDbPrivs(UserIdentity userIdentity, String ctl, String db, + boolean errOnExist, boolean errOnNonExist, PrivBitSet privs) throws DdlException { DbPrivEntry entry; try { - entry = DbPrivEntry.create(userIdentity.getHost(), db, userIdentity.getQualifiedUser(), - userIdentity.isDomain(), privs); + entry = DbPrivEntry.create(userIdentity.getQualifiedUser(), userIdentity.getHost(), + ctl, db, userIdentity.isDomain(), privs); entry.setSetByDomainResolver(false); } catch (AnalysisException e) { throw new DdlException(e.getMessage()); @@ -178,12 +211,12 @@ public class PaloAuth implements Writable { dbPrivTable.addEntry(entry, errOnExist, errOnNonExist); } - private void revokeDbPrivs(UserIdentity userIdentity, String db, PrivBitSet privs, boolean errOnNonExist) - throws DdlException { + private void revokeDbPrivs(UserIdentity userIdentity, String ctl, String db, + PrivBitSet privs, boolean errOnNonExist) throws DdlException { DbPrivEntry entry; try { - entry = DbPrivEntry.create(userIdentity.getHost(), db, userIdentity.getQualifiedUser(), - userIdentity.isDomain(), privs); + entry = DbPrivEntry.create(userIdentity.getQualifiedUser(), userIdentity.getHost(), + ctl, db, userIdentity.isDomain(), privs); entry.setSetByDomainResolver(false); } catch (AnalysisException e) { throw new DdlException(e.getMessage()); @@ -192,12 +225,12 @@ public class PaloAuth implements Writable { dbPrivTable.revoke(entry, errOnNonExist, true /* delete entry when empty */); } - private void grantTblPrivs(UserIdentity userIdentity, String db, String tbl, boolean errOnExist, - boolean errOnNonExist, PrivBitSet privs) throws DdlException { + private void grantTblPrivs(UserIdentity userIdentity, String ctl, String db, String tbl, + boolean errOnExist, boolean errOnNonExist, PrivBitSet privs) throws DdlException { TablePrivEntry entry; try { - entry = TablePrivEntry.create(userIdentity.getHost(), db, userIdentity.getQualifiedUser(), tbl, - userIdentity.isDomain(), privs); + entry = TablePrivEntry.create(userIdentity.getQualifiedUser(), userIdentity.getHost(), + ctl, db, tbl, userIdentity.isDomain(), privs); entry.setSetByDomainResolver(false); } catch (AnalysisException e) { throw new DdlException(e.getMessage()); @@ -205,12 +238,12 @@ public class PaloAuth implements Writable { tablePrivTable.addEntry(entry, errOnExist, errOnNonExist); } - private void revokeTblPrivs(UserIdentity userIdentity, String db, String tbl, PrivBitSet privs, - boolean errOnNonExist) throws DdlException { + private void revokeTblPrivs(UserIdentity userIdentity, String ctl, String db, String tbl, + PrivBitSet privs, boolean errOnNonExist) throws DdlException { TablePrivEntry entry; try { - entry = TablePrivEntry.create(userIdentity.getHost(), db, userIdentity.getQualifiedUser(), tbl, - userIdentity.isDomain(), privs); + entry = TablePrivEntry.create(userIdentity.getQualifiedUser(), userIdentity.getHost(), + ctl, db, tbl, userIdentity.isDomain(), privs); entry.setSetByDomainResolver(false); } catch (AnalysisException e) { throw new DdlException(e.getMessage()); @@ -324,11 +357,15 @@ public class PaloAuth implements Writable { return checkDbPriv(ctx.getCurrentUserIdentity(), qualifiedDb, wanted); } + public boolean checkDbPriv(UserIdentity currentUser, String db, PrivPredicate wanted) { + return checkDbPriv(currentUser, DEFAULT_CATALOG, db, wanted); + } + /* * Check if 'user'@'host' on 'db' has 'wanted' priv. * If the given db is null, which means it will no check if database name is matched. */ - public boolean checkDbPriv(UserIdentity currentUser, String db, PrivPredicate wanted) { + public boolean checkDbPriv(UserIdentity currentUser, String ctl, String db, PrivPredicate wanted) { if (!Config.enable_auth_check) { return true; } @@ -340,12 +377,13 @@ public class PaloAuth implements Writable { PrivBitSet savedPrivs = PrivBitSet.of(); if (checkGlobalInternal(currentUser, wanted, savedPrivs) - || checkDbInternal(currentUser, db, wanted, savedPrivs)) { + || checkCatalogInternal(currentUser, ctl, wanted, savedPrivs) + || checkDbInternal(currentUser, ctl, db, wanted, savedPrivs)) { return true; } // if user has any privs of table in this db, and the wanted priv is SHOW, return true - if (db != null && wanted == PrivPredicate.SHOW && checkTblWithDb(currentUser, db)) { + if (ctl != null && db != null && wanted == PrivPredicate.SHOW && checkTblWithDb(currentUser, ctl, db)) { return true; } @@ -358,21 +396,31 @@ public class PaloAuth implements Writable { * So we have to check if user has any privs of tables in this database. * if so, the database should be visible to this user. */ - private boolean checkTblWithDb(UserIdentity currentUser, String db) { + private boolean checkTblWithDb(UserIdentity currentUser, String ctl, String db) { readLock(); try { return (isLdapAuthEnabled() && LdapPrivsChecker.hasPrivsOfDb(currentUser, db)) - || tablePrivTable.hasPrivsOfDb(currentUser, db); + || tablePrivTable.hasPrivsOfDb(currentUser, ctl, db); } finally { readUnlock(); } } + public boolean checkTblPriv(ConnectContext ctx, String qualifiedCtl, + String qualifiedDb, String tbl, PrivPredicate wanted) { + return checkTblPriv(ctx.getCurrentUserIdentity(), qualifiedCtl, qualifiedDb, tbl, wanted); + } + public boolean checkTblPriv(ConnectContext ctx, String qualifiedDb, String tbl, PrivPredicate wanted) { - return checkTblPriv(ctx.getCurrentUserIdentity(), qualifiedDb, tbl, wanted); + return checkTblPriv(ctx, DEFAULT_CATALOG, qualifiedDb, tbl, wanted); } - public boolean checkTblPriv(UserIdentity currentUser, String db, String tbl, PrivPredicate wanted) { + public boolean checkTblPriv(ConnectContext ctx, TableName tableName, PrivPredicate wanted) { + Preconditions.checkState(tableName.isFullyQualified()); + return checkTblPriv(ctx, tableName.getCtl(), tableName.getDb(), wanted); + } + + public boolean checkTblPriv(UserIdentity currentUser, String ctl, String db, String tbl, PrivPredicate wanted) { if (!Config.enable_auth_check) { return true; } @@ -383,8 +431,9 @@ public class PaloAuth implements Writable { PrivBitSet savedPrivs = PrivBitSet.of(); if (checkGlobalInternal(currentUser, wanted, savedPrivs) - || checkDbInternal(currentUser, db, wanted, savedPrivs) - || checkTblInternal(currentUser, db, tbl, wanted, savedPrivs)) { + || checkCatalogInternal(currentUser, ctl, wanted, savedPrivs) + || checkDbInternal(currentUser, ctl, db, wanted, savedPrivs) + || checkTblInternal(currentUser, ctl, db, tbl, wanted, savedPrivs)) { return true; } @@ -392,6 +441,10 @@ public class PaloAuth implements Writable { return false; } + public boolean checkTblPriv(UserIdentity currentUser, String db, String tbl, PrivPredicate wanted) { + return checkTblPriv(currentUser, DEFAULT_CATALOG, db, tbl, wanted); + } + public boolean checkResourcePriv(ConnectContext ctx, String resourceName, PrivPredicate wanted) { return checkResourcePriv(ctx.getCurrentUserIdentity(), resourceName, wanted); } @@ -485,7 +538,22 @@ public class PaloAuth implements Writable { } } - private boolean checkDbInternal(UserIdentity currentUser, String db, PrivPredicate wanted, + private boolean checkCatalogInternal(UserIdentity currentUser, String ctl, + PrivPredicate wanted, PrivBitSet savedPrivs) { + // TODO(gaoxin): check privileges by ldap. + readLock(); + try { + catalogPrivTable.getPrivs(currentUser, ctl, savedPrivs); + if (PaloPrivilege.satisfy(savedPrivs, wanted)) { + return true; + } + } finally { + readUnlock(); + } + return false; + } + + private boolean checkDbInternal(UserIdentity currentUser, String ctl, String db, PrivPredicate wanted, PrivBitSet savedPrivs) { if (isLdapAuthEnabled() && LdapPrivsChecker.hasDbPrivFromLdap(currentUser, db, wanted)) { return true; @@ -493,7 +561,7 @@ public class PaloAuth implements Writable { readLock(); try { - dbPrivTable.getPrivs(currentUser, db, savedPrivs); + dbPrivTable.getPrivs(currentUser, ctl, db, savedPrivs); if (PaloPrivilege.satisfy(savedPrivs, wanted)) { return true; } @@ -503,7 +571,7 @@ public class PaloAuth implements Writable { return false; } - private boolean checkTblInternal(UserIdentity currentUser, String db, String tbl, + private boolean checkTblInternal(UserIdentity currentUser, String ctl, String db, String tbl, PrivPredicate wanted, PrivBitSet savedPrivs) { if (isLdapAuthEnabled() && LdapPrivsChecker.hasTblPrivFromLdap(currentUser, db, tbl, wanted)) { return true; @@ -511,7 +579,7 @@ public class PaloAuth implements Writable { readLock(); try { - tablePrivTable.getPrivs(currentUser, db, tbl, savedPrivs); + tablePrivTable.getPrivs(currentUser, ctl, db, tbl, savedPrivs); if (PaloPrivilege.satisfy(savedPrivs, wanted)) { return true; } @@ -607,7 +675,7 @@ public class PaloAuth implements Writable { if (!userIdent.getQualifiedUser().equals(ROOT_USER) && !userIdent.getQualifiedUser().equals(ADMIN_USER)) { // grant read privs to database information_schema - TablePattern tblPattern = new TablePattern(InfoSchemaDb.DATABASE_NAME, "*"); + TablePattern tblPattern = new TablePattern(DEFAULT_CATALOG, InfoSchemaDb.DATABASE_NAME, "*"); try { tblPattern.analyze(ClusterNamespace.getClusterNameFromFullName(userIdent.getQualifiedUser())); } catch (AnalysisException e) { @@ -681,6 +749,7 @@ public class PaloAuth implements Writable { // we don't check if user exists userPrivTable.dropUser(userIdent); + catalogPrivTable.dropUser(userIdent); dbPrivTable.dropUser(userIdent); tablePrivTable.dropUser(userIdent); resourcePrivTable.dropUser(userIdent); @@ -815,14 +884,22 @@ public class PaloAuth implements Writable { errOnNonExist, privs); break; + case CATALOG: + grantCatalogPrivs(userIdent, tblPattern.getQualifiedCtl(), + false /* err on exist */, + false /* err on non exist */, + privs); + break; case DATABASE: - grantDbPrivs(userIdent, tblPattern.getQualifiedDb(), + grantDbPrivs(userIdent, tblPattern.getQualifiedCtl(), + tblPattern.getQualifiedDb(), false /* err on exist */, false /* err on non exist */, privs); break; case TABLE: - grantTblPrivs(userIdent, tblPattern.getQualifiedDb(), + grantTblPrivs(userIdent, tblPattern.getQualifiedCtl(), + tblPattern.getQualifiedDb(), tblPattern.getTbl(), false /* err on exist */, false /* err on non exist */, @@ -971,12 +1048,16 @@ public class PaloAuth implements Writable { case GLOBAL: revokeGlobalPrivs(userIdent, privs, errOnNonExist); break; + case CATALOG: + revokeCatalogPrivs(userIdent, tblPattern.getQualifiedCtl(), privs, errOnNonExist); + break; case DATABASE: - revokeDbPrivs(userIdent, tblPattern.getQualifiedDb(), privs, errOnNonExist); + revokeDbPrivs(userIdent, tblPattern.getQualifiedCtl(), + tblPattern.getQualifiedDb(), privs, errOnNonExist); break; case TABLE: - revokeTblPrivs(userIdent, tblPattern.getQualifiedDb(), tblPattern.getTbl(), privs, - errOnNonExist); + revokeTblPrivs(userIdent, tblPattern.getQualifiedCtl(), tblPattern.getQualifiedDb(), + tblPattern.getTbl(), privs, errOnNonExist); break; default: Preconditions.checkNotNull(null, tblPattern.getPrivLevel()); @@ -1311,6 +1392,17 @@ public class PaloAuth implements Writable { } } + // catalog + String ctlPrivs = catalogPrivTable.entries.stream() + .filter(entry -> entry.match(userIdent, true)) + .map(entry -> String.format("%s: %s (%b)", + ((CatalogPrivEntry) entry).getOrigCtl(), entry.privSet, entry.isSetByDomainResolver())) + .collect(Collectors.joining("; ")); + if (Strings.isNullOrEmpty(ctlPrivs)) { + ctlPrivs = FeConstants.null_string; + } + userAuthInfo.add(ctlPrivs); + // db List<String> dbPrivs = Lists.newArrayList(); Set<String> addedDbs = Sets.newHashSet(); @@ -1326,16 +1418,16 @@ public class PaloAuth implements Writable { PrivBitSet savedPrivs = dEntry.getPrivSet().copy(); savedPrivs.or(LdapPrivsChecker.getDbPrivFromLdap(userIdent, dEntry.getOrigDb())); addedDbs.add(dEntry.getOrigDb()); - dbPrivs.add(dEntry.getOrigDb() + ": " + savedPrivs.toString() - + " (" + entry.isSetByDomainResolver() + ")"); + dbPrivs.add(String.format("%s.%s: %s (%b)", dEntry.getOrigCtl(), dEntry.getOrigDb(), + savedPrivs, dEntry.isSetByDomainResolver())); } // Add privs from ldap groups that have not been added in Doris. if (LdapPrivsChecker.hasLdapPrivs(userIdent)) { Map<TablePattern, PrivBitSet> ldapDbPrivs = LdapPrivsChecker.getLdapAllDbPrivs(userIdent); for (Map.Entry<TablePattern, PrivBitSet> entry : ldapDbPrivs.entrySet()) { if (!addedDbs.contains(entry.getKey().getQualifiedDb())) { - dbPrivs.add(entry.getKey().getQualifiedDb() + ": " - + entry.getValue().toString() + " (" + false + ")"); + dbPrivs.add(String.format("%s.%s: %s (%b)", entry.getKey().getQualifiedCtl(), + entry.getKey().getQualifiedDb(), entry.getValue(), false)); } } } @@ -1361,17 +1453,15 @@ public class PaloAuth implements Writable { PrivBitSet savedPrivs = tEntry.getPrivSet().copy(); savedPrivs.or(LdapPrivsChecker.getTblPrivFromLdap(userIdent, tEntry.getOrigDb(), tEntry.getOrigTbl())); addedtbls.add(tEntry.getOrigDb().concat(".").concat(tEntry.getOrigTbl())); - tblPrivs.add(tEntry.getOrigDb() + "." + tEntry.getOrigTbl() + ": " - + savedPrivs.toString() - + " (" + entry.isSetByDomainResolver() + ")"); + tblPrivs.add(String.format("%s.%s.%s: %s (%b)", tEntry.getOrigCtl(), tEntry.getOrigDb(), + tEntry.getOrigTbl(), savedPrivs, tEntry.isSetByDomainResolver())); } // Add privs from ldap groups that have not been added in Doris. if (LdapPrivsChecker.hasLdapPrivs(userIdent)) { Map<TablePattern, PrivBitSet> ldapTblPrivs = LdapPrivsChecker.getLdapAllTblPrivs(userIdent); for (Map.Entry<TablePattern, PrivBitSet> entry : ldapTblPrivs.entrySet()) { if (!addedtbls.contains(entry.getKey().getQualifiedDb().concat(".").concat(entry.getKey().getTbl()))) { - tblPrivs.add(entry.getKey().getQualifiedDb().concat(".").concat(entry.getKey().getTbl()) - .concat(": ").concat(entry.getValue().toString()).concat(" (false)")); + tblPrivs.add(String.format("%s: %s (%b)", entry.getKey(), entry.getValue(), false)); } } } @@ -1662,6 +1752,7 @@ public class PaloAuth implements Writable { // role manager must be first, because role should be exist before any user roleManager.write(out); userPrivTable.write(out); + catalogPrivTable.write(out); dbPrivTable.write(out); tablePrivTable.write(out); resourcePrivTable.write(out); @@ -1672,6 +1763,13 @@ public class PaloAuth implements Writable { public void readFields(DataInput in) throws IOException { roleManager = RoleManager.read(in); userPrivTable = (UserPrivTable) PrivTable.read(in); + if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_111) { + catalogPrivTable = (CatalogPrivTable) PrivTable.read(in); + } else { + catalogPrivTable = userPrivTable.degradeToInternalCatalogPriv(); + LOG.info("Load PaloAuth from meta version < {}, degrade UserPrivTable to CatalogPrivTable", + FeMetaVersion.VERSION_111); + } dbPrivTable = (DbPrivTable) PrivTable.read(in); tablePrivTable = (TablePrivTable) PrivTable.read(in); resourcePrivTable = (ResourcePrivTable) PrivTable.read(in); diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivEntry.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivEntry.java index 906c29b6da..8f59a59777 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivEntry.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivEntry.java @@ -24,6 +24,7 @@ import org.apache.doris.common.PatternMatcher; import org.apache.doris.common.io.Text; import org.apache.doris.common.io.Writable; +import com.google.common.base.Preconditions; import org.apache.commons.lang.NotImplementedException; import java.io.DataInput; @@ -253,4 +254,19 @@ public abstract class PrivEntry implements Comparable<PrivEntry>, Writable { public int compareTo(PrivEntry o) { throw new NotImplementedException(); } + + /** + * Help derived classes compare in the order of 'user', 'host', 'catalog', 'db', 'ctl'. + * Compare strings[i] with strings[i+1] successively, return if the comparison value is not 0 in current loop. + */ + protected static int compareAssist(String... strings) { + Preconditions.checkState(strings.length % 2 == 0); + for (int i = 0; i < strings.length; i += 2) { + int res = strings[i].compareTo(strings[i + 1]); + if (res != 0) { + return res; + } + } + return 0; + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java index ce2f7738c3..054ca2e62e 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java @@ -45,6 +45,27 @@ public abstract class PrivTable implements Writable { // see PrivEntry for more detail protected boolean isClassNameWrote = false; + /* + * Check if user@host has specified privilege + */ + public boolean hasPriv(String host, String user, PrivPredicate wanted) { + for (PrivEntry entry : entries) { + // check host + if (!entry.isAnyHost() && !entry.getHostPattern().match(host)) { + continue; + } + // check user + if (!entry.isAnyUser() && !entry.getUserPattern().match(user)) { + continue; + } + // check priv + if (entry.privSet.satisfy(wanted)) { + return true; + } + } + return false; + } + /* * Add an entry to priv table. * If entry already exists and errOnExist is false, we try to reset or merge the new priv entry with existing one. 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 38a0683228..19e0c703f0 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 @@ -26,6 +26,7 @@ import org.apache.doris.common.io.Writable; import org.apache.doris.mysql.privilege.PaloAuth.PrivLevel; import com.google.common.base.Joiner; +import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -34,6 +35,9 @@ import java.io.DataOutput; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class RoleManager implements Writable { private Map<String, PaloRole> roles = Maps.newHashMap(); @@ -132,60 +136,26 @@ public class RoleManager implements Writable { info.add(role.getRoleName()); info.add(Joiner.on(", ").join(role.getUsers())); - // global - boolean hasGlobal = false; - for (Map.Entry<TablePattern, PrivBitSet> entry : role.getTblPatternToPrivs().entrySet()) { - if (entry.getKey().getPrivLevel() == PrivLevel.GLOBAL) { - hasGlobal = true; - info.add(entry.getValue().toString()); - // global priv should only has one - break; - } - } - if (!hasGlobal) { - info.add(FeConstants.null_string); - } - - // db - List<String> tmp = Lists.newArrayList(); - for (Map.Entry<TablePattern, PrivBitSet> entry : role.getTblPatternToPrivs().entrySet()) { - if (entry.getKey().getPrivLevel() == PrivLevel.DATABASE) { - tmp.add(entry.getKey().toString() + ": " + entry.getValue().toString()); - } - } - if (tmp.isEmpty()) { - info.add(FeConstants.null_string); - } else { - info.add(Joiner.on("; ").join(tmp)); - } - - - // tbl - tmp.clear(); - for (Map.Entry<TablePattern, PrivBitSet> entry : role.getTblPatternToPrivs().entrySet()) { - if (entry.getKey().getPrivLevel() == PrivLevel.TABLE) { - tmp.add(entry.getKey().toString() + ": " + entry.getValue().toString()); - } - } - if (tmp.isEmpty()) { - info.add(FeConstants.null_string); - } else { - info.add(Joiner.on("; ").join(tmp)); - } - - // resource - tmp.clear(); - for (Map.Entry<ResourcePattern, PrivBitSet> entry : role.getResourcePatternToPrivs().entrySet()) { - if (entry.getKey().getPrivLevel() == PrivLevel.RESOURCE) { - tmp.add(entry.getKey().toString() + ": " + entry.getValue().toString()); - } - } - if (tmp.isEmpty()) { - info.add(FeConstants.null_string); - } else { - info.add(Joiner.on("; ").join(tmp)); - } - + Map<PrivLevel, String> infoMap = role.getTblPatternToPrivs().entrySet().stream() + .collect(Collectors.groupingBy(entry -> entry.getKey().getPrivLevel())).entrySet().stream() + .collect(Collectors.toMap(Entry::getKey, entry -> { + if (entry.getKey() == PrivLevel.GLOBAL) { + return entry.getValue().stream().findFirst().map(priv -> priv.getValue().toString()) + .orElse(FeConstants.null_string); + } else { + return entry.getValue().stream() + .map(priv -> priv.getKey() + ": " + priv.getValue()) + .collect(Collectors.joining("; ")); + } + })); + Stream.of(PrivLevel.GLOBAL, PrivLevel.CATALOG, PrivLevel.DATABASE, PrivLevel.TABLE, PrivLevel.RESOURCE) + .forEach(level -> { + String infoItem = infoMap.get(level); + if (Strings.isNullOrEmpty(infoItem)) { + infoItem = FeConstants.null_string; + } + info.add(infoItem); + }); results.add(info); } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivEntry.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivEntry.java index c85a1f2912..7304d31922 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivEntry.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivEntry.java @@ -36,10 +36,13 @@ public class TablePrivEntry extends DbPrivEntry { protected TablePrivEntry() { } - private TablePrivEntry(PatternMatcher hostPattern, String origHost, PatternMatcher dbPattern, String origDb, - PatternMatcher userPattern, String user, PatternMatcher tblPattern, String origTbl, - boolean isDomain, PrivBitSet privSet) { - super(hostPattern, origHost, dbPattern, origDb, userPattern, user, isDomain, privSet); + private TablePrivEntry(PatternMatcher userPattern, String user, + PatternMatcher hostPattern, String origHost, + PatternMatcher ctlPattern, String origCtl, + PatternMatcher dbPattern, String origDb, + PatternMatcher tblPattern, String origTbl, + boolean isDomain, PrivBitSet privSet) { + super(userPattern, user, hostPattern, origHost, ctlPattern, origCtl, dbPattern, origDb, isDomain, privSet); this.tblPattern = tblPattern; this.origTbl = origTbl; if (origTbl.equals(ANY_TBL)) { @@ -47,12 +50,15 @@ public class TablePrivEntry extends DbPrivEntry { } } - public static TablePrivEntry create(String host, String db, String user, String tbl, boolean isDomain, - PrivBitSet privs) throws AnalysisException { + public static TablePrivEntry create(String user, String host, + String ctl, String db, String tbl, + boolean isDomain, PrivBitSet privs) throws AnalysisException { PatternMatcher hostPattern = PatternMatcher.createMysqlPattern(host, CaseSensibility.HOST.getCaseSensibility()); PatternMatcher dbPattern = PatternMatcher.createFlatPattern( db, CaseSensibility.DATABASE.getCaseSensibility(), db.equals(ANY_DB)); PatternMatcher userPattern = PatternMatcher.createFlatPattern(user, CaseSensibility.USER.getCaseSensibility()); + PatternMatcher ctlPattern = PatternMatcher.createFlatPattern( + ctl, CaseSensibility.CATALOG.getCaseSensibility(), ctl.equals(ANY_CTL)); PatternMatcher tblPattern = PatternMatcher.createFlatPattern( tbl, CaseSensibility.TABLE.getCaseSensibility(), tbl.equals(ANY_TBL)); @@ -61,8 +67,8 @@ public class TablePrivEntry extends DbPrivEntry { throw new AnalysisException("Table privilege can not contains global or resource privileges: " + privs); } - return new TablePrivEntry(hostPattern, host, dbPattern, db, - userPattern, user, tblPattern, tbl, isDomain, privs); + return new TablePrivEntry(userPattern, user, hostPattern, host, + ctlPattern, ctl, dbPattern, db, tblPattern, tbl, isDomain, privs); } public PatternMatcher getTblPattern() { @@ -84,22 +90,11 @@ public class TablePrivEntry extends DbPrivEntry { } TablePrivEntry otherEntry = (TablePrivEntry) other; - int res = origHost.compareTo(otherEntry.origHost); - if (res != 0) { - return -res; - } - - res = origDb.compareTo(otherEntry.origDb); - if (res != 0) { - return -res; - } - - res = origUser.compareTo(otherEntry.origUser); - if (res != 0) { - return -res; - } - - return -origTbl.compareTo(otherEntry.origTbl); + return compareAssist(origUser, otherEntry.origUser, + origHost, otherEntry.origHost, + origCtl, otherEntry.origCtl, + origDb, otherEntry.origDb, + origTbl, otherEntry.origTbl); } @Override @@ -109,21 +104,16 @@ public class TablePrivEntry extends DbPrivEntry { } TablePrivEntry otherEntry = (TablePrivEntry) other; - if (origHost.equals(otherEntry.origHost) && origUser.equals(otherEntry.origUser) - && origDb.equals(otherEntry.origDb) && origTbl.equals(otherEntry.origTbl) - && isDomain == otherEntry.isDomain) { - return true; - } - return false; + return origUser.equals(otherEntry.origUser) && origHost.equals(otherEntry.origHost) + && origCtl.equals(otherEntry.origCtl) && origDb.equals(otherEntry.origDb) + && origTbl.equals(otherEntry.origTbl) && isDomain == otherEntry.isDomain; } @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("db priv. host: ").append(origHost).append(", db: ").append(origDb); - sb.append(", user: ").append(origUser).append(", tbl: ").append(origTbl); - sb.append(", priv: ").append(privSet).append(", set by resolver: ").append(isSetByDomainResolver); - return sb.toString(); + return String.format("table privilege. user: %s, host: %s, " + + "ctl: %s, db: %s, tbl: %s, priv: %s, set by resolver: %b", + origUser, origHost, origCtl, origDb, origTbl, privSet.toString(), isSetByDomainResolver); } @Override diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivTable.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivTable.java index 0de7816981..475452ac29 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivTable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivTable.java @@ -32,10 +32,10 @@ import java.io.IOException; public class TablePrivTable extends PrivTable { /* - * Return first priv which match the user@host on db.tbl The returned priv will + * Return first priv which match the user@host on ctl.db.tbl The returned priv will * be saved in 'savedPrivs'. */ - public void getPrivs(UserIdentity currentUser, String db, String tbl, PrivBitSet savedPrivs) { + public void getPrivs(UserIdentity currentUser, String ctl, String db, String tbl, PrivBitSet savedPrivs) { TablePrivEntry matchedEntry = null; for (PrivEntry entry : entries) { TablePrivEntry tblPrivEntry = (TablePrivEntry) entry; @@ -43,6 +43,11 @@ public class TablePrivTable extends PrivTable { continue; } + // check catalog + if (!tblPrivEntry.isAnyCtl() && !tblPrivEntry.getCtlPattern().match(ctl)) { + continue; + } + // check db Preconditions.checkState(!tblPrivEntry.isAnyDb()); if (!tblPrivEntry.getDbPattern().match(db)) { @@ -64,33 +69,17 @@ public class TablePrivTable extends PrivTable { savedPrivs.or(matchedEntry.getPrivSet()); } - /* - * Check if user@host has specified privilege on any table - */ - public boolean hasPriv(String host, String user, PrivPredicate wanted) { + public boolean hasPrivsOfDb(UserIdentity currentUser, String ctl, String db) { for (PrivEntry entry : entries) { TablePrivEntry tblPrivEntry = (TablePrivEntry) entry; - // check host - if (!tblPrivEntry.isAnyHost() && !tblPrivEntry.getHostPattern().match(host)) { - continue; - } - // check user - if (!tblPrivEntry.isAnyUser() && !tblPrivEntry.getUserPattern().match(user)) { + + if (!tblPrivEntry.match(currentUser, true)) { continue; } - // check priv - if (tblPrivEntry.privSet.satisfy(wanted)) { - return true; - } - } - return false; - } - public boolean hasPrivsOfDb(UserIdentity currentUser, String db) { - for (PrivEntry entry : entries) { - TablePrivEntry tblPrivEntry = (TablePrivEntry) entry; - - if (!tblPrivEntry.match(currentUser, true)) { + // check catalog + Preconditions.checkState(!tblPrivEntry.isAnyCtl()); + if (!tblPrivEntry.getCtlPattern().match(ctl)) { continue; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPrivTable.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPrivTable.java index 2ae20c3658..c50f5569bf 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPrivTable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPrivTable.java @@ -20,6 +20,7 @@ package org.apache.doris.mysql.privilege; import org.apache.doris.analysis.UserIdentity; import org.apache.doris.common.DdlException; import org.apache.doris.common.io.Text; +import org.apache.doris.datasource.InternalDataSource; import org.apache.doris.mysql.MysqlPassword; import org.apache.logging.log4j.LogManager; @@ -27,6 +28,7 @@ import org.apache.logging.log4j.Logger; import java.io.DataOutput; import java.io.IOException; +import java.util.LinkedList; import java.util.List; /* @@ -57,27 +59,6 @@ public class UserPrivTable extends PrivTable { savedPrivs.or(matchedEntry.getPrivSet()); } - /* - * Check if user@host has specified privilege - */ - public boolean hasPriv(String host, String user, PrivPredicate wanted) { - for (PrivEntry entry : entries) { - GlobalPrivEntry globalPrivEntry = (GlobalPrivEntry) entry; - // check host - if (!globalPrivEntry.isAnyHost() && !globalPrivEntry.getHostPattern().match(host)) { - continue; - } - // check user - if (!globalPrivEntry.isAnyUser() && !globalPrivEntry.getUserPattern().match(user)) { - continue; - } - if (globalPrivEntry.getPrivSet().satisfy(wanted)) { - return true; - } - } - return false; - } - // validate the connection by host, user and password. // return true if this connection is valid, and 'savedPrivs' save all global privs got from user table. // if currentUser is not null, save the current user identity @@ -196,4 +177,33 @@ public class UserPrivTable extends PrivTable { super.write(out); } + + /** + * When replay UserPrivTable from journal whose FeMetaVersion < VERSION_111, the global-level privileges should + * degrade to internal-catalog-level privileges. + */ + public CatalogPrivTable degradeToInternalCatalogPriv() throws IOException { + CatalogPrivTable catalogPrivTable = new CatalogPrivTable(); + List<PrivEntry> degradedEntries = new LinkedList<>(); + for (PrivEntry privEntry : entries) { + GlobalPrivEntry globalPrivEntry = (GlobalPrivEntry) privEntry; + if (!globalPrivEntry.match(UserIdentity.ROOT, true) + && !globalPrivEntry.match(UserIdentity.ADMIN, true) + && !globalPrivEntry.privSet.isEmpty()) { + try { + CatalogPrivEntry entry = CatalogPrivEntry.create(globalPrivEntry.origUser, globalPrivEntry.origHost, + InternalDataSource.INTERNAL_DS_NAME, globalPrivEntry.isDomain, globalPrivEntry.privSet); + entry.setSetByDomainResolver(false); + catalogPrivTable.addEntry(entry, false, false); + degradedEntries.add(globalPrivEntry); + } catch (Exception e) { + throw new IOException(e.getMessage()); + } + } + } + for (PrivEntry degraded : degradedEntries) { + dropEntry(degraded); + } + return catalogPrivTable; + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java index 31f71ecb40..724278af10 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java @@ -23,6 +23,7 @@ import org.apache.doris.catalog.Database; import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.UserException; import org.apache.doris.common.util.DebugUtil; +import org.apache.doris.datasource.InternalDataSource; import org.apache.doris.datasource.SessionContext; import org.apache.doris.mysql.MysqlCapability; import org.apache.doris.mysql.MysqlChannel; @@ -108,6 +109,7 @@ public class ConnectContext { // Catalog: put catalog here is convenient for unit test, // because catalog is singleton, hard to mock protected Catalog catalog; + protected String defaultCatalog = InternalDataSource.INTERNAL_DS_NAME; protected boolean isSend; protected AuditEventBuilder auditEventBuilder = new AuditEventBuilder(); @@ -290,6 +292,7 @@ public class ConnectContext { public void setCatalog(Catalog catalog) { this.catalog = catalog; + defaultCatalog = catalog.getInternalDataSource().getName(); } public Catalog getCatalog() { @@ -410,6 +413,14 @@ public class ConnectContext { return serverCapability; } + public String getDefaultCatalog() { + return defaultCatalog; + } + + public void changeDefaultCatalog(String catalogName) { + defaultCatalog = catalogName; + } + public String getDatabase() { return currentDb; } 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 886783896a..4c2d7e7ce9 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 @@ -35,6 +35,7 @@ import org.apache.doris.common.AnalysisException; import org.apache.doris.common.Config; import org.apache.doris.common.DdlException; import org.apache.doris.common.UserException; +import org.apache.doris.datasource.InternalDataSource; import org.apache.doris.persist.EditLog; import org.apache.doris.persist.PrivInfo; import org.apache.doris.qe.ConnectContext; @@ -102,6 +103,10 @@ public class AuthTest { minTimes = 0; result = SystemInfoService.DEFAULT_CLUSTER; + analyzer.getDefaultCatalog(); + minTimes = 0; + result = InternalDataSource.INTERNAL_DS_NAME; + Catalog.getCurrentCatalog(); minTimes = 0; result = catalog; @@ -1242,7 +1247,7 @@ public class AuthTest { } }; Assert.assertFalse(auth.checkGlobalPriv(ctx, PrivPredicate.OPERATOR)); - grantStmt = new GrantStmt(opUser, null, new TablePattern("*", "*"), privileges); + grantStmt = new GrantStmt(opUser, null, new TablePattern("*", "*", "*"), privileges); // first, use op_user itself to grant node_priv, which is not allowed try { new Expectations() { diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PrivEntryTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PrivEntryTest.java index 71f6990191..8e9a3f173b 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PrivEntryTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PrivEntryTest.java @@ -26,7 +26,7 @@ public class PrivEntryTest { @Test public void testNameWithUnderscores() throws Exception { TablePrivEntry tablePrivEntry = TablePrivEntry.create( - "127.%", "db_db1", "user1", "tbl_tbl1", false, + "user1", "127.%", "__internal", "db_db1", "tbl_tbl1", false, PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.DROP_PRIV)); // pattern match Assert.assertFalse(tablePrivEntry.getDbPattern().match("db-db1")); @@ -38,11 +38,11 @@ public class PrivEntryTest { userIdentity.setIsAnalyzed(); PrivBitSet privs1 = PrivBitSet.of(); - tablePrivTable.getPrivs(userIdentity, "db#db1", "tbl#tbl1", privs1); + tablePrivTable.getPrivs(userIdentity, "##internal", "db#db1", "tbl#tbl1", privs1); Assert.assertFalse(PaloPrivilege.satisfy(privs1, PrivPredicate.DROP)); PrivBitSet privs2 = PrivBitSet.of(); - tablePrivTable.getPrivs(userIdentity, "db_db1", "tbl_tbl1", privs2); + tablePrivTable.getPrivs(userIdentity, "__internal", "db_db1", "tbl_tbl1", privs2); Assert.assertTrue(PaloPrivilege.satisfy(privs2, PrivPredicate.DROP)); } } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org