Skip to content

Conversation

@hantangwangd
Copy link
Member

@hantangwangd hantangwangd commented Dec 14, 2025

Description

This PR implements the following functionality:

  1. Add basic access control check for calling procedures. Procedures are now access control objects at the same level as tables, requiring EXECUTE privilege for invocation.

  2. Make access control in Iceberg configurable and introduces file-based access control into Iceberg. The default remains allow-all.

  3. Add target table's INSERT/DELETE access control checks for TABLE_DATA_REWRITE distributed procedure.

  4. Add row filters and column masks access control checks for TABLE_DATA_REWRITE distributed procedure.

To be implemented: Enforce access control for CALL procedures in RangerBasedAccessControl and SqlStandardAccessControl as well. (current behavior: calls are always allowed, but the necessary permissions on target tables will be checked.)

Motivation and Context

See issue: #26680

Impact

  • Access control in Iceberg is now configuration. The currently supported types are allow-all and file.
  • Support access control for procedures, including the access control for the target tables involved in distributed procedures

Test Plan

  • Newly added test cases to cover the access control for procedures and distributed procedures in Iceberg

Contributor checklist

  • Please make sure your submission complies with our contributing guide, in particular code style and commit standards.
  • PR description addresses the issue accurately and concisely. If the change is non-trivial, a GitHub Issue is referenced.
  • Documented new properties (with its default value), SQL syntax, functions, or other functionality.
  • If release notes are required, they follow the release notes guidelines.
  • Adequate tests were added if applicable.
  • CI passed.
  • If adding new dependencies, verified they have an OpenSSF Scorecard score of 5.0 or higher (or obtained explicit TSC approval for lower scores).

Release Notes

== NO RELEASE NOTE ==

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 14, 2025

Reviewer's Guide

Introduces EXECUTE-level access control for procedures (including Iceberg distributed procedures), wires Iceberg to a configurable security system (allow-all or file-based), and enforces additional checks such as table INSERT/DELETE permissions and row filter/column mask constraints while updating SPI/System/connector access control surfaces and tests.

Sequence diagram for procedure CALL access control

sequenceDiagram
    actor User
    participant Client
    participant Coordinator
    participant StatementAnalyzer
    participant CallTask
    participant AccessControlManager as AccessControl
    participant SystemAC as SystemAccessControl
    participant ConnectorAC as ConnectorAccessControl

    User->>Client: submit CALL schema.proc(...)
    Client->>Coordinator: send query
    Coordinator->>StatementAnalyzer: analyze Call
    StatementAnalyzer->>AccessControlManager: checkCanCallProcedure(txnId, identity, ctx, procedureName)
    AccessControlManager->>SystemAC: checkCanCallProcedure(identity, ctx, catalogSchemaTableName)
    SystemAC-->>AccessControlManager: allow or deny (system-level)
    alt catalog has ConnectorAccessControl
        AccessControlManager->>ConnectorAC: checkCanCallProcedure(connectorTxn, connectorIdentity, ctx, schemaTableName)
        ConnectorAC-->>AccessControlManager: allow or deny (connector-level)
    end
    AccessControlManager-->>StatementAnalyzer: result
    StatementAnalyzer-->>Coordinator: analysis (distributed or regular)
    note over StatementAnalyzer,AccessControlManager: For TABLE_DATA_REWRITE also register INSERT and DELETE checks

    Coordinator->>CallTask: execute Call
    CallTask->>AccessControlManager: checkCanCallProcedure(txnId, identity, ctx, procedureName)
    AccessControlManager-->>CallTask: result
    CallTask-->>Coordinator: invoke connector procedure
    Coordinator-->>Client: return result
Loading

Class diagram for new procedure access control structures

