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 3de3a756f74 [fix](binlog) Require SELECT privilege for binlog TVF 
(#68088)
3de3a756f74 is described below

commit 3de3a756f74bd4b1950516311d315d503f3bd947
Author: morrySnow <[email protected]>
AuthorDate: Sun Sep 20 11:11:48 2026 +0800

    [fix](binlog) Require SELECT privilege for binlog TVF (#68088)
    
    ### What problem does this PR solve?
    
    Issue Number: N/A
    
    Related PR: N/A
    
    Problem Summary:
    
    The `binlog()` table-valued function did not implement the standard TVF
    authorization hook. As a result, the Nereids privilege-check phase could
    not enforce table privileges for the target OLAP table, and a user
    denied direct `SELECT` could still read its row-binlog contents through
    the TVF.
    
    This change overrides `TableBinlogFunction.checkAuth(ConnectContext)`
    and requires `SELECT` on the target table. The TVF is therefore
    authorized through the standard `CheckPrivileges` flow, independently of
    constructor and metadata initialization.
---
 .../doris/tablefunction/TableBinlogFunction.java   | 13 ++++
 .../tablefunction/TableBinlogFunctionAuthTest.java | 77 +++++++++++++++++++++
 .../suites/auth_p0/test_binlog_tvf_auth.groovy     | 80 ++++++++++++++++++++++
 3 files changed, 170 insertions(+)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/tablefunction/TableBinlogFunction.java
 
b/fe/fe-core/src/main/java/org/apache/doris/tablefunction/TableBinlogFunction.java
index dc84f391424..aa3cd5eb1a0 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/tablefunction/TableBinlogFunction.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/tablefunction/TableBinlogFunction.java
@@ -29,9 +29,12 @@ import org.apache.doris.catalog.TableIf;
 import org.apache.doris.catalog.TableIf.TableType;
 import org.apache.doris.catalog.info.PartitionNamesInfo;
 import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.ErrorCode;
 import org.apache.doris.common.MetaNotFoundException;
 import org.apache.doris.common.util.Util;
+import org.apache.doris.datasource.InternalCatalog;
 import org.apache.doris.mtmv.ivm.IvmUtil;
+import org.apache.doris.mysql.privilege.PrivPredicate;
 import org.apache.doris.planner.OlapScanNode;
 import org.apache.doris.planner.PlanNodeId;
 import org.apache.doris.planner.ScanContext;
@@ -126,6 +129,16 @@ public class TableBinlogFunction extends 
TableValuedFunctionIf {
         }
     }
 
+    @Override
+    public void checkAuth(ConnectContext ctx) {
+        if (!Env.getCurrentEnv().getAccessManager().checkTblPriv(ctx, 
InternalCatalog.INTERNAL_CATALOG_NAME,
+                dbName, tableName, PrivPredicate.SELECT)) {
+            String message = 
ErrorCode.ERR_TABLE_ACCESS_DENIED_ERROR.formatErrorMsg(
+                    PrivPredicate.SELECT.getPrivs().toString(), tableName);
+            throw new 
org.apache.doris.nereids.exceptions.AnalysisException(message);
+        }
+    }
+
     @Override
     public String getTableName() {
         return "BinlogTableFunction";
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/tablefunction/TableBinlogFunctionAuthTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/tablefunction/TableBinlogFunctionAuthTest.java
new file mode 100644
index 00000000000..bf536717af8
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/tablefunction/TableBinlogFunctionAuthTest.java
@@ -0,0 +1,77 @@
+// 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.tablefunction;
+
+import org.apache.doris.catalog.Database;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.RowBinlogTableWrapper;
+import org.apache.doris.catalog.TableIf.TableType;
+import org.apache.doris.datasource.InternalCatalog;
+import org.apache.doris.mysql.privilege.AccessControllerManager;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.qe.ConnectContext;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+public class TableBinlogFunctionAuthTest {
+
+    @Test
+    public void testCheckAuthRequiresSelectPrivilege() throws Exception {
+        Env env = Mockito.mock(Env.class);
+        ConnectContext context = Mockito.mock(ConnectContext.class);
+        AccessControllerManager accessManager = 
Mockito.mock(AccessControllerManager.class);
+        InternalCatalog catalog = Mockito.mock(InternalCatalog.class);
+        Database database = Mockito.mock(Database.class);
+        OlapTable table = Mockito.mock(OlapTable.class);
+
+        Mockito.when(env.getAccessManager()).thenReturn(accessManager);
+        Mockito.when(env.getInternalCatalog()).thenReturn(catalog);
+        
Mockito.when(catalog.getDbOrMetaException("test_db")).thenReturn(database);
+        Mockito.when(database.getTableOrMetaException("test_table", 
TableType.OLAP)).thenReturn(table);
+        Mockito.when(table.needRowBinlog()).thenReturn(true);
+        Mockito.when(accessManager.checkTblPriv(context, 
InternalCatalog.INTERNAL_CATALOG_NAME,
+                "test_db", "test_table", 
PrivPredicate.SELECT)).thenReturn(false);
+
+        try (MockedStatic<Env> mockedEnv = Mockito.mockStatic(Env.class);
+                MockedConstruction<RowBinlogTableWrapper> ignored =
+                        Mockito.mockConstruction(RowBinlogTableWrapper.class)) 
{
+            mockedEnv.when(Env::getCurrentEnv).thenReturn(env);
+
+            TableBinlogFunction function = new 
TableBinlogFunction(ImmutableMap.of(
+                    "db", "test_db", "table", "test_table"));
+            Mockito.verifyNoInteractions(accessManager);
+
+            AnalysisException exception = 
Assertions.assertThrows(AnalysisException.class,
+                    () -> function.checkAuth(context));
+            Assertions.assertTrue(exception.getMessage().contains("Access 
denied"));
+            Mockito.verify(accessManager).checkTblPriv(context, 
InternalCatalog.INTERNAL_CATALOG_NAME,
+                    "test_db", "test_table", PrivPredicate.SELECT);
+
+            Mockito.when(accessManager.checkTblPriv(context, 
InternalCatalog.INTERNAL_CATALOG_NAME,
+                    "test_db", "test_table", 
PrivPredicate.SELECT)).thenReturn(true);
+            Assertions.assertDoesNotThrow(() -> function.checkAuth(context));
+        }
+    }
+}
diff --git a/regression-test/suites/auth_p0/test_binlog_tvf_auth.groovy 
b/regression-test/suites/auth_p0/test_binlog_tvf_auth.groovy
new file mode 100644
index 00000000000..c1e3dee38f4
--- /dev/null
+++ b/regression-test/suites/auth_p0/test_binlog_tvf_auth.groovy
@@ -0,0 +1,80 @@
+// 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_binlog_tvf_auth", "p0,auth") {
+    String user = "test_binlog_tvf_auth_user"
+    String password = "C123_567p"
+
+    sql "DROP USER IF EXISTS '${user}'"
+    sql "DROP DATABASE IF EXISTS test_binlog_tvf_auth_db"
+    sql "CREATE DATABASE test_binlog_tvf_auth_db"
+    sql """
+        CREATE TABLE test_binlog_tvf_auth_db.test_binlog_tvf_auth_table (
+            k BIGINT,
+            v INT
+        ) ENGINE=OLAP
+        DUPLICATE KEY(k)
+        DISTRIBUTED BY HASH(k) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "binlog.enable" = "true",
+            "binlog.format" = "ROW"
+        )
+    """
+    sql "INSERT INTO test_binlog_tvf_auth_db.test_binlog_tvf_auth_table VALUES 
(1, 100)"
+    sql "SYNC"
+    sql "CREATE USER '${user}' IDENTIFIED BY '${password}'"
+
+    if (isCloudMode()) {
+        def clusters = sql "SHOW CLUSTERS"
+        assertFalse(clusters.isEmpty())
+        sql "GRANT USAGE_PRIV ON CLUSTER `${clusters[0][0]}` TO ${user}"
+    }
+    // The configured JDBC URL connects to regression_test before executing 
the query.
+    // Grant access to that default database while keeping the target table 
unauthorized.
+    sql "GRANT SELECT_PRIV ON regression_test TO ${user}"
+
+    connect(user, password, context.config.jdbcUrl) {
+        test {
+            sql "SELECT * FROM 
test_binlog_tvf_auth_db.test_binlog_tvf_auth_table"
+            exception "denied"
+        }
+        test {
+            sql """
+                SELECT k, v
+                FROM binlog(
+                    "db" = "test_binlog_tvf_auth_db",
+                    "table" = "test_binlog_tvf_auth_table"
+                )
+                LIMIT 1
+            """
+            exception "denied"
+        }
+    }
+
+    sql "GRANT SELECT_PRIV ON 
test_binlog_tvf_auth_db.test_binlog_tvf_auth_table TO ${user}"
+    connect(user, password, context.config.jdbcUrl) {
+        sql """
+            SELECT k, v
+            FROM binlog(
+                "db" = "test_binlog_tvf_auth_db",
+                "table" = "test_binlog_tvf_auth_table"
+            )
+            LIMIT 1
+        """
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to