branch-2.1:[fix](auth)Fix the issue of incorrectly checking base table permissio… (#54005)

…ns when querying external views (#53786)

pick: https://github.com/apache/doris/pull/53786
This commit is contained in:
zhangdong
2025-08-01 11:33:12 +08:00
committed by GitHub
parent 72bde71602
commit 8523fdeba3
6 changed files with 315 additions and 10 deletions

View File

@ -49,7 +49,7 @@ import java.util.List;
* Refreshing or invalidating a view will reload the view's definition but will not
* affect the metadata of the underlying tables (if any).
*/
public class View extends Table {
public class View extends Table implements ViewIf {
private static final Logger LOG = LogManager.getLogger(View.class);
// The original SQL-string given as view definition. Set during analysis.
@ -207,6 +207,11 @@ public class View extends Table {
return colLabels != null;
}
@Override
public String getViewText() {
return inlineViewDef;
}
// Get the md5 of signature string of this view.
// This method is used to determine whether the views have the same schema.
// Contains:

View File

@ -0,0 +1,23 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.doris.catalog;
public interface ViewIf extends TableIf {
String getViewText();
}

View File

@ -0,0 +1,204 @@
// 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.datasource;
import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.DatabaseIf;
import org.apache.doris.catalog.ViewIf;
import org.apache.doris.common.Pair;
import org.apache.doris.statistics.AnalysisInfo;
import org.apache.doris.statistics.BaseAnalysisTask;
import org.apache.doris.statistics.ColumnStatistic;
import org.apache.doris.statistics.TableStatsMeta;
import org.apache.doris.thrift.TTableDescriptor;
import java.io.DataOutput;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
public class ExternalView implements ViewIf {
private String viewText;
private ExternalTable externalTable;
public ExternalView(ExternalTable externalTable, String viewText) {
this.viewText = viewText;
this.externalTable = externalTable;
}
@Override
public String getViewText() {
return viewText;
}
public ExternalTable getExternalTable() {
return externalTable;
}
@Override
public long getId() {
return externalTable.getId();
}
public String getName() {
return externalTable.getName();
}
@Override
public TableType getType() {
return externalTable.getType();
}
@Override
public List<Column> getFullSchema() {
return externalTable.getFullSchema();
}
@Override
public List<Column> getBaseSchema() {
return externalTable.getBaseSchema();
}
@Override
public List<Column> getSchemaAllIndexes(boolean full) {
return externalTable.getSchemaAllIndexes(full);
}
@Override
public List<Column> getBaseSchema(boolean full) {
return externalTable.getBaseSchema();
}
@Override
public void setNewFullSchema(List<Column> newSchema) {
externalTable.setNewFullSchema(newSchema);
}
@Override
public Column getColumn(String name) {
return externalTable.getColumn(name);
}
@Override
public String getMysqlType() {
return externalTable.getMysqlType();
}
@Override
public String getEngine() {
return externalTable.getEngine();
}
@Override
public String getComment() {
return externalTable.getComment();
}
@Override
public long getCreateTime() {
return externalTable.getCreateTime();
}
@Override
public long getUpdateTime() {
return externalTable.getUpdateTime();
}
@Override
public long getRowCount() {
return externalTable.getRowCount();
}
@Override
public long getCachedRowCount() {
return externalTable.getCachedRowCount();
}
@Override
public long fetchRowCount() {
return externalTable.fetchRowCount();
}
@Override
public long getDataLength() {
return externalTable.getDataLength();
}
@Override
public long getAvgRowLength() {
return externalTable.getAvgRowLength();
}
@Override
public long getLastCheckTime() {
return externalTable.getLastCheckTime();
}
@Override
public String getComment(boolean escapeQuota) {
return externalTable.getComment();
}
@Override
public TTableDescriptor toThrift() {
return externalTable.toThrift();
}
@Override
public BaseAnalysisTask createAnalysisTask(AnalysisInfo info) {
return externalTable.createAnalysisTask(info);
}
@Override
public DatabaseIf getDatabase() {
return externalTable.getDatabase();
}
@Override
public Optional<ColumnStatistic> getColumnStatistic(String colName) {
return externalTable.getColumnStatistic(colName);
}
@Override
public boolean needReAnalyzeTable(TableStatsMeta tblStats) {
return false;
}
@Override
public List<Pair<String, String>> getColumnIndexPairs(Set<String> columns) {
return externalTable.getColumnIndexPairs(columns);
}
@Override
public List<Long> getChunkSizes() {
return externalTable.getChunkSizes();
}
@Override
public void write(DataOutput out) throws IOException {
externalTable.write(out);
}
@Override
public boolean autoAnalyzeEnabled() {
return externalTable.autoAnalyzeEnabled();
}
}

View File

@ -33,6 +33,7 @@ import org.apache.doris.common.Config;
import org.apache.doris.common.Pair;
import org.apache.doris.common.util.Util;
import org.apache.doris.datasource.ExternalTable;
import org.apache.doris.datasource.ExternalView;
import org.apache.doris.datasource.hive.HMSExternalTable;
import org.apache.doris.datasource.hive.HMSExternalTable.DLAType;
import org.apache.doris.nereids.CTEContext;
@ -464,7 +465,8 @@ public class BindRelation extends OneAnalysisRuleFactory {
ctx.changeDefaultCatalog(hiveCatalog);
ctx.setDatabase(hiveDb);
try {
return parseAndAnalyzeView(table, ddlSql, cascadesContext);
return new LogicalView<>(new ExternalView(table, ddlSql),
parseAndAnalyzeView(table, ddlSql, cascadesContext));
} finally {
// restore catalog and db in connect context
ctx.changeDefaultCatalog(previousCatalog);

View File

@ -17,7 +17,7 @@
package org.apache.doris.nereids.trees.plans.logical;
import org.apache.doris.catalog.View;
import org.apache.doris.catalog.ViewIf;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.memo.GroupExpression;
import org.apache.doris.nereids.properties.FdItem;
@ -40,10 +40,10 @@ import java.util.Optional;
/** LogicalView */
public class LogicalView<BODY extends Plan> extends LogicalUnary<BODY> {
private final View view;
private final ViewIf view;
/** LogicalView */
public LogicalView(View view, BODY body) {
public LogicalView(ViewIf view, BODY body) {
super(PlanType.LOGICAL_VIEW, Optional.empty(), Optional.empty(), body);
this.view = Objects.requireNonNull(view, "catalog can not be null");
if (!(body instanceof LogicalPlan)) {
@ -73,11 +73,7 @@ public class LogicalView<BODY extends Plan> extends LogicalUnary<BODY> {
return view.getName();
}
public String getViewString() {
return view.getInlineViewDef();
}
public View getView() {
public ViewIf getView() {
return view;
}

View File

@ -0,0 +1,75 @@
// 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("test_select_external_view_auth","p0,auth") {
String enabled = context.config.otherConfigs.get("enableHiveTest")
if (enabled == null || !enabled.equalsIgnoreCase("true")) {
logger.info("diable Hive test.")
return;
}
for (String hivePrefix : ["hive2", "hive3"]) {
try {
String hms_port = context.config.otherConfigs.get(hivePrefix + "HmsPort")
String catalogName = "${hivePrefix}_test_mtmv"
String externalEnvIp = context.config.otherConfigs.get("externalEnvIp")
sql """drop catalog if exists ${catalogName}"""
sql """create catalog if not exists ${catalogName} properties (
"type"="hms",
'hive.metastore.uris' = 'thrift://${externalEnvIp}:${hms_port}'
);"""
String suiteName = "test_select_external_view_auth"
String user = "${suiteName}_user"
String pwd = 'C123_567p'
String dbName = "`default`"
String tableName = "sale_table"
String viewName = "test_view1"
try_sql("drop user ${user}")
sql """create user '${user}' IDENTIFIED by '${pwd}'"""
sql """grant select_priv on regression_test to ${user}"""
//cloud-mode
if (isCloudMode()) {
def clusters = sql " SHOW CLUSTERS; "
assertTrue(!clusters.isEmpty())
def validCluster = clusters[0][0]
sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}""";
}
sql """grant select_priv on ${catalogName}.${dbName}.${tableName} to ${user}"""
// table column
connect(user, "${pwd}", context.config.jdbcUrl) {
try {
sql "select * from ${catalogName}.${dbName}.${viewName}"
} catch (Exception e) {
log.info(e.getMessage())
assertTrue(e.getMessage().contains("denied"))
}
}
sql """revoke select_priv on ${catalogName}.${dbName}.${tableName} from ${user}"""
sql """grant select_priv on ${catalogName}.${dbName}.${viewName} to ${user}"""
connect(user, "${pwd}", context.config.jdbcUrl) {
sql "select * from ${catalogName}.${dbName}.${viewName}"
}
try_sql("drop user ${user}")
sql """drop catalog if exists ${catalogName}"""
} finally {
}
}
}