classDiagram
    class AccessControl {
        <<interface>>
        +checkCanSelectFromColumns(transactionId, identity, context, tableName, columnOrSubfieldNames)
        +checkCanCallProcedure(transactionId, identity, context, procedureName)
    }

    class SystemAccessControl {
        <<interface>>
        +checkCanSelectFromColumns(identity, context, table, columns)
        +checkCanCallProcedure(identity, context, procedure)
    }

    class ConnectorAccessControl {
        <<interface>>
        +checkCanSelectFromColumns(transactionHandle, identity, context, tableName, columnOrSubfieldNames)
        +checkCanCallProcedure(transactionHandle, identity, context, procedureName)
    }

    class AccessDeniedException {
        +denySelectColumns(tableName, columnNames)
        +denyCallProcedure(procedureName)
        +denyCallProcedure(procedureName, extraInfo)
    }

    class AccessControlManager {
        +checkCanCallProcedure(transactionId, identity, context, procedureName)
    }

    class StatsRecordingSystemAccessControl {
        -delegate
        -stats
        +checkCanCallProcedure(identity, context, procedure)
    }

    class StatsRecordingSystemAccessControl_Stats {
        +checkCanCallProcedure:SystemAccessControlStats
        +getCheckCanCallProcedure()
    }

    class FileBasedAccessControl {
        -schemaRules
        -tableRules
        -sessionPropertyRules
        -procedureRules
        +checkCanCallProcedure(transactionHandle, identity, context, procedureName)
        -checkProcedurePermission(identity, procedureName, requiredPrivileges)
        -isDatabaseOwner(identity, schemaName)
    }

    class ProcedureAccessControlRule {
        -privileges:Set~ProcedurePrivilege~
        -userRegex:Optional~Pattern~
        -schemaRegex:Optional~Pattern~
        -procedureRegex:Optional~Pattern~
        +ProcedureAccessControlRule(privileges, userRegex, schemaRegex, procedureRegex)
        +match(user, procedure):Optional~Set~ProcedurePrivilege~~
    }

    class ProcedurePrivilege {
        <<enumeration>>
        EXECUTE
    }

    class AccessControlRules {
        -schemaRules:List~SchemaAccessControlRule~
        -tableRules:List~TableAccessControlRule~
        -sessionPropertyRules:List~SessionPropertyAccessControlRule~
        -procedureRules:List~ProcedureAccessControlRule~
        +getSchemaRules()
        +getTableRules()
        +getSessionPropertyRules()
        +getProcedureRules()
    }

    class FileBasedSystemAccessControl {
        +checkCanCallProcedure(identity, context, procedure)
    }

    class AllowAllSystemAccessControl {
        +checkCanCallProcedure(identity, context, procedure)
    }

    class ReadOnlySystemAccessControl {
        +checkCanCallProcedure(identity, context, procedure)
    }

    class DenyQueryIntegrityCheckSystemAccessControl {
        +checkCanCallProcedure(identity, context, procedure)
    }

    class AllowAllAccessControl_plugin {
        <<connector access control>>
        +checkCanCallProcedure(transactionHandle, identity, context, procedureName)
    }

    class ReadOnlyAccessControl_plugin {
        <<connector access control>>
        +checkCanCallProcedure(transactionHandle, identity, context, procedureName)
    }

    class DenyAllAccessControl_spi {
        +checkCanCallProcedure(transactionId, identity, context, procedureName)
    }

    class AllowAllAccessControl_spi {
        +checkCanCallProcedure(transactionId, identity, context, procedureName)
    }

    class ViewAccessControl {
        +checkCanCallProcedure(transactionId, identity, context, procedureName)
    }

    class SystemTableAwareAccessControl {
        -delegate:ConnectorAccessControl
        +checkCanCallProcedure(transactionHandle, identity, context, procedureName)
    }

    AccessControl <|.. AccessControlManager
    SystemAccessControl <|.. FileBasedSystemAccessControl
    SystemAccessControl <|.. AllowAllSystemAccessControl
    SystemAccessControl <|.. ReadOnlySystemAccessControl
    SystemAccessControl <|.. DenyQueryIntegrityCheckSystemAccessControl
    SystemAccessControl <|.. StatsRecordingSystemAccessControl

    ConnectorAccessControl <|.. FileBasedAccessControl
    ConnectorAccessControl <|.. AllowAllAccessControl_plugin
    ConnectorAccessControl <|.. ReadOnlyAccessControl_plugin
    ConnectorAccessControl <|.. SystemTableAwareAccessControl

    AccessControlRules o-- ProcedureAccessControlRule
    FileBasedAccessControl o-- ProcedureAccessControlRule
    ProcedureAccessControlRule ..> ProcedurePrivilege

    StatsRecordingSystemAccessControl *-- StatsRecordingSystemAccessControl_Stats

    AccessControlManager ..> SystemAccessControl
    AccessControlManager ..> ConnectorAccessControl
    AccessControlManager ..> AccessDeniedException
Loading

