mrhhsg commented on code in PR #67343:
URL: https://github.com/apache/doris/pull/67343#discussion_r3900843564


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DictGet.java:
##########
@@ -93,6 +96,16 @@ public Pair<FunctionSignature, Dictionary> 
customSignatureDict() {
         String dictName = firstNames[1];
         String colName = ((Literal) getArgument(1)).getStringValue();
 
+        // Reading dictionary values must be authorized like reading its 
source data. Check before
+        // the lookup so a caller without the privilege cannot even probe 
whether the dictionary
+        // exists. ConnectContext may be absent on internal paths, which carry 
no user to check.
+        ConnectContext connectContext = ConnectContext.get();
+        if (connectContext != null && 
!Env.getCurrentEnv().getAccessManager().checkTblPriv(connectContext,

Review Comment:
   Fixed in c0c06d6: statements that bind `dict_get` / `dict_get_many` are now 
marked `cannotProcessExpression` in `ExpressionAnalyzer` (next to the UDF 
exclusion), so they never enter the FE or BE sql cache — `supportSqlCache()` 
gates both `tryAddFeSqlCache` and `tryAddBeCache`. That covers the BE-folded 
literal-only case as well as the default-settings case with a table scan, and 
also removes the pre-existing staleness after `REFRESH DICTIONARY` (a refresh 
changes no table version, so a cached result was never invalidated). Regression 
`test_dictionary_read_auth_cache` primes the sql 
(`cache_last_version_interval_second=0`, `enable_sql_cache=true`), checks 
`explain physical plan` shows no `PhysicalSqlCache`, revokes `SELECT` on the 
dictionary and expects `SELECT command denied` on the identical statement.
   



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/refresh/RefreshDictionaryCommand.java:
##########
@@ -45,7 +55,26 @@ public RefreshDictionaryCommand(String dbName, String 
dicName) {
     public void run(ConnectContext ctx, StmtExecutor executor) throws 
Exception {
         DictionaryManager dictionaryManager = 
ctx.getEnv().getDictionaryManager();
         String db = dbName == null ? ctx.getDatabase() : dbName;
+        // The reload is an INSERT INTO the dictionary executed as the current 
user, which requires
+        // LOAD on the dictionary (and SELECT on the source table). Check LOAD 
up front so a user
+        // without it can neither probe whether the dictionary exists nor flip 
it to LOADING.
+        if (!Env.getCurrentEnv().getAccessManager().checkTblPriv(ctx, 
InternalCatalog.INTERNAL_CATALOG_NAME,
+                db, dictionaryName, PrivPredicate.LOAD)) {
+            
ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, 
"LOAD",
+                    ctx.getQualifiedUser(), ctx.getRemoteIP(), db + ": " + 
dictionaryName);
+        }
         Dictionary dictionary = dictionaryManager.getDictionary(db, 
dictionaryName);
+        // The reload also reads the source table as the current user, and its 
INSERT authorizes
+        // exactly the dictionary's source columns through the column-aware 
contract (BindSink
+        // projects the source to the dictionary schema before CheckPrivileges 
runs). Use the same
+        // contract here so column-level grants keep working, and reject a 
missing privilege before
+        // dataLoad() publishes the LOADING status and blocks concurrent 
refreshes.
+        Set<String> sourceColumns = dictionary.getDicColumns().stream()
+                .map(DictionaryColumnDefinition::getName)

Review Comment:
   Fixed in c0c06d6: the preflight now derives the column set from 
`DictionaryColumnDefinition.getOriginColumn().getName()` — the source column's 
own spelling that `BindSink` / `CheckPrivileges` compare — instead of the DDL 
spelling. `originColumn` has been persisted since the feature's first commit, 
so no migration concern. Regression adds a dictionary declared as `(ID KEY, 
USERNAME VALUE)` over a source table with `id` / `username`, grants 
`SELECT_PRIV(id)` / `SELECT_PRIV(username)` plus `LOAD`, and refreshes 
successfully.
   



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DictGetMany.java:
##########
@@ -99,6 +102,16 @@ public Pair<FunctionSignature, Dictionary> 
customSignatureDict() {
         String dictName = firstNames[1];
         List<Literal> colNames = ((ArrayLiteral) getArgument(1)).getValue();
 
+        // Reading dictionary values must be authorized like reading its 
source data. Check before
+        // the lookup so a caller without the privilege cannot even probe 
whether the dictionary
+        // exists. ConnectContext may be absent on internal paths, which carry 
no user to check.
+        ConnectContext connectContext = ConnectContext.get();
+        if (connectContext != null && 
!Env.getCurrentEnv().getAccessManager().checkTblPriv(connectContext,

Review Comment:
   Fixed in c0c06d6: `LogicalResultSinkToShortCircuitPointQuery` no longer sets 
the short-circuit flag when the projection contains `dict_get` / 
`dict_get_many`, so such point queries take the normal path and every EXECUTE 
of a server prepared statement is analyzed — and authorized — again; nothing 
about the dictionary ID/version is retained in a `ShortCircuitQueryContext`. 
Regression `test_dictionary_read_auth_cache` prepares `SELECT dict_get(...) 
FROM <MoW row-store point table> WHERE id = ?` over `useServerPrepStmts=true`, 
executes once, revokes `SELECT` on the dictionary from an admin connection and 
expects the second EXECUTE of the same statement to be denied.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to