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
The following commit(s) were added to refs/heads/branch-2.0 by this push:
new 044634e2289 [fix](Nereids) support check authorization for view but
skip check in the view #31289 (#31294)
044634e2289 is described below
commit 044634e22894e425f6ae8713d888e8fd4c8d4dd9
Author: 924060929 <[email protected]>
AuthorDate: Fri Feb 23 12:46:49 2024 +0800
[fix](Nereids) support check authorization for view but skip check in the
view #31289 (#31294)
---
.../org/apache/doris/nereids/CascadesContext.java | 30 +++++---
.../doris/nereids/jobs/executor/Analyzer.java | 7 +-
.../doris/nereids/rules/analysis/BindRelation.java | 18 +++--
.../nereids/rules/analysis/UserAuthentication.java | 33 +++------
.../apache/doris/external/hms/HmsCatalogTest.java | 20 ++++++
.../org/apache/doris/qe/HmsQueryCacheTest.java | 16 +++++
.../account_p0/test_nereids_row_policy.groovy | 10 ++-
.../authorization/view_authorization.groovy | 82 ++++++++++++++++++++++
8 files changed, 172 insertions(+), 44 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/CascadesContext.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/CascadesContext.java
index dc1b40b4db1..434b87b3fcb 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/CascadesContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/CascadesContext.java
@@ -116,6 +116,7 @@ public class CascadesContext implements ScheduleContext {
private boolean isLeadingJoin = false;
private final Map<String, Hint> hintMap = Maps.newLinkedHashMap();
+ private final boolean shouldCheckRelationAuthentication;
/**
* Constructor of OptimizerContext.
@@ -125,7 +126,7 @@ public class CascadesContext implements ScheduleContext {
*/
private CascadesContext(Optional<CascadesContext> parent, Optional<CTEId>
currentTree,
StatementContext statementContext, Plan plan, Memo memo,
- CTEContext cteContext, PhysicalProperties requireProperties) {
+ CTEContext cteContext, PhysicalProperties requireProperties,
boolean shouldCheckRelationAuthentication) {
this.parent = Objects.requireNonNull(parent, "parent should not null");
this.currentTree = Objects.requireNonNull(currentTree, "currentTree
should not null");
this.statementContext = Objects.requireNonNull(statementContext,
"statementContext should not null");
@@ -138,6 +139,7 @@ public class CascadesContext implements ScheduleContext {
this.currentJobContext = new JobContext(this, requireProperties,
Double.MAX_VALUE);
this.subqueryExprIsAnalyzed = new HashMap<>();
this.runtimeFilterContext = new
RuntimeFilterContext(getConnectContext().getSessionVariable());
+ this.shouldCheckRelationAuthentication =
shouldCheckRelationAuthentication;
}
/**
@@ -146,7 +148,13 @@ public class CascadesContext implements ScheduleContext {
public static CascadesContext initContext(StatementContext
statementContext,
Plan initPlan, PhysicalProperties requireProperties) {
return newContext(Optional.empty(), Optional.empty(), statementContext,
- initPlan, new CTEContext(), requireProperties);
+ initPlan, new CTEContext(), requireProperties, true);
+ }
+
+ public static CascadesContext initViewContext(StatementContext
statementContext,
+ Plan initPlan,
PhysicalProperties requireProperties) {
+ return newContext(Optional.empty(), Optional.empty(), statementContext,
+ initPlan, new CTEContext(), requireProperties, false);
}
/**
@@ -155,13 +163,14 @@ public class CascadesContext implements ScheduleContext {
public static CascadesContext newContextWithCteContext(CascadesContext
cascadesContext,
Plan initPlan, CTEContext cteContext) {
return newContext(Optional.of(cascadesContext), Optional.empty(),
- cascadesContext.getStatementContext(), initPlan, cteContext,
PhysicalProperties.ANY);
+ cascadesContext.getStatementContext(), initPlan, cteContext,
PhysicalProperties.ANY,
+ cascadesContext.shouldCheckRelationAuthentication);
}
public static CascadesContext newCurrentTreeContext(CascadesContext
context) {
return CascadesContext.newContext(context.getParent(),
context.getCurrentTree(), context.getStatementContext(),
context.getRewritePlan(), context.getCteContext(),
- context.getCurrentJobContext().getRequiredProperties());
+ context.getCurrentJobContext().getRequiredProperties(),
context.shouldCheckRelationAuthentication);
}
/**
@@ -170,13 +179,14 @@ public class CascadesContext implements ScheduleContext {
public static CascadesContext newSubtreeContext(Optional<CTEId> subtree,
CascadesContext context,
Plan plan, PhysicalProperties requireProperties) {
return CascadesContext.newContext(Optional.of(context), subtree,
context.getStatementContext(),
- plan, context.getCteContext(), requireProperties);
+ plan, context.getCteContext(), requireProperties,
context.shouldCheckRelationAuthentication);
}
private static CascadesContext newContext(Optional<CascadesContext>
parent, Optional<CTEId> subtree,
- StatementContext statementContext, Plan initPlan,
- CTEContext cteContext, PhysicalProperties requireProperties) {
- return new CascadesContext(parent, subtree, statementContext,
initPlan, null, cteContext, requireProperties);
+ StatementContext statementContext, Plan initPlan, CTEContext
cteContext,
+ PhysicalProperties requireProperties, boolean
shouldCheckRelationAuthentication) {
+ return new CascadesContext(parent, subtree, statementContext,
initPlan, null,
+ cteContext, requireProperties, shouldCheckRelationAuthentication);
}
public CascadesContext getRoot() {
@@ -622,6 +632,10 @@ public class CascadesContext implements ScheduleContext {
isLeadingJoin = leadingJoin;
}
+ public boolean shouldCheckRelationAuthentication() {
+ return shouldCheckRelationAuthentication;
+ }
+
public Map<String, Hint> getHintMap() {
return hintMap;
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Analyzer.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Analyzer.java
index 4ce556c5038..78fdd17a7a6 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Analyzer.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Analyzer.java
@@ -41,7 +41,6 @@ import
org.apache.doris.nereids.rules.analysis.ProjectWithDistinctToAggregate;
import org.apache.doris.nereids.rules.analysis.ReplaceExpressionByChildOutput;
import
org.apache.doris.nereids.rules.analysis.ResolveOrdinalInOrderByAndGroupBy;
import org.apache.doris.nereids.rules.analysis.SubqueryToApply;
-import org.apache.doris.nereids.rules.analysis.UserAuthentication;
import org.apache.doris.nereids.rules.rewrite.JoinCommute;
import org.apache.doris.nereids.rules.rewrite.MergeProjects;
@@ -115,8 +114,7 @@ public class Analyzer extends AbstractBatchJobExecutor {
topDown(new AnalyzeCTE()),
bottomUp(
new BindRelation(customTableResolver),
- new CheckPolicy(),
- new UserAuthentication()
+ new CheckPolicy()
)
);
}
@@ -128,8 +126,7 @@ public class Analyzer extends AbstractBatchJobExecutor {
topDown(new EliminateLogicalSelectHint()),
bottomUp(
new BindRelation(customTableResolver),
- new CheckPolicy(),
- new UserAuthentication()
+ new CheckPolicy()
),
bottomUp(new BindExpression()),
topDown(new BindSink()),
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
index 1d40880d7de..b8884b3334d 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
@@ -157,7 +157,7 @@ public class BindRelation extends OneAnalysisRuleFactory {
}
// TODO: should generate different Scan sub class according to table's
type
- LogicalPlan scan = getLogicalPlan(table, unboundRelation,
tableQualifier, cascadesContext);
+ LogicalPlan scan = getAndCheckLogicalPlan(table, unboundRelation,
tableQualifier, cascadesContext);
if (cascadesContext.isLeadingJoin()) {
LeadingHint leading = (LeadingHint)
cascadesContext.getHintMap().get("Leading");
leading.putRelationIdAndTableName(Pair.of(unboundRelation.getRelationId(),
tableName));
@@ -178,7 +178,7 @@ public class BindRelation extends OneAnalysisRuleFactory {
if (table == null) {
table = RelationUtil.getTable(tableQualifier,
cascadesContext.getConnectContext().getEnv());
}
- return getLogicalPlan(table, unboundRelation, tableQualifier,
cascadesContext);
+ return getAndCheckLogicalPlan(table, unboundRelation, tableQualifier,
cascadesContext);
}
private LogicalPlan makeOlapScan(TableIf table, UnboundRelation
unboundRelation, List<String> tableQualifier) {
@@ -234,7 +234,17 @@ public class BindRelation extends OneAnalysisRuleFactory {
return scan;
}
- private LogicalPlan getLogicalPlan(TableIf table, UnboundRelation
unboundRelation, List<String> tableQualifier,
+ private LogicalPlan getAndCheckLogicalPlan(TableIf table, UnboundRelation
unboundRelation,
+ List<String> tableQualifier,
CascadesContext cascadesContext) {
+ // if current context is in the view, we can skip check authentication
because
+ // the view already checked authentication
+ if (cascadesContext.shouldCheckRelationAuthentication()) {
+ UserAuthentication.checkPermission(table,
cascadesContext.getConnectContext());
+ }
+ return doGetLogicalPlan(table, unboundRelation, tableQualifier,
cascadesContext);
+ }
+
+ private LogicalPlan doGetLogicalPlan(TableIf table, UnboundRelation
unboundRelation, List<String> tableQualifier,
CascadesContext cascadesContext) {
switch (table.getType()) {
case OLAP:
@@ -288,7 +298,7 @@ public class BindRelation extends OneAnalysisRuleFactory {
if (parsedViewPlan instanceof UnboundResultSink) {
parsedViewPlan = (LogicalPlan) ((UnboundResultSink<?>)
parsedViewPlan).child();
}
- CascadesContext viewContext = CascadesContext.initContext(
+ CascadesContext viewContext = CascadesContext.initViewContext(
parentContext.getStatementContext(), parsedViewPlan,
PhysicalProperties.ANY);
viewContext.newAnalyzer(true, customTableResolver).analyze();
// we should remove all group expression of the plan which in other
memo, so the groupId would not conflict
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/UserAuthentication.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/UserAuthentication.java
index 86fb64d6e56..ffed8d4ea93 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/UserAuthentication.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/UserAuthentication.java
@@ -23,44 +23,31 @@ import org.apache.doris.common.ErrorCode;
import org.apache.doris.datasource.CatalogIf;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.nereids.exceptions.AnalysisException;
-import org.apache.doris.nereids.rules.Rule;
-import org.apache.doris.nereids.rules.RuleType;
-import org.apache.doris.nereids.trees.plans.Plan;
-import org.apache.doris.nereids.trees.plans.algebra.CatalogRelation;
import org.apache.doris.qe.ConnectContext;
/**
* Check whether a user is permitted to scan specific tables.
*/
-public class UserAuthentication extends OneAnalysisRuleFactory {
-
- @Override
- public Rule build() {
- return logicalRelation()
- .when(CatalogRelation.class::isInstance)
- .thenApply(ctx -> checkPermission((CatalogRelation) ctx.root,
ctx.connectContext))
- .toRule(RuleType.RELATION_AUTHENTICATION);
- }
-
- private Plan checkPermission(CatalogRelation relation, ConnectContext
connectContext) {
+public class UserAuthentication {
+ /** checkPermission. */
+ public static void checkPermission(TableIf table, ConnectContext
connectContext) {
+ if (table == null) {
+ return;
+ }
// do not check priv when replaying dump file
if (connectContext.getSessionVariable().isPlayNereidsDump()) {
- return null;
- }
- TableIf table = relation.getTable();
- if (table == null) {
- return null;
+ return;
}
String tableName = table.getName();
DatabaseIf db = table.getDatabase();
// when table inatanceof FunctionGenTable,db will be null
if (db == null) {
- return null;
+ return;
}
String dbName = db.getFullName();
CatalogIf catalog = db.getCatalog();
if (catalog == null) {
- return null;
+ return;
}
String ctlName = catalog.getName();
// TODO: 2023/7/19 checkColumnsPriv
@@ -71,7 +58,5 @@ public class UserAuthentication extends
OneAnalysisRuleFactory {
ctlName + ": " + dbName + ": " + tableName);
throw new AnalysisException(message);
}
- return null;
}
-
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/external/hms/HmsCatalogTest.java
b/fe/fe-core/src/test/java/org/apache/doris/external/hms/HmsCatalogTest.java
index 66e22fd4c1c..556297fa4f6 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/external/hms/HmsCatalogTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/external/hms/HmsCatalogTest.java
@@ -127,6 +127,10 @@ public class HmsCatalogTest extends AnalyzeCheckTestBase {
tbl.getType();
minTimes = 0;
result = TableIf.TableType.HMS_EXTERNAL_TABLE;
+
+ tbl.getDatabase();
+ minTimes = 0;
+ result = db;
}
};
@@ -169,6 +173,10 @@ public class HmsCatalogTest extends AnalyzeCheckTestBase {
view1.isSupportedHmsTable();
minTimes = 0;
result = true;
+
+ view1.getDatabase();
+ minTimes = 0;
+ result = db;
}
};
@@ -211,6 +219,10 @@ public class HmsCatalogTest extends AnalyzeCheckTestBase {
view2.isSupportedHmsTable();
minTimes = 0;
result = true;
+
+ view2.getDatabase();
+ minTimes = 0;
+ result = db;
}
};
@@ -253,6 +265,10 @@ public class HmsCatalogTest extends AnalyzeCheckTestBase {
view3.isSupportedHmsTable();
minTimes = 0;
result = true;
+
+ view3.getDatabase();
+ minTimes = 0;
+ result = db;
}
};
@@ -295,6 +311,10 @@ public class HmsCatalogTest extends AnalyzeCheckTestBase {
view4.isSupportedHmsTable();
minTimes = 0;
result = true;
+
+ view4.getDatabase();
+ minTimes = 0;
+ result = db;
}
};
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/qe/HmsQueryCacheTest.java
b/fe/fe-core/src/test/java/org/apache/doris/qe/HmsQueryCacheTest.java
index 16de423b28d..61db0da9011 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/qe/HmsQueryCacheTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/qe/HmsQueryCacheTest.java
@@ -159,6 +159,10 @@ public class HmsQueryCacheTest extends
AnalyzeCheckTestBase {
// mock initSchemaAndUpdateTime and do nothing
tbl.initSchemaAndUpdateTime();
minTimes = 0;
+
+ tbl.getDatabase();
+ minTimes = 0;
+ result = db;
}
};
@@ -203,6 +207,10 @@ public class HmsQueryCacheTest extends
AnalyzeCheckTestBase {
// mock initSchemaAndUpdateTime and do nothing
tbl2.initSchemaAndUpdateTime();
minTimes = 0;
+
+ tbl2.getDatabase();
+ minTimes = 0;
+ result = db;
}
};
@@ -254,6 +262,10 @@ public class HmsQueryCacheTest extends
AnalyzeCheckTestBase {
view1.getUpdateTime();
minTimes = 0;
result = NOW;
+
+ view1.getDatabase();
+ minTimes = 0;
+ result = db;
}
};
@@ -304,6 +316,10 @@ public class HmsQueryCacheTest extends
AnalyzeCheckTestBase {
view2.getUpdateTime();
minTimes = 0;
result = NOW;
+
+ view2.getDatabase();
+ minTimes = 0;
+ result = db;
}
};
diff --git a/regression-test/suites/account_p0/test_nereids_row_policy.groovy
b/regression-test/suites/account_p0/test_nereids_row_policy.groovy
index d12b11261d8..1a81f6ddd18 100644
--- a/regression-test/suites/account_p0/test_nereids_row_policy.groovy
+++ b/regression-test/suites/account_p0/test_nereids_row_policy.groovy
@@ -32,14 +32,17 @@ suite("test_nereids_row_policy") {
sql "set enable_fallback_to_original_planner = false"
sql "SELECT * FROM ${tableName}"
}
- def result3 = connect(user=user, password='123abc!@#', url=url) {
+ connect(user=user, password='123abc!@#', url=url) {
sql "set enable_nereids_planner = true"
sql "set enable_fallback_to_original_planner = false"
- sql "SELECT * FROM ${viewName}"
+
+ test {
+ sql "SELECT * FROM ${viewName}"
+ exception "SELECT command denied to user"
+ }
}
assertEquals(size, result1.size())
assertEquals(size, result2.size())
- assertEquals(size, result3.size())
}
def createPolicy = { name, predicate, type ->
@@ -79,6 +82,7 @@ suite("test_nereids_row_policy") {
sql "CREATE USER ${user} IDENTIFIED BY '123abc!@#'"
sql "GRANT SELECT_PRIV ON internal.${dbName}.${tableName} TO ${user}"
+ sql 'sync'
// no policy
assertQueryResult 3
diff --git
a/regression-test/suites/nereids_p0/authorization/view_authorization.groovy
b/regression-test/suites/nereids_p0/authorization/view_authorization.groovy
new file mode 100644
index 00000000000..672cd680ec8
--- /dev/null
+++ b/regression-test/suites/nereids_p0/authorization/view_authorization.groovy
@@ -0,0 +1,82 @@
+// 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.
+
+suite("view_authorization") {
+ def db = context.config.getDbNameByFile(context.file)
+ def user1 = "test_view_auth_user1"
+ def baseTable = "test_view_auth_base_table"
+ def view1 = "test_view_auth_view1"
+ def view2 = "test_view_auth_view2"
+ def view3 = "test_view_auth_view3"
+
+
+ sql "drop table if exists ${baseTable}"
+ sql "drop view if exists ${view1}"
+ sql "drop view if exists ${view2}"
+ sql "drop view if exists ${view3}"
+ sql "drop user if exists ${user1}"
+
+ sql """
+ CREATE TABLE ${baseTable} (id INT, name TEXT)
+ DISTRIBUTED BY HASH(`id`)
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ );
+ """
+
+ sql "insert into ${baseTable} values(1, 'hello'), (2, 'world'), (3,
'doris');"
+ sql "create view ${view1} as select *, concat(name, '_', id) from
${db}.${baseTable} where id=1;"
+ sql "create view ${view2} as select *, concat(name, '_', id) as xxx from
${db}.${baseTable} where id != 1;"
+ sql "create view ${view3} as select xxx, 100 from ${db}.${view2} where
id=3"
+
+ sql "create user ${user1}"
+ sql "grant SELECT_PRIV on ${db}.${view1} to '${user1}'@'%';"
+ sql "grant SELECT_PRIV on ${db}.${view3} to '${user1}'@'%';"
+
+ sql 'sync'
+
+ def defaultDbUrl = context.config.jdbcUrl.substring(0,
context.config.jdbcUrl.lastIndexOf("/"))
+ logger.info("connect to ${defaultDbUrl}".toString())
+ connect(user = user1, password = null, url = defaultDbUrl) {
+ sql "set enable_fallback_to_original_planner=false"
+
+ // no privilege to base table
+ test {
+ sql "select * from ${db}.${baseTable}"
+ exception "SELECT command denied to user"
+ }
+
+ // has privilege to view1
+ test {
+ sql "select * from ${db}.${view1}"
+ result([[1, 'hello', 'hello_1']])
+ }
+
+ // no privilege to view2
+ test {
+ sql "select * from ${db}.${view2}"
+ exception "SELECT command denied to user"
+ }
+
+ // nested view
+ // has privilege to view3
+ test {
+ sql "select * from ${db}.${view3}"
+ result([['doris_3', 100]])
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]