Class diagram for RewriteWriterTarget access checks

classDiagram
    class RewriteWriterTarget {
        -metadata:Metadata
        -accessControl:AccessControl
        +RewriteWriterTarget(metadata, accessControl)
        +optimize(plan, session, types, variableAllocator, idAllocator, warningCollector):PlanOptimizerResult
    }

    class RewriteWriterTarget_Rewriter {
        -session:Session
        -metadata:Metadata
        -accessControl:AccessControl
        -planChanged:boolean
        +Rewriter(session, metadata, accessControl)
        +visitCallDistributedProcedure(node, context):PlanNode
        +isPlanChanged():boolean
        -findTableHandleForCallDistributedProcedure(startNode):Optional~TableHandle~
        -hasFullDataAccessControl(tableHandle):boolean
    }

    class Metadata {
        +getTableMetadata(session, tableHandle):TableMetadata
        +getColumnHandles(session, tableHandle):Map~String,ColumnHandle~
        +getColumnMetadata(session, tableHandle, columnHandle):ColumnMetadata
    }

    class AccessControl {
        +getRowFilters(transactionId, identity, context, table):List~ViewExpression~
        +getColumnMasks(transactionId, identity, context, table, columns):Map~ColumnMetadata,ViewExpression~
    }

    class TableMetadata {
        +getConnectorId():ConnectorId
        +getTable():SchemaTableName
    }

    class Session {
        +getTransactionId():Optional~TransactionId~
        +getRequiredTransactionId():TransactionId
        +getIdentity():Identity
        +getAccessControlContext():AccessControlContext
    }

    class StatementAnalyzer {
        +visitCall(call, scope):Scope
    }

    class AccessControlInfoForTable {
        +AccessControlInfoForTable(accessControl, identity, transactionId, context, tableName)
    }

    class Analysis {
        +addAccessControlCheckForTable(operation, accessControlInfoForTable)
    }

    RewriteWriterTarget *-- RewriteWriterTarget_Rewriter
    RewriteWriterTarget_Rewriter ..> Metadata
    RewriteWriterTarget_Rewriter ..> AccessControl
    RewriteWriterTarget_Rewriter ..> Session
    RewriteWriterTarget_Rewriter ..> TableMetadata

    StatementAnalyzer ..> AccessControl
    StatementAnalyzer ..> Analysis
    Analysis o-- AccessControlInfoForTable

    AccessControlInfoForTable ..> AccessControl
    AccessControlInfoForTable ..> Session
Loading

File-Level Changes

Change Details Files
Enforce EXECUTE privilege checks for procedure calls at system and connector layers, including statistics and metrics wiring.
  • Extend AccessControl, SystemAccessControl, ConnectorAccessControl, and related wrapper/implementation classes to add checkCanCallProcedure methods with a default deny or pass-through behavior as appropriate (AllowAll, DenyAll, ReadOnly, FileBased, Legacy, Ranger, SqlStandard, SystemTableAware, Forwarding, ViewAccessControl, TestingAccessControlManager, etc.).
  • Update AccessDeniedException to support denyCallProcedure helpers and use them in new access-control methods.
  • Wrap system-level procedure access checks in StatsRecordingSystemAccessControl with timing and failure stats, and expose new metrics via nested SystemAccessControlStats accessors.
presto-spi/src/main/java/com/facebook/presto/spi/security/AccessControl.java
presto-spi/src/main/java/com/facebook/presto/spi/security/SystemAccessControl.java
presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorAccessControl.java
presto-spi/src/main/java/com/facebook/presto/spi/security/AccessDeniedException.java
presto-spi/src/main/java/com/facebook/presto/spi/security/AllowAllAccessControl.java
presto-spi/src/main/java/com/facebook/presto/spi/security/DenyAllAccessControl.java
presto-spi/src/main/java/com/facebook/presto/spi/security/ViewAccessControl.java
presto-main-base/src/main/java/com/facebook/presto/security/AccessControlManager.java
presto-main-base/src/main/java/com/facebook/presto/security/StatsRecordingSystemAccessControl.java
presto-main-base/src/main/java/com/facebook/presto/security/FileBasedSystemAccessControl.java
presto-main-base/src/main/java/com/facebook/presto/security/AllowAllSystemAccessControl.java
presto-main-base/src/main/java/com/facebook/presto/security/DenyQueryIntegrityCheckSystemAccessControl.java
presto-main-base/src/main/java/com/facebook/presto/security/ReadOnlySystemAccessControl.java
presto-main-base/src/main/java/com/facebook/presto/testing/TestingAccessControlManager.java
presto-main-base/src/test/java/com/facebook/presto/security/TestAccessControlManager.java
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ForwardingConnectorAccessControl.java
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ForwardingSystemAccessControl.java
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/AllowAllAccessControl.java
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ReadOnlyAccessControl.java
presto-hive/src/main/java/com/facebook/presto/hive/security/SystemTableAwareAccessControl.java
presto-hive/src/main/java/com/facebook/presto/hive/security/LegacyAccessControl.java
presto-hive/src/main/java/com/facebook/presto/hive/security/SqlStandardAccessControl.java
presto-hive/src/main/java/com/facebook/presto/hive/security/ranger/RangerBasedAccessControl.java
Add file-based procedure-level access control rules and tests for Hive and general connector tooling.
  • Extend AccessControlRules and FileBasedAccessControl to support procedureRules and checkCanCallProcedure, leveraging a new ProcedureAccessControlRule model with regex-based matching and EXECUTE privilege enforcement.
  • Gate schema create/drop/rename in FileBasedAccessControl on database ownership via isDatabaseOwner.
  • Add new JSON configuration (procedure.json, updated security.json) and unit tests to validate procedure permissions across users and schemas, including Hive file-based security behavior for procedure calls.
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/AccessControlRules.java
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/FileBasedAccessControl.java
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ProcedureAccessControlRule.java
presto-plugin-toolkit/src/test/java/com/facebook/presto/plugin/base/security/TestFileBasedAccessControl.java
presto-hive/src/test/java/com/facebook/presto/hive/TestHiveFileBasedSecurity.java
presto-hive/src/test/resources/com/facebook/presto/hive/security.json
presto-plugin-toolkit/src/test/resources/procedure.json
Wire procedure EXECUTE checks into statement analysis and execution, and add table-level checks for TABLE_DATA_REWRITE distributed procedures.
  • In StatementAnalyzer.visitCall, after resolving the distributed procedure, call accessControl.checkCanCallProcedure for the resolved QualifiedObjectName.
  • For TABLE_DATA_REWRITE distributed procedures, register table INSERT and DELETE access-control checks for the target table via Analysis.addAccessControlCheckForTable with appropriate AccessControlInfoForTable instances.
  • In CallTask.execute, perform access-control checkCanCallProcedure before invoking connector procedures.
presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java
presto-main-base/src/main/java/com/facebook/presto/execution/CallTask.java
Prevent TABLE_DATA_REWRITE from running when row filters or column masks are active on the target table, and expose assertions helpers to test this.
  • Refactor RewriteWriterTarget into a Metadata+AccessControl-aware optimizer that inspects the target TableHandle for CallDistributedProcedureNode, rejecting plans when row filters or column masks are present by throwing AccessDeniedException with a clear message.
  • Add hasFullDataAccessControl helper to fetch row filters and column masks via AccessControl and Metadata APIs and determine whether full-table access is allowed.
  • Relax QueryAssertions to support session-specific assertFails and public executeExclusively for deterministic access-control mutation tests.
presto-main-base/src/main/java/com/facebook/presto/sql/planner/optimizations/RewriteWriterTarget.java
presto-main-base/src/main/java/com/facebook/presto/sql/planner/PlanOptimizers.java
presto-main-base/src/test/java/com/facebook/presto/sql/query/QueryAssertions.java
Add configurable Iceberg security with file-based access control, ensure procedures respect those rules, and add integration tests.
  • Introduce IcebergSecurityModule and SecurityConfig to configure iceberg.security as either allow-all or file, and register the appropriate access-control module via Guice conditional modules.
  • Update InternalIcebergConnectorFactory to install IcebergSecurityModule, construct a SystemTableAwareAccessControl, and pass it into IcebergConnector instead of the previous AllowAllAccessControl.
  • Add TestIcebergFileBasedSecurity with a custom Iceberg catalog and security.json to validate procedure EXECUTE checks, distributed TABLE_DATA_REWRITE behavior w.r.t INSERT/DELETE privileges, and failures when row filters/column masks are present.
presto-iceberg/src/main/java/com/facebook/presto/iceberg/InternalIcebergConnectorFactory.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/security/IcebergSecurityModule.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/security/SecurityConfig.java
presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergFileBasedSecurity.java
presto-iceberg/src/test/resources/com/facebook/presto/iceberg/security.json
presto-docs/src/main/sphinx/connector/hive-security.rst
presto-docs/src/main/sphinx/connector/iceberg.rst

Assessment against linked issues

Issue Objective Addressed Explanation
#26680 Introduce a procedure-level access control mechanism (EXECUTE privilege) for both normal and distributed procedures, analogous to table-level access control, and plumb it through the system and connector access control APIs.
#26680 For distributed TABLE_DATA_REWRITE procedures, enforce access control on the target tables so that executing the procedure requires the necessary INSERT and DELETE permissions (and disallow execution when full table data access is restricted by row filters or column masks).

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@hantangwangd hantangwangd force-pushed the access_control_for_procedures branch 2 times, most recently from 06b6b85 to 04be7a6 Compare December 18, 2025 07:35
@hantangwangd hantangwangd linked an issue Dec 18, 2025 that may be closed by this pull request
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 7 issues, and left some high level feedback:

  • In StatsRecordingSystemAccessControl.checkCanCallProcedure, the failure and duration are recorded on checkCanAddColumn instead of the new checkCanCallProcedure stat, so the metrics for procedure calls will be misreported; update both the recordFailure() and record(...) calls to use stats.checkCanCallProcedure.
  • In ProcedureAccessControlRule's constructor, the schemaRegex null-check uses the message "sourceRegex is null", which looks like a copy‑paste error and should be corrected to reference schemaRegex for clearer diagnostics.
  • In TestIcebergFileBasedSecurity.testCallDistributedProceduresWithInsertDeletePermission, the expected error messages hardcode schema.test_rewrite_table instead of using the schema variable, which makes the test brittle if the schema changes; consider constructing the fully qualified table name from schema and tableName in the assertions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `StatsRecordingSystemAccessControl.checkCanCallProcedure`, the failure and duration are recorded on `checkCanAddColumn` instead of the new `checkCanCallProcedure` stat, so the metrics for procedure calls will be misreported; update both the `recordFailure()` and `record(...)` calls to use `stats.checkCanCallProcedure`.
- In `ProcedureAccessControlRule`'s constructor, the `schemaRegex` null-check uses the message "sourceRegex is null", which looks like a copy‑paste error and should be corrected to reference `schemaRegex` for clearer diagnostics.
- In `TestIcebergFileBasedSecurity.testCallDistributedProceduresWithInsertDeletePermission`, the expected error messages hardcode `schema.test_rewrite_table` instead of using the `schema` variable, which makes the test brittle if the schema changes; consider constructing the fully qualified table name from `schema` and `tableName` in the assertions.

## Individual Comments

### Comment 1
<location> `presto-main-base/src/main/java/com/facebook/presto/sql/planner/optimizations/RewriteWriterTarget.java:187-196` </location>
<code_context>
+        private boolean hasFullDataAccessControl(TableHandle tableHandle)
</code_context>

<issue_to_address>
**issue (bug_risk):** Use a required transaction ID instead of `session.getTransactionId().get()` to avoid Optional misuse

In `hasFullDataAccessControl`, `getRowFilters` and `getColumnMasks` currently call `session.getTransactionId().get()`. If no transaction ID is present (e.g. some autocommit/non-transactional flows), this will throw `NoSuchElementException` instead of an access-control error. Please use `session.getRequiredTransactionId()` here (optionally passing it into the rewriter) to match the rest of the access-control code and avoid this failure mode.
</issue_to_address>

### Comment 2
<location> `presto-main-base/src/main/java/com/facebook/presto/security/StatsRecordingSystemAccessControl.java:498-507` </location>
<code_context>
     }

+    @Override
+    public void checkCanCallProcedure(Identity identity, AccessControlContext context, CatalogSchemaTableName procedure)
+    {
+    }
</code_context>

<issue_to_address>
**issue (bug_risk):** checkCanCallProcedure is recording stats against the wrong counter (checkCanAddColumn)

In `checkCanCallProcedure`, the catch and finally blocks still use `stats.checkCanAddColumn` instead of the new `stats.checkCanCallProcedure`. This will misclassify procedure calls as "add column" operations and leave the procedure-specific metric unused. Please update those calls to `stats.checkCanCallProcedure.recordFailure()` and `stats.checkCanCallProcedure.record(duration)` to keep metrics accurate.
</issue_to_address>

### Comment 3
<location> `presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ProcedureAccessControlRule.java:43` </location>
<code_context>
+    public ProcedureAccessControlRule(
+            @JsonProperty("privileges") Set<ProcedurePrivilege> privileges,
+            @JsonProperty("user") Optional<Pattern> userRegex,
+            @JsonProperty("schema") Optional<Pattern> schemaRegex,
+            @JsonProperty("procedure") Optional<Pattern> procedureRegex)
+    {
+        this.privileges = ImmutableSet.copyOf(requireNonNull(privileges, "privileges is null"));
+        this.userRegex = requireNonNull(userRegex, "userRegex is null");
+        this.schemaRegex = requireNonNull(schemaRegex, "sourceRegex is null");
+        this.procedureRegex = requireNonNull(procedureRegex, "procedureRegex is null");
+    }
</code_context>

<issue_to_address>
**nitpick (typo):** Constructor error message refers to `sourceRegex` instead of `schemaRegex`

`this.schemaRegex` is initialized with `requireNonNull(schemaRegex, "sourceRegex is null")`. For consistency and clarity, the message should reference `schemaRegex` instead.

```suggestion
        this.schemaRegex = requireNonNull(schemaRegex, "schemaRegex is null");
```
</issue_to_address>

### Comment 4
<location> `presto-hive/src/test/java/com/facebook/presto/hive/TestHiveFileBasedSecurity.java:89` </location>
<code_context>
+        Session joe = Session.builder(getSession("joe"))
+                .setConnectionProperty(new ConnectorId("hive"), USE_LIST_DIRECTORY_CACHE, "true")
+                .build();
+        assertDenied(() -> queryRunner.execute(joe, "call hive.system.invalidate_directory_list_cache()"));
+    }
+
</code_context>

<issue_to_address>
**suggestion (testing):** Strengthen the negative assertion to check for access-denied semantics rather than any RuntimeException.

`assertDenied` currently passes on any `RuntimeException`, so this test would also succeed for unrelated failures (e.g., syntax errors or catalog misconfiguration). To better validate access control, tighten the expectation by asserting the specific access-denied exception type and/or checking that the exception message clearly indicates an access-denied condition for this procedure. That way the test only passes when the failure is truly due to access control.

Suggested implementation:

```java
        Session bob = Session.builder(getSession("bob"))
                .setConnectionProperty(new ConnectorId("hive"), USE_LIST_DIRECTORY_CACHE, "true")
                .build();
        queryRunner.execute(bob, "call hive.system.invalidate_directory_list_cache()");

        Session joe = Session.builder(getSession("joe"))
                .setConnectionProperty(new ConnectorId("hive"), USE_LIST_DIRECTORY_CACHE, "true")
                .build();

        try {
            queryRunner.execute(joe, "call hive.system.invalidate_directory_list_cache()");
            fail("Expected access to be denied for joe when calling invalidate_directory_list_cache");
        }
        catch (AccessDeniedException e) {
            assertTrue(
                    e.getMessage() != null && e.getMessage().contains("invalidate_directory_list_cache"),
                    "AccessDeniedException message should indicate invalidate_directory_list_cache was denied, but was: " + e.getMessage());
        }

```

```java
import com.facebook.presto.Session;
import com.facebook.presto.spi.ConnectorId;
import com.facebook.presto.spi.security.AccessDeniedException;
import com.facebook.presto.spi.security.Identity;
import com.facebook.presto.testing.QueryRunner;
import com.google.common.collect.ImmutableList;
import java.util.Optional;

```

```java
import static com.facebook.presto.hive.HiveQueryRunner.createQueryRunner;
import static com.facebook.presto.hive.HiveSessionProperties.USE_LIST_DIRECTORY_CACHE;
import static com.facebook.presto.testing.TestingSession.testSessionBuilder;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

```
</issue_to_address>

### Comment 5
<location> `presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergFileBasedSecurity.java:124-125` </location>
<code_context>
+            assertUpdate(icebergAdmin, format("call system.rewrite_data_files('%s', '%s')", schema, tableName), 2);
+            // `alice` and `bob` have the permission to execute `iceberg.system.rewrite_data_files`,
+            // but they lack the necessary permission to perform INSERT or DELETE on the target table
+            assertDenied(() -> assertUpdate(alice, format("call system.rewrite_data_files('%s', '%s')", schema, tableName)),
+                    "Access Denied: Cannot delete from table schema.test_rewrite_table");
+            assertDenied(() -> assertUpdate(bob, format("call system.rewrite_data_files('%s', '%s')", schema, tableName)),
+                    "Access Denied: Cannot insert into table schema.test_rewrite_table");
</code_context>

<issue_to_address>
**issue (testing):** Make the expected error messages resilient to catalog/schema naming and align them with the actual object name formatting.

These assertions hard-code `schema.test_rewrite_table`, while the actual error messages come from `QualifiedObjectName` (often including the catalog, e.g., `iceberg.<schema>.test_rewrite_table`) and use `getSession().getSchema()` instead of the literal `schema`.

To avoid brittle tests, either build the expected message with `format` using the actual schema (and catalog, if present), or match only the stable portion via regex, e.g.:
- `"Access Denied: Cannot delete from table .*\\." + tableName`
- `"Access Denied: Cannot insert into table .*\\." + tableName`

This will keep the tests stable if catalog/schema naming or formatting changes.
</issue_to_address>

### Comment 6
<location> `presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergFileBasedSecurity.java:168-174` </location>
<code_context>
+            // perform INSERT/DELETE operations on the target table involved in the procedure
+            assertUpdate(icebergAdmin, format("call system.rewrite_data_files('%s', '%s')", schema, tableName), 2);
+
+            assertions.executeExclusively(() -> {
+                accessControl.reset();
+                accessControl.rowFilter(
+                        new QualifiedObjectName("iceberg", schema, tableName),
+                        "iceberg",
+                        new ViewExpression("iceberg", Optional.empty(), Optional.empty(), "a < 2"));
+                assertions.assertQuery(icebergAdmin, "SELECT count(*) FROM " + tableName, "VALUES BIGINT '1'");
+                assertions.assertFails(icebergAdmin, format("call system.rewrite_data_files('%s', '%s')", schema, tableName),
+                        "Access Denied: Full data access is restricted by row filters and column masks");
+            });
</code_context>

<issue_to_address>
**suggestion (testing):** Consider also asserting normal read/masked behavior in the column-mask scenario to fully validate the access-control interaction.

In the row-filter case you both confirm the filter is enforced (via a `SELECT` count) and that `rewrite_data_files` is denied. In the column-mask case you only check the denial. Please also assert that a normal query still works and applies the mask (for example, selecting column `b` and verifying it reads as `'noop'`), so the test fails if masks are accidentally disabled instead of the rewrite being blocked.

```suggestion
            assertions.executeExclusively(() -> {
                accessControl.reset();
                accessControl.columnMask(
                        new QualifiedObjectName("iceberg", schema, tableName),
                        "b",
                        "iceberg",
                        new ViewExpression("iceberg", Optional.empty(), Optional.empty(), "'noop'"));
                // verify that normal reads still work and the column mask is applied
                assertions.assertQuery(
                        icebergAdmin,
                        "SELECT b FROM " + tableName + " ORDER BY a",
                        "VALUES 'noop', 'noop'");
                assertions.assertFails(
                        icebergAdmin,
                        format("call system.rewrite_data_files('%s', '%s')", schema, tableName),
                        "Access Denied: Full data access is restricted by row filters and column masks");
            });
```
</issue_to_address>

### Comment 7
<location> `presto-docs/src/main/sphinx/connector/iceberg.rst:2408-2409` </location>
<code_context>
+================================================== ============================================================
+Property Value                                     Description
+================================================== ============================================================
+``allow-all`` (default value)                      None authorization checks are enforced, thus allowing all
+                                                   operations.
+
+``file``                                           Authorization checks are enforced using a config file specified
</code_context>

<issue_to_address>
**issue (typo):** Fix grammar in the `allow-all` description (`None authorization``No authorization`).

Also consider rewriting the full sentence to: `No authorization checks are enforced, thus allowing all operations.`

```suggestion
``allow-all`` (default value)                      No authorization checks are enforced, thus allowing all
                                                   operations.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@hantangwangd
Copy link
Member Author

@sourcery-ai resolve

@tdcmeehan tdcmeehan self-assigned this Dec 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add access control for Procedure architecture

2 participants