This is an automated email from the ASF dual-hosted git repository.

csringhofer pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git


The following commit(s) were added to refs/heads/master by this push:
     new 09bed366f IMPALA-14507: Register column-level privilege requests for 
INSERT
09bed366f is described below

commit 09bed366fe7e681108f164ee53433b791dada90a
Author: Fang-Yu Rao <[email protected]>
AuthorDate: Tue Oct 21 17:24:10 2025 -0700

    IMPALA-14507: Register column-level privilege requests for INSERT
    
    This patch registers column-level privilege requests for columns
    involved in the INSERT statement so that the requesting user does not
    need to be granted the INSERT privilege on the entire table. This would
    be helpful in the case when different users are allowed to insert data
    into different sets of columns in the same table. Moreover, column-level
    Ranger audit events for INSERT requests will be produced after this
    patch.
    
    This would also allow an administrator to add deny policies on columns
    against a user if we would like to prevent the user from inserting data
    into the specified columns.
    
    On the other hand, this patch slightly revises the Preconditions checks
    in BaseAuthorizationChecker#authorize() so that it is easier to
    understand what those checks verify. The code comment there explicitly
    mentions that for a statement that may produce several hierarchical
    privilege requests, it should always have a corresponding table-level
    privilege request if it has a column-level privilege request. This was
    not entirely true and could not be detected by the previous checks.
    Specifically, for the CREATE TABLE <db>.<target_tbl> AS SELECT
    statement, we would register an ANY privilege request for the column
    with wildcard table and column names denoting any column and table in
    the database the target table belongs, i.e., '<db>.*.*', whereas there
    was no privilege request for the table '<db>.*' registered. This patch
    corrects this by registering an ANY privilege request for the database
    of the target table instead. Due to this, we also changed the expected
    error messages for some Java and end-to-end authorization-related tests.
    This should not affect the security in that the resulting
    RangerResourceImpl sent to the Ranger plug-in is the same whether the
    privilege request is an ANY privilege request for the wildcard column
    '<db>.*.*' or an ANY privilege request for the database '<db>'. Refer to
    RangerAuthorizationChecker#authorizeResource() for more details.
    
    Testing:
     - Added frontend and end-to-end tests to verify that
       a) we register column-level privilege requests in the INSERT
          statement in addition to the table-level one,
       b) a user is not allowed to insert data into a column of a table
          if there is a deny policy defined on the column against the user,
          even though the user was already granted the INSERT privilege on
          the table,
       c) a user is not allowed to insert data into a column if there is a
          column masking policy defined on any column of the same table,
          even though the user was already granted the INSERT privilege on
          the table (RANGER-1087 and IMPALA-10554),
       d) a user is allowed to insert data into a set of columns of a table
          as long as the user was granted the INSERT privileges on those
          columns given that there is no deny policy on those columns and
          no column masking policy on any column of the same table,
       e) column-level Ranger audit events could be produced after the
          introduction of column-level INSERT privilege,
       f) we are able to grant, revoke column-level INSERT privileges via
          the catalog server, and show column-level INSERT privileges via a
          coordinator.
    
    Change-Id: I2ef61801d3b394c56702b193c250492a62b111df
    Reviewed-on: http://gerrit.cloudera.org:8080/23569
    Reviewed-by: Quanlong Huang <[email protected]>
    Tested-by: Impala Public Jenkins <[email protected]>
    Reviewed-by: Csaba Ringhofer <[email protected]>
---
 .../java/org/apache/impala/analysis/Analyzer.java  |   2 +-
 .../org/apache/impala/analysis/InsertStmt.java     |  10 +
 .../org/apache/impala/analysis/PrivilegeSpec.java  |   7 +-
 .../impala/authorization/AuthorizableColumn.java   |  29 +-
 .../impala/authorization/AuthorizableTable.java    |   2 +-
 .../authorization/BaseAuthorizationChecker.java    |  80 ++++--
 .../ranger/RangerAuthorizationChecker.java         |  78 ++++--
 .../ranger/RangerAuthorizationContext.java         |   8 +-
 .../impala/analysis/AnalyzeAuthStmtsTest.java      |   9 +-
 .../authorization/AuthorizationStmtTest.java       |  80 +++++-
 .../authorization/AuthorizationTestBase.java       |  40 +++
 .../authorization/ranger/RangerAuditLogTest.java   | 191 ++++++++++++-
 .../queries/QueryTest/grant_revoke.test            |   4 +-
 tests/authorization/test_ranger.py                 | 301 +++++++++++++++++++--
 14 files changed, 720 insertions(+), 121 deletions(-)

diff --git a/fe/src/main/java/org/apache/impala/analysis/Analyzer.java 
b/fe/src/main/java/org/apache/impala/analysis/Analyzer.java
index 270857332..4109ea174 100644
--- a/fe/src/main/java/org/apache/impala/analysis/Analyzer.java
+++ b/fe/src/main/java/org/apache/impala/analysis/Analyzer.java
@@ -4092,7 +4092,7 @@ public class Analyzer {
       }
       if (privilege == Privilege.ANY) {
         String dbOwner = db == null ? null : db.getOwnerUser();
-        return builder.any().onAnyColumn(dbName, dbOwner).build();
+        return builder.any().onDb(dbName, dbOwner).build();
       } else if (db == null) {
         // Db does not exist, register a privilege request based on the DB 
name.
         return builder.allOf(privilege).onDb(dbName, null).build();
diff --git a/fe/src/main/java/org/apache/impala/analysis/InsertStmt.java 
b/fe/src/main/java/org/apache/impala/analysis/InsertStmt.java
index b1d5381bc..ab565fce9 100644
--- a/fe/src/main/java/org/apache/impala/analysis/InsertStmt.java
+++ b/fe/src/main/java/org/apache/impala/analysis/InsertStmt.java
@@ -371,6 +371,11 @@ public class InsertStmt extends DmlStatementBase {
             "Duplicate column '" + columnName + "' in column permutation");
       }
       selectExprTargetColumns.add(column);
+
+      analyzer.registerPrivReq(builder -> builder
+          .allOf(Privilege.INSERT)
+          .onColumn(table_.getTableName().getDb(), 
table_.getTableName().getTbl(),
+              column.getName(), table_.getOwnerUser()).build());
     }
 
     int numStaticPartitionExprs = 0;
@@ -396,6 +401,11 @@ public class InsertStmt extends DmlStatementBase {
         } else {
           selectExprTargetColumns.add(column);
         }
+
+        analyzer.registerPrivReq(builder -> builder
+            .allOf(Privilege.INSERT)
+            .onColumn(table_.getTableName().getDb(), 
table_.getTableName().getTbl(),
+                column.getName(), table_.getOwnerUser()).build());
       }
     }
 
diff --git a/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java 
b/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java
index aab0948c5..aba9d2744 100644
--- a/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java
+++ b/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java
@@ -270,7 +270,7 @@ public class PrivilegeSpec extends StmtNode {
    * 1. No columns are specified.
    * 2. Privilege is applied on a view or an external data source.
    * 3. Referenced table and/or columns do not exist.
-   * 4. Privilege level is not SELECT.
+   * 4. Privilege level is neither SELECT nor INSERT.
    */
   private void analyzeColumnPrivScope(Analyzer analyzer) throws 
AnalysisException {
     Preconditions.checkState(scope_ == TPrivilegeScope.COLUMN);
@@ -278,8 +278,9 @@ public class PrivilegeSpec extends StmtNode {
     if (columnNames_.isEmpty()) {
       throw new AnalysisException("Empty column list in column privilege 
spec.");
     }
-    if (privilegeLevel_ != TPrivilegeLevel.SELECT) {
-      throw new AnalysisException("Only 'SELECT' privileges are allowed " +
+    if (privilegeLevel_ != (TPrivilegeLevel.SELECT) &&
+        privilegeLevel_ != (TPrivilegeLevel.INSERT)) {
+      throw new AnalysisException("Only 'SELECT' and 'INSERT' privileges are 
allowed " +
           "in a column privilege spec.");
     }
     FeTable table = analyzeTargetTable(analyzer);
diff --git 
a/fe/src/main/java/org/apache/impala/authorization/AuthorizableColumn.java 
b/fe/src/main/java/org/apache/impala/authorization/AuthorizableColumn.java
index e0a2bdc79..6fc299a0d 100644
--- a/fe/src/main/java/org/apache/impala/authorization/AuthorizableColumn.java
+++ b/fe/src/main/java/org/apache/impala/authorization/AuthorizableColumn.java
@@ -25,43 +25,24 @@ import com.google.common.base.Strings;
 /**
  * A class to authorize access to a column.
  */
-public class AuthorizableColumn extends Authorizable {
-  private final String dbName_;
-  private final String tableName_;
+public class AuthorizableColumn extends AuthorizableTable {
   private final String columnName_;
-  // Owner for the parent db or table that this Authorizable corresponds to.
-  @Nullable
-  private final String ownerUser_;
 
   public AuthorizableColumn(
       String dbName, String tableName, String columnName, @Nullable String 
ownerUser) {
-    Preconditions.checkArgument(!Strings.isNullOrEmpty(dbName));
-    Preconditions.checkArgument(!Strings.isNullOrEmpty(tableName));
+    super(dbName, tableName, ownerUser);
     Preconditions.checkArgument(!Strings.isNullOrEmpty(columnName));
-    dbName_ = dbName;
-    tableName_ = tableName;
     columnName_ = columnName;
-    ownerUser_ = ownerUser;
   }
 
   @Override
-  public String getName() { return dbName_ + "." + tableName_ + "." + 
columnName_; }
+  public String getName() {
+    return getDbName() + "." + getTableName() + "." + columnName_;
+  }
 
   @Override
   public Type getType() { return Type.COLUMN; }
 
-  @Override
-  public String getFullTableName() { return dbName_ + "." + tableName_; }
-
-  @Override
-  public String getDbName() { return dbName_; }
-
-  @Override
-  public String getTableName() { return tableName_; }
-
   @Override
   public String getColumnName() { return columnName_; }
-
-  @Override
-  public String getOwnerUser() { return ownerUser_; }
 }
diff --git 
a/fe/src/main/java/org/apache/impala/authorization/AuthorizableTable.java 
b/fe/src/main/java/org/apache/impala/authorization/AuthorizableTable.java
index 3cfc8ad86..66544e044 100644
--- a/fe/src/main/java/org/apache/impala/authorization/AuthorizableTable.java
+++ b/fe/src/main/java/org/apache/impala/authorization/AuthorizableTable.java
@@ -52,7 +52,7 @@ public class AuthorizableTable extends Authorizable {
   public String getTableName() { return tableName_; }
 
   @Override
-  public String getFullTableName() { return getName(); }
+  public String getFullTableName() { return getDbName() + "." + 
getTableName(); }
 
   @Override
   public String getOwnerUser() { return ownerUser_; }
diff --git 
a/fe/src/main/java/org/apache/impala/authorization/BaseAuthorizationChecker.java
 
b/fe/src/main/java/org/apache/impala/authorization/BaseAuthorizationChecker.java
index b2b3980cc..9e255e370 100644
--- 
a/fe/src/main/java/org/apache/impala/authorization/BaseAuthorizationChecker.java
+++ 
b/fe/src/main/java/org/apache/impala/authorization/BaseAuthorizationChecker.java
@@ -19,6 +19,7 @@ package org.apache.impala.authorization;
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
 import org.apache.impala.analysis.AnalysisContext.AnalysisResult;
 import org.apache.impala.analysis.Analyzer;
 import org.apache.impala.authorization.Authorizable.Type;
@@ -48,6 +49,10 @@ public abstract class BaseAuthorizationChecker implements 
AuthorizationChecker {
   private final static Logger LOG = LoggerFactory.getLogger(
       BaseAuthorizationChecker.class);
 
+  private final static Set<Privilege> ALLOWED_HIER_AUTHZ_TABLE_PRIVILEGES =
+      ImmutableSet.of(Privilege.SELECT, Privilege.INSERT, Privilege.CREATE,
+          Privilege.ALL, Privilege.ALTER, Privilege.VIEW_METADATA);
+
   protected final AuthorizationConfig config_;
 
   /**
@@ -130,32 +135,51 @@ public abstract class BaseAuthorizationChecker implements 
AuthorizationChecker {
     // has column-level privilege request. The hierarchical nature requires 
special
     // logic to process correctly and efficiently.
     if (analysisResult.isHierarchicalAuthStmt()) {
-      // Map of table name to a list of privilege requests associated with 
that table.
-      // These include both table-level and column-level privilege requests. 
We use a
-      // LinkedHashMap to preserve the order in which requests are inserted.
+      // Map of table name to a list of table-level privilege requests 
associated with
+      // that table.
       Map<String, List<PrivilegeRequest>> tablePrivReqs = new 
LinkedHashMap<>();
+      // Map of table name to a list of column-level privilege requests 
associated with
+      // that table.
+      Map<String, List<PrivilegeRequest>> columnPrivReqs = new 
LinkedHashMap<>();
       // Privilege requests that are not column or table-level.
       List<PrivilegeRequest> otherPrivReqs = new ArrayList<>();
-      // Group the registered privilege requests based on the table they 
reference.
+      // Group the registered privilege requests based on the type of the 
Authorizable
+      // and the table they reference.
       for (PrivilegeRequest privReq : analyzer.getPrivilegeReqs()) {
         String tableName = privReq.getAuthorizable().getFullTableName();
         if (tableName == null) {
           otherPrivReqs.add(privReq);
         } else {
-          List<PrivilegeRequest> requests = tablePrivReqs.get(tableName);
-          if (requests == null) {
-            requests = new ArrayList<>();
-            tablePrivReqs.put(tableName, requests);
+          List<PrivilegeRequest> requests;
+          if (privReq.getAuthorizable().getType() == Authorizable.Type.TABLE) {
+            // We allow the ALL privilege because for the UPSERT operation 
against Kudu
+            // tables, we set the required privilege to ALL since we don't 
have an UPSERT
+            // privilege yet. Refer to InsertStmt#analyzeTargetTable().
+            // CREATE privilege would be registered in 
CreateTableAsSelectStmt#analyze().
+            // In addition, VIEW_METADATA privilege would be registered in
+            // CopyTestCaseStmt#analyze().
+            Preconditions.checkState(ALLOWED_HIER_AUTHZ_TABLE_PRIVILEGES
+                .contains(privReq.getPrivilege()));
+            requests = tablePrivReqs.computeIfAbsent(tableName, k -> new 
ArrayList<>());
+            requests.add(privReq);
+          } else {
+            Preconditions.checkState(privReq.getAuthorizable().getType() == 
Type.COLUMN);
+            requests = columnPrivReqs.computeIfAbsent(tableName, k -> new 
ArrayList<>());
+            requests.add(privReq);
           }
-          // The table-level SELECT must be the first table-level request, and 
it
-          // must precede all column-level privilege requests.
-          Preconditions.checkState((requests.isEmpty() ||
-              !(privReq.getAuthorizable().getType() == 
Authorizable.Type.COLUMN)) ||
-              (requests.get(0).getAuthorizable().getType() == 
Authorizable.Type.TABLE &&
-                  requests.get(0).getPrivilege() == Privilege.SELECT));
-          requests.add(privReq);
         }
       }
+      // The following makes sure each column-level privilege request has at 
least one
+      // corresponding table-level privilege request.
+      // For each list of column-level privilege requests associated with a 
table, add
+      // the requests to the respective list of table-level privilege requests 
so that
+      // entries in 'tablePrivReqs' could be used to authorize table accesses 
in
+      // authorizeTableAccess() below.
+      for (Map.Entry<String, List<PrivilegeRequest>> entry : 
columnPrivReqs.entrySet()) {
+        List<PrivilegeRequest> privReqs = tablePrivReqs.get(entry.getKey());
+        Preconditions.checkState(privReqs != null);
+        privReqs.addAll(entry.getValue());
+      }
 
       // Check any non-table, non-column privilege requests first.
       for (PrivilegeRequest request : otherPrivReqs) {
@@ -238,29 +262,36 @@ public abstract class BaseAuthorizationChecker implements 
AuthorizationChecker {
     // not want Impala to show any information when these features are 
disabled.
     authorizeRowFilterAndColumnMask(analyzer.getUser(), requests);
 
-    boolean hasTableSelectPriv = true;
-    boolean hasColumnSelectPriv = false;
+    boolean hasTableAccessPriv = true;
+    boolean hasColumnAccessPriv = false;
     for (PrivilegeRequest request: requests) {
       if (request.getAuthorizable().getType() == Authorizable.Type.TABLE) {
         try {
           authorizePrivilegeRequest(authzCtx, analysisResult, catalog, 
request);
         } catch (AuthorizationException e) {
           // Authorization fails if we fail to authorize any table-level 
request that is
-          // not a SELECT privilege (e.g. INSERT).
-          if (request.getPrivilege() != Privilege.SELECT) throw e;
-          hasTableSelectPriv = false;
+          // neither the SELECT nor INSERT privileges.
+          // For UPSERT against a Kudu table, since we set the required 
privilege to ALL,
+          // we would throw an AuthorizationException if the requesting user 
does not
+          // have the ALL privilege on the Kudu table.
+          // For CTAS, the requesting user is required to have the CREATE 
privilege on
+          // the target table, so we would throw an AuthorizationException too 
if the
+          // requesting user does not have the required privilege.
+          if (request.getPrivilege() != Privilege.SELECT &&
+              request.getPrivilege() != Privilege.INSERT) throw e;
+          hasTableAccessPriv = false;
         }
       } else {
         Preconditions.checkState(
             request.getAuthorizable().getType() == Authorizable.Type.COLUMN);
         // In order to support deny policies on columns
-        if (hasTableSelectPriv &&
+        if (hasTableAccessPriv &&
                 request.getPrivilege() != Privilege.SELECT &&
                 request.getPrivilege() != Privilege.INSERT) {
           continue;
         }
         if (hasAccess(authzCtx, analyzer.getUser(), request)) {
-          hasColumnSelectPriv = true;
+          hasColumnAccessPriv = true;
           continue;
         }
         // Make sure we don't reveal any column names in the error message.
@@ -270,9 +301,10 @@ public abstract class BaseAuthorizationChecker implements 
AuthorizationChecker {
             request.getAuthorizable().getFullTableName()));
       }
     }
-    if (!hasTableSelectPriv && !hasColumnSelectPriv) {
+    if (!hasTableAccessPriv && !hasColumnAccessPriv) {
       throw new AuthorizationException(String.format("User '%s' does not have 
" +
-              "privileges to execute 'SELECT' on: %s", 
analyzer.getUser().getName(),
+              "privileges to execute '%s' on: %s", 
analyzer.getUser().getName(),
+          requests.get(0).getPrivilege().toString(),
           requests.get(0).getAuthorizable().getFullTableName()));
     }
   }
diff --git 
a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationChecker.java
 
b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationChecker.java
index 207303003..7c02dc483 100644
--- 
a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationChecker.java
+++ 
b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationChecker.java
@@ -25,7 +25,6 @@ import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.impala.analysis.AnalysisContext.AnalysisResult;
 import org.apache.impala.authorization.Authorizable;
 import org.apache.impala.authorization.Authorizable.Type;
-import org.apache.impala.authorization.AuthorizableTable;
 import org.apache.impala.authorization.AuthorizationChecker;
 import org.apache.impala.authorization.AuthorizationConfig;
 import org.apache.impala.authorization.AuthorizationContext;
@@ -46,6 +45,7 @@ import org.apache.ranger.plugin.model.RangerPolicy;
 import org.apache.ranger.plugin.model.RangerServiceDef;
 import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
 import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl;
+import org.apache.ranger.plugin.policyengine.RangerAccessResource;
 import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl;
 import org.apache.ranger.plugin.policyengine.RangerAccessResult;
 import org.apache.ranger.plugin.policyengine.RangerPolicyEngine;
@@ -284,17 +284,24 @@ public class RangerAuthorizationChecker extends 
BaseAuthorizationChecker {
       throws AuthorizationException, InternalException {
     RangerAuthorizationContext originalCtx = (RangerAuthorizationContext) 
authzCtx;
     RangerBufferAuditHandler originalAuditHandler = 
originalCtx.getAuditHandler();
-    // case 1: table (select) OK, columns (select) OK --> add the table event,
-    //                                                    add the column events
-    // case 2: table (non-select) ERROR --> add the table event
-    // case 3: table (select) ERROR, columns (select) OK -> only add the 
column events
-    // case 4: table (select) ERROR, columns (select) ERROR --> add the table 
event
-    // case 5: table (select) ERROR --> add the table event
+    // For access_type in {select, insert}, the following holds.
+    // case 1: table (access_type) OK, columns (access_type) OK --> add the 
table event,
+    //         add the column events
+    // case 2: table (non-select and non-insert) ERROR --> add the table event
+    //         This could happen in upsert against a Kudu table if the 
requesting user
+    //         does not have the all privilege on the target table.
+    // case 3: table (access_type) ERROR, columns (access_type) OK -> only add 
the column
+    //         events
+    // case 4: table (access_type) ERROR, columns (access_type) ERROR --> add 
the table
+    //         event
+    // case 5: table (access_type) ERROR --> add the table event
     //         This could happen when the select request for a non-existing 
table fails
     //         the authorization.
-    // case 6: table (select) OK, columns (select) ERROR --> add the first 
column event
-    //         This could happen when the requesting user is granted the 
select privilege
-    //         on the table but is denied access to a column in the same table 
in Ranger.
+    // case 6: table (access_type) OK, columns (access_type) ERROR --> add the 
first
+    //         column event
+    //         This could happen when the requesting user is granted the 
select or insert
+    //         privileges on the table but is denied access to a column in the 
same table
+    //         in Ranger.
     RangerAuthorizationContext tmpCtx = new RangerAuthorizationContext(
         originalCtx.getSessionState(), originalCtx.getTimeline());
     tmpCtx.setAuditHandler(new RangerBufferAuditHandler(originalAuditHandler));
@@ -305,8 +312,10 @@ public class RangerAuthorizationChecker extends 
BaseAuthorizationChecker {
       authorizationException = e;
       tmpCtx.getAuditHandler().getAuthzEvents().stream()
           .filter(evt ->
-              // case 2: get the first failing non-select table
-              (!"select".equalsIgnoreCase(evt.getAccessType()) &&
+              // case 2: get the first failing table event that is neither 
select nor
+              // insert.
+              ((!"select".equalsIgnoreCase(evt.getAccessType()) &&
+                  !"insert".equalsIgnoreCase(evt.getAccessType())) &&
                   "@table".equals(evt.getResourceType())) ||
               // case 4 & 5 & 6: get the table or a column event
               (("@table".equals(evt.getResourceType()) ||
@@ -719,21 +728,39 @@ public class RangerAuthorizationChecker extends 
BaseAuthorizationChecker {
     Preconditions.checkState(accessResult.getIsAllowed(),
         "update should be allowed before checking this");
     String originalAccessType = request.getAccessType();
+    RangerAccessResource originalResource = null;
     // Row-filtering/Column-masking policies are defined only for SELECT 
requests.
     request.setAccessType(SELECT_ACCESS_TYPE);
+    // This if block allows us to correctly deny the INSERT request when there 
is any row
+    // filtering policy defined on the table against the requesting user.
+    // Recall that a user is allowed to execute the INSERT statement as long 
as the user
+    // is allowed to insert values into each target column, even though the 
user is not
+    // allowed to insert values into the entire table.
+    // Therefore, when determining whether the requesting user is allowed to 
insert
+    // values into a target column, we need to temporarily set the resource to 
the table
+    // to which the column belongs. Without us doing so,
+    // rowFilterResult.isRowFilterEnabled() will return false and thus the 
requesting
+    // user will not be blocked.
+    if (authorizable.getType() == Type.COLUMN) {
+      originalResource = request.getResource();
+      RangerAccessResourceImpl derivedTableResource = new 
RangerImpalaResourceBuilder()
+          .database(authorizable.getDbName())
+          .table(authorizable.getTableName())
+          .owner(authorizable.getOwnerUser())
+          .build();
+      request.setResource(derivedTableResource);
+    }
     // Check if row filtering is enabled for the table/view.
-    if (authorizable.getType() == Type.TABLE) {
-      RangerAccessResult rowFilterResult = plugin_.evalRowFilterPolicies(
-          request, /*resultProcessor*/null);
-      if (rowFilterResult != null && rowFilterResult.isRowFilterEnabled()) {
-        LOG.info("Deny {} on {} due to row filtering policy {}",
-            privilege, authorizable.getName(), rowFilterResult.getPolicyId());
-        accessResult.setIsAllowed(false);
-        accessResult.setPolicyId(rowFilterResult.getPolicyId());
-        accessResult.setReason("User does not have access to all rows of the 
table");
-      } else {
-        LOG.trace("No row filtering policy found on {}.", 
authorizable.getName());
-      }
+    RangerAccessResult rowFilterResult = plugin_.evalRowFilterPolicies(
+        request, /*resultProcessor*/null);
+    if (rowFilterResult != null && rowFilterResult.isRowFilterEnabled()) {
+      LOG.info("Deny {} on {} due to row filtering policy {}",
+          privilege, authorizable.getName(), rowFilterResult.getPolicyId());
+      accessResult.setIsAllowed(false);
+      accessResult.setPolicyId(rowFilterResult.getPolicyId());
+      accessResult.setReason("User does not have access to all rows of the 
table");
+    } else {
+      LOG.trace("No row filtering policy found on {}.", 
authorizable.getName());
     }
     // Check if masking is enabled for any column in the table/view.
     if (accessResult.getIsAllowed()) {
@@ -751,6 +778,9 @@ public class RangerAuthorizationChecker extends 
BaseAuthorizationChecker {
     // Set back the original access type. The request object is still 
referenced by the
     // access result.
     request.setAccessType(originalAccessType);
+    if (originalResource != null) {
+      request.setResource(originalResource);
+    }
     // Only add deny audits.
     if (!accessResult.getIsAllowed() && auditHandler != null) {
       auditHandler.processResult(accessResult);
diff --git 
a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationContext.java
 
b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationContext.java
index 5c918f976..3ac694064 100644
--- 
a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationContext.java
+++ 
b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationContext.java
@@ -113,10 +113,10 @@ public class RangerAuthorizationContext extends 
AuthorizationContext {
         String databaseName = parsedNames[0];
         String tableName = parsedNames[1];
         String columnName = parsedNames[2];
-        // Currently the access type can only be "select" for a non-column 
masking audit
-        // log entry. We include the access type as part of the key to avoid 
accidentally
-        // combining audit log entries corresponding to the same column but of 
different
-        // access types in the future.
+        // Currently the access type can only be "select" or "insert" for a 
non-column
+        // masking audit log entry. We include the access type as part of the 
key to
+        // avoid accidentally combining audit log entries corresponding to the 
same
+        // column but of different access types in the future.
         String accessType = event.getAccessType();
         String key = policyId + "/" + databaseName + "/" + tableName + "/" + 
accessType;
         if (!consolidatedEvents.containsKey(key)) {
diff --git 
a/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java 
b/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java
index 727c510ae..79c48af92 100644
--- a/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java
@@ -259,12 +259,11 @@ public class AnalyzeAuthStmtsTest extends 
FrontendTestBase {
         AnalysisError(String.format("%s SELECT () ON TABLE functional.alltypes 
" +
             "%s %s", formatArgs), "Empty column list in column privilege 
spec.");
         // INSERT/ALL privileges on columns
-        AnalysisError(String.format("%s INSERT (id, tinyint_col) ON TABLE " +
-            "functional.alltypes %s %s", formatArgs), "Only 'SELECT' 
privileges " +
-            "are allowed in a column privilege spec.");
+        AnalyzesOk(String.format("%s INSERT (id, tinyint_col) ON TABLE " +
+            "functional.alltypes %s %s", formatArgs));
         AnalysisError(String.format("%s ALL (id, tinyint_col) ON TABLE " +
-            "functional.alltypes %s %s", formatArgs), "Only 'SELECT' 
privileges " +
-            "are allowed in a column privilege spec.");
+            "functional.alltypes %s %s", formatArgs), "Only 'SELECT' and 
'INSERT' " +
+            "privileges are allowed in a column privilege spec.");
         // Columns/table that don't exist
         AnalysisError(String.format("%s SELECT (invalid_col) ON TABLE " +
             "functional.alltypes %s %s", formatArgs), "Error setting/showing " 
+
diff --git 
a/fe/src/test/java/org/apache/impala/authorization/AuthorizationStmtTest.java 
b/fe/src/test/java/org/apache/impala/authorization/AuthorizationStmtTest.java
index edc71d82c..70dc27ef7 100644
--- 
a/fe/src/test/java/org/apache/impala/authorization/AuthorizationStmtTest.java
+++ 
b/fe/src/test/java/org/apache/impala/authorization/AuthorizationStmtTest.java
@@ -166,7 +166,11 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
     verifyPrivilegeReqs("select a.id from functional.alltypes a", 
expectedAuthorizables);
 
     // Insert.
-    expectedAuthorizables = Sets.newHashSet("functional.alltypes");
+    expectedAuthorizables = Sets.newHashSet(
+        "functional.alltypes",
+        "functional.alltypes.id",
+        "functional.alltypes.month",
+        "functional.alltypes.year");
     verifyPrivilegeReqs("insert into functional.alltypes(id) partition(month, 
year) " +
         "values(1, 1, 2018)", expectedAuthorizables);
     verifyPrivilegeReqs(createAnalysisCtx("functional"),
@@ -174,7 +178,9 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
         expectedAuthorizables);
 
     // Insert with constant select.
-    expectedAuthorizables = Sets.newHashSet("functional.zipcode_incomes");
+    expectedAuthorizables = Sets.newHashSet(
+        "functional.zipcode_incomes",
+        "functional.zipcode_incomes.id");
     verifyPrivilegeReqs("insert into functional.zipcode_incomes(id) select 
'123'",
         expectedAuthorizables);
     verifyPrivilegeReqs(createAnalysisCtx("functional"),
@@ -210,7 +216,7 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
         expectedAuthorizables);
 
     // Show tables.
-    expectedAuthorizables = Sets.newHashSet("functional.*.*");
+    expectedAuthorizables = Sets.newHashSet("functional");
     verifyPrivilegeReqs("show tables in functional", expectedAuthorizables);
     verifyPrivilegeReqs(createAnalysisCtx("functional"), "show tables",
         expectedAuthorizables);
@@ -304,7 +310,10 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
         "update alltypes set int_col = 1", expectedAuthorizables);
 
     // Upsert table.
-    expectedAuthorizables = Sets.newHashSet("functional_kudu.alltypes");
+    expectedAuthorizables = Sets.newHashSet(
+        "functional_kudu.alltypes",
+        "functional_kudu.alltypes.id",
+        "functional_kudu.alltypes.int_col");
     verifyPrivilegeReqs("upsert into table functional_kudu.alltypes(id, 
int_col) " +
         "values(1, 1)", expectedAuthorizables);
     verifyPrivilegeReqs(createAnalysisCtx("functional_kudu"),
@@ -920,6 +929,9 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
           .ok(onTable("functional", "zipcode_incomes", TPrivilegeLevel.ALL))
           .ok(onTable("functional", "zipcode_incomes", TPrivilegeLevel.OWNER))
           .ok(onTable("functional", "zipcode_incomes", TPrivilegeLevel.INSERT))
+          .ok(onColumn("functional", "zipcode_incomes", "id", 
TPrivilegeLevel.ALL))
+          .ok(onColumn("functional", "zipcode_incomes", "id", 
TPrivilegeLevel.OWNER))
+          .ok(onColumn("functional", "zipcode_incomes", "id", 
TPrivilegeLevel.INSERT))
           .error(insertError("functional.zipcode_incomes"))
           .error(insertError("functional.zipcode_incomes"), onServer(allExcept(
               TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER, 
TPrivilegeLevel.INSERT)))
@@ -928,7 +940,10 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
               TPrivilegeLevel.INSERT)))
           .error(insertError("functional.zipcode_incomes"), 
onTable("functional",
               "zipcode_incomes", allExcept(TPrivilegeLevel.ALL, 
TPrivilegeLevel.OWNER,
-              TPrivilegeLevel.INSERT)));
+              TPrivilegeLevel.INSERT)))
+          .error(insertError("functional.zipcode_incomes"), 
onColumn("functional",
+              "zipcode_incomes", "id", allExcept(TPrivilegeLevel.ALL,
+              TPrivilegeLevel.OWNER, TPrivilegeLevel.INSERT)));
     }
 
     for (AuthzTest test: new AuthzTest[]{
@@ -947,13 +962,25 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
           .ok(onDatabase("functional", TPrivilegeLevel.INSERT, 
TPrivilegeLevel.SELECT))
           .ok(onTable("functional", "alltypes", TPrivilegeLevel.ALL),
               onTable("functional", "alltypestiny", TPrivilegeLevel.ALL))
+          .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS, 
TPrivilegeLevel.ALL),
+              onTable("functional", "alltypestiny", TPrivilegeLevel.ALL))
           .ok(onTable("functional", "alltypes", TPrivilegeLevel.OWNER),
               onTable("functional", "alltypestiny", TPrivilegeLevel.OWNER))
+          .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
+                  TPrivilegeLevel.OWNER),
+              onTable("functional", "alltypestiny", TPrivilegeLevel.OWNER))
           .ok(onTable("functional", "alltypes", TPrivilegeLevel.INSERT),
               onTable("functional", "alltypestiny", TPrivilegeLevel.SELECT))
+          .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
+                  TPrivilegeLevel.INSERT),
+              onTable("functional", "alltypestiny", TPrivilegeLevel.SELECT))
           .ok(onTable("functional", "alltypes", TPrivilegeLevel.INSERT),
               onColumn("functional", "alltypestiny", ALLTYPES_COLUMNS,
               TPrivilegeLevel.SELECT))
+          .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
+                  TPrivilegeLevel.INSERT),
+              onColumn("functional", "alltypestiny", ALLTYPES_COLUMNS,
+                  TPrivilegeLevel.SELECT))
           .error(selectError("functional.alltypestiny"))
           .error(selectError("functional.alltypestiny"), onServer(allExcept(
               TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER, 
TPrivilegeLevel.INSERT,
@@ -965,6 +992,10 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
               "alltypestiny", TPrivilegeLevel.SELECT), onTable("functional",
               "alltypes", allExcept(TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER,
               TPrivilegeLevel.INSERT)))
+          .error(insertError("functional.alltypes"), onTable("functional",
+              "alltypestiny", TPrivilegeLevel.SELECT), onColumn("functional",
+              "alltypes", ALLTYPES_COLUMNS, allExcept(TPrivilegeLevel.ALL,
+                  TPrivilegeLevel.OWNER, TPrivilegeLevel.INSERT)))
           .error(selectError("functional.alltypestiny"), onTable("functional",
               "alltypestiny", allExcept(TPrivilegeLevel.ALL, 
TPrivilegeLevel.OWNER,
               TPrivilegeLevel.SELECT)), onTable("functional", "alltypes",
@@ -982,13 +1013,22 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
         .ok(onDatabase("functional", TPrivilegeLevel.INSERT, 
TPrivilegeLevel.SELECT))
         .ok(onTable("functional", "alltypes", TPrivilegeLevel.ALL),
             onTable("functional", "alltypes_view", TPrivilegeLevel.ALL))
+        .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS, 
TPrivilegeLevel.ALL),
+            onTable("functional", "alltypes_view", TPrivilegeLevel.ALL))
         .ok(onTable("functional", "alltypes", TPrivilegeLevel.OWNER),
             onTable("functional", "alltypes_view", TPrivilegeLevel.OWNER))
+        .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS, 
TPrivilegeLevel.OWNER),
+            onTable("functional", "alltypes_view", TPrivilegeLevel.OWNER))
         .ok(onTable("functional", "alltypes", TPrivilegeLevel.INSERT),
             onTable("functional", "alltypes_view", TPrivilegeLevel.SELECT))
+        .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS, 
TPrivilegeLevel.INSERT),
+            onTable("functional", "alltypes_view", TPrivilegeLevel.SELECT))
         .ok(onTable("functional", "alltypes", TPrivilegeLevel.INSERT),
             onColumn("functional", "alltypes_view", ALLTYPES_COLUMNS,
                 TPrivilegeLevel.SELECT))
+        .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS, 
TPrivilegeLevel.INSERT),
+            onColumn("functional", "alltypes_view", ALLTYPES_COLUMNS,
+                TPrivilegeLevel.SELECT))
         .error(selectError("functional.alltypes_view"))
         .error(selectError("functional.alltypes_view"), onServer(allExcept(
             TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER, TPrivilegeLevel.INSERT,
@@ -1000,6 +1040,10 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
             "alltypes_view", TPrivilegeLevel.SELECT), onTable("functional",
             "alltypes", allExcept(TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER,
             TPrivilegeLevel.INSERT)))
+        .error(insertError("functional.alltypes"), onTable("functional",
+            "alltypes_view", TPrivilegeLevel.SELECT), onColumn("functional",
+            "alltypes", ALLTYPES_COLUMNS, allExcept(TPrivilegeLevel.ALL,
+                TPrivilegeLevel.OWNER, TPrivilegeLevel.INSERT)))
         .error(selectError("functional.alltypes_view"), onTable("functional",
             "alltypes_view", allExcept(TPrivilegeLevel.ALL, 
TPrivilegeLevel.OWNER,
             TPrivilegeLevel.SELECT)),
@@ -1018,12 +1062,21 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
         .ok(onTable("functional", "alltypes", TPrivilegeLevel.ALL),
             onTable("functional", "alltypesagg", TPrivilegeLevel.ALL),
             onTable("functional", "alltypestiny", TPrivilegeLevel.ALL))
+        .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS, 
TPrivilegeLevel.ALL),
+            onTable("functional", "alltypesagg", TPrivilegeLevel.ALL),
+            onTable("functional", "alltypestiny", TPrivilegeLevel.ALL))
         .ok(onTable("functional", "alltypes", TPrivilegeLevel.OWNER),
             onTable("functional", "alltypesagg", TPrivilegeLevel.OWNER),
             onTable("functional", "alltypestiny", TPrivilegeLevel.OWNER))
+        .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS, 
TPrivilegeLevel.OWNER),
+            onTable("functional", "alltypesagg", TPrivilegeLevel.OWNER),
+            onTable("functional", "alltypestiny", TPrivilegeLevel.OWNER))
         .ok(onTable("functional", "alltypes", TPrivilegeLevel.INSERT),
             onTable("functional", "alltypesagg", TPrivilegeLevel.SELECT),
             onTable("functional", "alltypestiny", TPrivilegeLevel.SELECT))
+        .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS, 
TPrivilegeLevel.INSERT),
+            onTable("functional", "alltypesagg", TPrivilegeLevel.SELECT),
+            onTable("functional", "alltypestiny", TPrivilegeLevel.SELECT))
         .error(selectError("functional.alltypesagg"))
         .error(selectError("functional.alltypesagg"), onServer(allExcept(
             TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER, TPrivilegeLevel.INSERT,
@@ -1036,6 +1089,11 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
             "alltypestiny", TPrivilegeLevel.SELECT), onTable("functional",
             "alltypes", allExcept(TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER,
             TPrivilegeLevel.INSERT)))
+        .error(insertError("functional.alltypes"), onTable("functional",
+            "alltypesagg", TPrivilegeLevel.SELECT), onTable("functional",
+            "alltypestiny", TPrivilegeLevel.SELECT), onColumn("functional",
+            "alltypes", ALLTYPES_COLUMNS, allExcept(TPrivilegeLevel.ALL,
+                TPrivilegeLevel.OWNER, TPrivilegeLevel.INSERT)))
         .error(selectError("functional.alltypesagg"), onTable("functional",
             "alltypesagg", allExcept(TPrivilegeLevel.ALL, 
TPrivilegeLevel.OWNER,
             TPrivilegeLevel.SELECT)),
@@ -1073,7 +1131,7 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
           .ok(onTable("functional", "alltypes", privilege))
           .ok(onColumn("functional", "alltypes", "id", privilege));
     }
-    test.error(accessError("functional.*.*"));
+    test.error(accessError("functional"));
 
     // Accessing default database should always be allowed.
     authorize("use default").ok();
@@ -1082,7 +1140,7 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
     authorize("use _impala_builtins").ok();
 
     // Use a non-existent database.
-    authorize("use nodb").error(accessError("nodb.*.*"));
+    authorize("use nodb").error(accessError("nodb"));
   }
 
   @Test
@@ -1264,7 +1322,7 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
           .ok(onDatabase("functional", privilege))
           .ok(onTable("functional", "alltypes", privilege));
     }
-    test.error(accessError("functional.*.*"));
+    test.error(accessError("functional"));
 
     // Show metadata tables in table.
     test = authorize("show metadata tables in 
functional_parquet.iceberg_query_metadata");
@@ -1286,7 +1344,7 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
           .ok(onDatabase("functional", privilege))
           .ok(onTable("functional", "alltypes_views", privilege));
     }
-    test.error(accessError("functional.*.*"));
+    test.error(accessError("functional"));
 
     // Show functions.
     test = authorize("show functions in functional");
@@ -1829,7 +1887,7 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
           .ok(onDatabase("functional"), onDatabase("functional", 
TPrivilegeLevel.CREATE,
               TPrivilegeLevel.INSERT), onColumn("functional", "alltypes", 
"int_col",
               TPrivilegeLevel.OWNER))
-          .error(createError("functional"))
+          .error(accessError("functional"))
           .error(createError("functional"), onServer(allExcept(
               TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER, 
TPrivilegeLevel.CREATE,
               TPrivilegeLevel.INSERT, TPrivilegeLevel.SELECT)))
@@ -2019,7 +2077,7 @@ public class AuthorizationStmtTest extends 
AuthorizationTestBase {
       .ok(onServer(TPrivilegeLevel.OWNER))
       .ok(onDatabase("functional", TPrivilegeLevel.CREATE, 
TPrivilegeLevel.INSERT,
           TPrivilegeLevel.SELECT))
-      .error(createError("functional"))
+      .error(accessError("functional"))
       .error(createError("functional"), onServer(allExcept(TPrivilegeLevel.ALL,
           TPrivilegeLevel.OWNER, TPrivilegeLevel.CREATE, 
TPrivilegeLevel.INSERT,
           TPrivilegeLevel.SELECT)))
diff --git 
a/fe/src/test/java/org/apache/impala/authorization/AuthorizationTestBase.java 
b/fe/src/test/java/org/apache/impala/authorization/AuthorizationTestBase.java
index 480391c26..96c3c7940 100644
--- 
a/fe/src/test/java/org/apache/impala/authorization/AuthorizationTestBase.java
+++ 
b/fe/src/test/java/org/apache/impala/authorization/AuthorizationTestBase.java
@@ -654,6 +654,46 @@ public abstract class AuthorizationTestBase extends 
FrontendTestBase {
     LOG.info("Deleted ranger policy {}", policyName);
   }
 
+  protected String createJsonDenyPolicy(String policyName, String databaseName,
+      String tableName, String columnName, String accessType, String user) {
+    String json = String.format("{\n" +
+            "  \"name\": \"%s\",\n" +
+            "  \"policyType\": 0,\n" +
+            "  \"serviceType\": \"%s\",\n" +
+            "  \"service\": \"%s\",\n" +
+            "  \"resources\": {\n" +
+            "    \"database\": {\n" +
+            "      \"values\": [\"%s\"],\n" +
+            "      \"isExcludes\": false,\n" +
+            "      \"isRecursive\": false\n" +
+            "    },\n" +
+            "    \"table\": {\n" +
+            "      \"values\": [\"%s\"],\n" +
+            "      \"isExcludes\": false,\n" +
+            "      \"isRecursive\": false\n" +
+            "    },\n" +
+            "    \"column\": {\n" +
+            "      \"values\": [\"%s\"],\n" +
+            "      \"isExcludes\": false,\n" +
+            "      \"isRecursive\": false\n" +
+            "    }\n" +
+            "  },\n" +
+            "  \"denyPolicyItems\": [\n" +
+            "    {\n" +
+            "      \"accesses\": [\n" +
+            "        {\n" +
+            "          \"type\": \"%s\",\n" +
+            "          \"isAllowed\": true\n" +
+            "        }\n" +
+            "      ],\n" +
+            "      \"users\": [\"%s\"]\n" +
+            "    }\n" +
+            "  ]\n" +
+            "}", policyName, RANGER_SERVICE_TYPE, RANGER_SERVICE_NAME, 
databaseName,
+        tableName, columnName, accessType, user);
+    return json;
+  }
+
   // Convert TDescribeResult to list of strings.
   private static List<String> resultToStringList(TDescribeResult result) {
     List<String> list = new ArrayList<>();
diff --git 
a/fe/src/test/java/org/apache/impala/authorization/ranger/RangerAuditLogTest.java
 
b/fe/src/test/java/org/apache/impala/authorization/ranger/RangerAuditLogTest.java
index 7dd779ef3..0130f944c 100644
--- 
a/fe/src/test/java/org/apache/impala/authorization/ranger/RangerAuditLogTest.java
+++ 
b/fe/src/test/java/org/apache/impala/authorization/ranger/RangerAuditLogTest.java
@@ -112,7 +112,7 @@ public class RangerAuditLogTest extends 
AuthorizationTestBase {
         onUri("hdfs://localhost:20500/test-warehouse/new_table", 
TPrivilegeLevel.ALL));
 
     authzOk(events -> {
-      // Table event and 2 column events
+      // A table event and 1 consolidated column event.
       assertEquals(2, events.size());
       assertEventEquals("@table", "select", "functional/alltypes", 1, 
events.get(0));
       assertEventEquals("@column", 
"select","functional/alltypes/id,string_col", 1,
@@ -122,6 +122,18 @@ public class RangerAuditLogTest extends 
AuthorizationTestBase {
     }, "select id, string_col from functional.alltypes",
         onTable("functional", "alltypes", TPrivilegeLevel.SELECT));
 
+    authzOk(events -> {
+      // A table event and 1 consolidated column event.
+      assertEquals(2, events.size());
+      assertEventEquals("@table", "insert", "functional/alltypes", 1, 
events.get(0));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/id,string_col,year,month", 1, events.get(1));
+      assertEquals("insert into functional.alltypes (id, string_col) partition 
" +
+          "(year, month) values (1, \"abc\", 2026, 12)", 
events.get(0).getRequestData());
+    }, "insert into functional.alltypes (id, string_col) partition " +
+        "(year, month) values (1, \"abc\", 2026, 12)",
+        onTable("functional", "alltypes", TPrivilegeLevel.INSERT));
+
     // Audit log entries are not consolidated since each of them corresponds 
to a
     // different policy.
     authzOk(events -> {
@@ -169,6 +181,76 @@ public class RangerAuditLogTest extends 
AuthorizationTestBase {
         onColumn("functional", "alltypes", "year", TPrivilegeLevel.SELECT),
         onColumn("functional", "alltypes", "month", TPrivilegeLevel.SELECT));
 
+    // Audit log entries are not consolidated since each of them corresponds 
to a
+    // different policy.
+    // In this query, we insert data into each column of the table.
+    authzOk(events -> {
+      assertEquals(13, events.size());
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/id", 1, events.get(0));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/bool_col", 1, events.get(1));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/tinyint_col", 1, events.get(2));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/smallint_col", 1, events.get(3));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/int_col", 1, events.get(4));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/bigint_col", 1, events.get(5));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/float_col", 1, events.get(6));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/double_col", 1, events.get(7));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/date_string_col", 1, events.get(8));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/string_col", 1, events.get(9));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/timestamp_col", 1, events.get(10));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/year", 1, events.get(11));
+      assertEventEquals("@column", "insert",
+          "functional/alltypes/month", 1, events.get(12));
+      assertEquals("insert into functional.alltypes partition (year, month) " +
+          "values (0, true, 0, 0, 0, 0, 0.0, 0.0, '12/31/26', '0'," +
+          "'2026-12-31 00:00:00', 2026, 12)", events.get(0).getRequestData());
+    }, "insert into functional.alltypes partition (year, month) " +
+        "values (0, true, 0, 0, 0, 0, 0.0, 0.0, '12/31/26', '0'," +
+        "'2026-12-31 00:00:00', 2026, 12)",
+        onColumn("functional", "alltypes", "id", TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "bool_col", TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "tinyint_col", 
TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "smallint_col", 
TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "int_col", TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "bigint_col", 
TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "float_col", 
TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "double_col", 
TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "date_string_col", 
TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "string_col", 
TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "timestamp_col", 
TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "year", TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "month", TPrivilegeLevel.INSERT));
+
+    // Audit log entries are consolidated since every column is covered by the 
same
+    // policy.
+    authzOk(events -> {
+          assertEquals(1, events.size());
+          assertEventEquals("@column", "insert",
+              
"functional/alltypes/id,bool_col,tinyint_col,smallint_col,int_col," +
+                  
"bigint_col,float_col,double_col,date_string_col,string_col," +
+                  "timestamp_col,year,month", 1, events.get(0));
+          assertEquals("insert into functional.alltypes partition (year, 
month) " +
+              "values (0, true, 0, 0, 0, 0, 0.0, 0.0, '12/31/26', '0'," +
+              "'2026-12-31 00:00:00', 2026, 12)", 
events.get(0).getRequestData());
+        }, "insert into functional.alltypes partition (year, month) " +
+            "values (0, true, 0, 0, 0, 0, 0.0, 0.0, '12/31/26', '0'," +
+            "'2026-12-31 00:00:00', 2026, 12)",
+        // This creates one single Ranger policy covering all columns of the 
table.
+        onColumn("functional", "alltypes", 
"id,bool_col,tinyint_col,smallint_col," +
+            
"int_col,bigint_col,float_col,double_col,date_string_col,string_col," +
+            "timestamp_col,year,month", TPrivilegeLevel.INSERT));
+
     authzOk(events -> {
       // Only the column events. We don't want to log the failing table event 
used for
       // short circuiting.
@@ -184,6 +266,30 @@ public class RangerAuditLogTest extends 
AuthorizationTestBase {
         onColumn("functional", "alltypes", "id", TPrivilegeLevel.SELECT),
         onColumn("functional", "alltypes", "string_col", 
TPrivilegeLevel.SELECT));
 
+    String insertQuery = "insert into functional.alltypes (id, string_col) 
partition " +
+        "(year, month) values (1, \"abc\", 2026, 12)";
+    authzOk(events -> {
+      // Only the column events. We don't want to log the failing table event 
used for
+      // short circuiting.
+      // In this query, we only insert data into 4 columns of the table.
+      assertEquals(4, events.size());
+      assertEventEquals("@column", "insert", "functional/alltypes/id", 1, 
events.get(0));
+      assertEquals(insertQuery, events.get(0).getRequestData());
+      assertEventEquals("@column", "insert", "functional/alltypes/string_col", 
1,
+          events.get(1));
+      assertEquals(insertQuery, events.get(1).getRequestData());
+      assertEventEquals("@column", "insert", "functional/alltypes/year", 1,
+          events.get(2));
+      assertEquals(insertQuery, events.get(2).getRequestData());
+      assertEventEquals("@column", "insert", "functional/alltypes/month", 1,
+          events.get(3));
+      assertEquals(insertQuery, events.get(3).getRequestData());
+    }, insertQuery,
+        onColumn("functional", "alltypes", "id", TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "string_col", 
TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "year", TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "month", TPrivilegeLevel.INSERT));
+
     authzOk(events -> {
       assertEquals(3, events.size());
       assertEventEquals("@udf", "refresh", "*/*", 1, events.get(0));
@@ -302,6 +408,21 @@ public class RangerAuditLogTest extends 
AuthorizationTestBase {
       assertEventEquals(null, "_ANY", null, 1, events.get(0));
       assertEquals("show databases like 'default*'", 
events.get(0).getRequestData());
     }, "show databases like 'default*'");
+
+    // No audit log entry should be produced for an authorized query against a
+    // non-existing column in an existing table.
+    // Note that for this query, we only register the INSERT privileges on the 
columns
+    // 'id' and 'string_col' because 'non_existing_int_col' does not exist, 
and thus it
+    // results in an exception before we try to register the privilege 
requests for
+    // partitioned columns, i.e., 'year' and 'month'.
+    authzOk(events -> {
+      assertEquals(0, events.size());
+    }, "insert into " +
+            "functional.alltypes (id, string_col, non_existing_int_col) " +
+            "partition (year, month) values (1, \"abc\", 1, 2026, 12)",
+        /* expectAnalysisOk */ false,
+        onColumn("functional", "alltypes", "id", TPrivilegeLevel.INSERT),
+        onColumn("functional", "alltypes", "string_col", 
TPrivilegeLevel.INSERT));
   }
 
   @Test
@@ -348,6 +469,25 @@ public class RangerAuditLogTest extends 
AuthorizationTestBase {
           events.get(0).getRequestData());
     }, "select id, string_col from functional.alltypes");
 
+    authzError(events -> {
+      // Only log the table event.
+      assertEquals(1, events.size());
+      assertEventEquals("@table", "insert", "functional/alltypes", 0, 
events.get(0));
+      assertEquals("insert into functional.alltypes (id, string_col) partition 
" +
+          "(year, month) values (1, \"abc\", 2026, 12)", 
events.get(0).getRequestData());
+    }, "insert into functional.alltypes (id, string_col) partition " +
+        "(year, month) values (1, \"abc\", 2026, 12)");
+
+    authzError(events -> {
+      // Only log the table event.
+      // Note that for UPSERT against a Kudu table, we register the ALL 
privilege on the
+      // target table.
+      assertEquals(1, events.size());
+      assertEventEquals("@table", "all", "functional_kudu/alltypes", 0, 
events.get(0));
+      assertEquals("upsert into table functional_kudu.alltypes (id, int_col) " 
+
+          "values (1, 1)", events.get(0).getRequestData());
+    }, "upsert into table functional_kudu.alltypes (id, int_col) values (1, 
1)");
+
     authzError(events -> {
       // Log the table event even when the table does not exist.
       assertEquals(1, events.size());
@@ -357,6 +497,30 @@ public class RangerAuditLogTest extends 
AuthorizationTestBase {
           events.get(0).getRequestData());
     }, "select * from functional.non_existing_tbl");
 
+    authzError(events -> {
+      // Log the table event even when the table does not exist.
+      assertEquals(1, events.size());
+      assertEventEquals("@table", "insert", "functional/non_existing_tbl", 0,
+          events.get(0));
+      assertEquals("insert into functional.non_existing_tbl (id, string_col) " 
+
+          "partition (year, month) values (1, \"abc\", 2026, 12)",
+          events.get(0).getRequestData());
+    }, "insert into functional.non_existing_tbl (id, string_col) partition " +
+        "(year, month) values (1, \"abc\", 2026, 12)");
+
+    authzError(events -> {
+      // Log the table event even when a column does not exist in an existing 
table.
+      assertEquals(1, events.size());
+      assertEventEquals("@table", "insert", "functional/alltypes", 0,
+          events.get(0));
+      assertEquals("insert into " +
+              "functional.alltypes (id, string_col, non_existing_int_col) " +
+              "partition (year, month) values (1, \"abc\", 1, 2026, 12)",
+          events.get(0).getRequestData());
+    }, "insert into " +
+        "functional.alltypes (id, string_col, non_existing_int_col) " +
+        "partition (year, month) values (1, \"abc\", 1, 2026, 12)");
+
     authzError(events -> {
       // Show the actual view_metadata audit log.
       assertEquals(1, events.size());
@@ -364,6 +528,31 @@ public class RangerAuditLogTest extends 
AuthorizationTestBase {
           events.get(0));
       assertEquals("show partitions functional.alltypes", 
events.get(0).getRequestData());
     }, "show partitions functional.alltypes");
+
+    String databaseName = "functional";
+    String tableName = "alltypes";
+    String columnName = "id";
+    String policyName = "deny_insert_column";
+    String user = user_.getShortName();
+    String accessType = "update";
+    String json = createJsonDenyPolicy(policyName, databaseName, tableName, 
columnName,
+        accessType, user);
+    try {
+      createRangerPolicy(policyName, json);
+      authzError(events -> {
+        // Only log the table event.
+        assertEquals(1, events.size());
+        assertEventEquals("@column", "insert", "functional/alltypes/id", 0,
+            events.get(0));
+        assertEquals("insert into functional.alltypes (id, string_col) 
partition " +
+            "(year, month) values (1, \"abc\", 2026, 12)",
+            events.get(0).getRequestData());
+      }, "insert into functional.alltypes (id, string_col) partition " +
+          "(year, month) values (1, \"abc\", 2026, 12)",
+          onTable("functional", "alltypes", TPrivilegeLevel.INSERT));
+    } finally {
+      deleteRangerPolicy(policyName);
+    }
   }
 
   @Test
diff --git 
a/testdata/workloads/functional-query/queries/QueryTest/grant_revoke.test 
b/testdata/workloads/functional-query/queries/QueryTest/grant_revoke.test
index 747891692..40031c73f 100644
--- a/testdata/workloads/functional-query/queries/QueryTest/grant_revoke.test
+++ b/testdata/workloads/functional-query/queries/QueryTest/grant_revoke.test
@@ -201,7 +201,7 @@ does not have privileges to execute 'CREATE' on: 
grant_rev_db
 ---- QUERY
 show tables in grant_rev_db
 ---- CATCH
-does not have privileges to access: grant_rev_db.*
+does not have privileges to access: grant_rev_db
 ====
 ---- QUERY
 show functions in grant_rev_db
@@ -386,7 +386,7 @@ drop role grant_revoke_test_ALL_TEST_DB;
 # Recall that the owner of the database 'grant_rev_db' has been changed to 
'admin'.
 show tables in grant_rev_db
 ---- CATCH
-does not have privileges to access: grant_rev_db.*
+does not have privileges to access: grant_rev_db
 ====
 ---- QUERY
 # The user 'getuser()' can select the data from the table 
'grant_rev_db.test_tbl1'
diff --git a/tests/authorization/test_ranger.py 
b/tests/authorization/test_ranger.py
index 4c090cf26..458984a7c 100644
--- a/tests/authorization/test_ranger.py
+++ b/tests/authorization/test_ranger.py
@@ -412,7 +412,7 @@ class TestRanger(CustomClusterTestSuite):
           "show grant {0} {1} on user_defined_fn {2}.{3}"
           .format(kw, id, unique_database, udf), [])
 
-      # Grant column privileges and verify
+      # Grant the select privilege on a column and verify
       self._update_privileges_and_verify(
           admin_client, "grant select(x) on table {0}.{1} to {2} {3}"
           .format(unique_database, unique_table, kw, id),
@@ -421,12 +421,28 @@ class TestRanger(CustomClusterTestSuite):
               [kw, id, unique_database, unique_table, "x", "", "", "", "", 
"select",
                "false"]])
 
-      # Revoke column privileges and verify
+      # Revoke the select privilege on a column and verify
       self._update_privileges_and_verify(
           admin_client, "revoke select(x) on table {0}.{1} from {2} {3}"
           .format(unique_database, unique_table, kw, id),
           "show grant {0} {1} on column {2}.{3}.x"
           .format(kw, id, unique_database, unique_table), [])
+
+      # Grant the insert privilege on a column and verify
+      self._update_privileges_and_verify(
+          admin_client, "grant insert(x) on table {0}.{1} to {2} {3}"
+          .format(unique_database, unique_table, kw, id),
+          "show grant {0} {1} on column {2}.{3}.x"
+          .format(kw, id, unique_database, unique_table), [
+              [kw, id, unique_database, unique_table, "x", "", "", "", "", 
"insert",
+               "false"]])
+
+      # Revoke the insert privilege on a column and verify
+      self._update_privileges_and_verify(
+          admin_client, "revoke insert(x) on table {0}.{1} from {2} {3}"
+          .format(unique_database, unique_table, kw, id),
+          "show grant {0} {1} on column {2}.{3}.x"
+          .format(kw, id, unique_database, unique_table), [])
     finally:
       admin_client.execute("revoke all on server from {0} {1}".format(kw, id))
       admin_client.execute("revoke all on uri '{0}' from {1} {2}"
@@ -447,6 +463,8 @@ class TestRanger(CustomClusterTestSuite):
                            .format(unique_database, udf, kw, id))
       admin_client.execute("revoke select(x) on table {0}.{1} from {2} {3}"
                            .format(unique_database, unique_table, kw, id))
+      admin_client.execute("revoke insert(x) on table {0}.{1} from {2} {3}"
+                           .format(unique_database, unique_table, kw, id))
 
   def _test_show_grant_inherited(self, admin_client, kw, id, unique_database,
                                  unique_table):
@@ -461,29 +479,30 @@ class TestRanger(CustomClusterTestSuite):
           [kw, id, "*", "", "", "", "", "", "*", "select", "false"],
           [kw, id, "*", "*", "*", "", "", "", "", "select", "false"]])
 
-      # Verify the highest level of resource that contains the specified 
resource could
-      # be computed when the specified resource is a database
+      # Verify the resources that contain the specified resource could be 
computed when
+      # the specified resource is a database.
       result = self.client.execute("show grant {0} {1} on database {2}"
           .format(kw, id, unique_database))
       TestRanger._check_privileges(result, [
           [kw, id, "*", "", "", "", "", "", "*", "select", "false"],
           [kw, id, "*", "*", "*", "", "", "", "", "select", "false"]])
 
-      # Verify the highest level of resource that contains the specified 
resource could
-      # be computed when the specified resource is a table
+      # Verify the resource that contains the specified resource could be 
computed when
+      # the specified resource is a table.
       result = self.client.execute("show grant {0} {1} on table {2}.{3}"
                                    .format(kw, id, unique_database, 
unique_table))
       TestRanger._check_privileges(result, [
           [kw, id, "*", "*", "*", "", "", "", "", "select", "false"]])
 
-      # Verify the highest level of resource that contains the specified 
resource could
-      # be computed when the specified resource is a column
+      # Verify the resource that contains the specified resource could be 
computed when
+      # the specified resource is a column.
       result = self.client.execute("show grant {0} {1} on column {2}.{3}.x"
                                    .format(kw, id, unique_database, 
unique_table))
       TestRanger._check_privileges(result, [
           [kw, id, "*", "*", "*", "", "", "", "", "select", "false"]])
 
-      # Grant the create privilege on database and verify
+      # Grant the create privilege on database and verify that the resources 
that contain
+      # the specified database could be computed.
       admin_client.execute("grant create on database {0} to {1} {2}"
                            .format(unique_database, kw, id))
       result = self.client.execute("show grant {0} {1} on database {2}"
@@ -495,7 +514,8 @@ class TestRanger(CustomClusterTestSuite):
           [kw, id, unique_database, "*", "*", "", "", "", "", "create", 
"false"]
       ])
 
-      # Grant the insert privilege on table and verify
+      # Grant the insert privilege on table and verify that the resources that 
contain
+      # the specified table could be computed.
       admin_client.execute("grant insert on table {0}.{1} to {2} {3}"
                            .format(unique_database, unique_table, kw, id))
       result = self.client.execute("show grant {0} {1} on table {2}.{3}"
@@ -506,7 +526,9 @@ class TestRanger(CustomClusterTestSuite):
           [kw, id, unique_database, unique_table, "*", "", "", "", "", 
"insert", "false"]
       ])
 
-      # Grant the select privilege on column and verify
+      # Grant the select privilege on the column x and verify that
+      # the select privilege on the column x masks the select privilege on 
every
+      # database, every table, and every column.
       admin_client.execute("grant select(x) on table {0}.{1} to {2} {3}"
                            .format(unique_database, unique_table, kw, id))
       result = self.client.execute("show grant {0} {1} on column {2}.{3}.x"
@@ -517,7 +539,10 @@ class TestRanger(CustomClusterTestSuite):
           [kw, id, unique_database, unique_table, "x", "", "", "", "", 
"select", "false"]
       ])
 
-      # The insert privilege on table masks the select privilege just added
+      # Grant the select privilege on the table and verify that
+      # the select privilege on the column x masks
+      # a) the select privilege on every database, every table, and every 
column,
+      # b) the select privilege on the table just added.
       admin_client.execute("grant select on table {0}.{1} to {2} {3}"
                            .format(unique_database, unique_table, kw, id))
       result = self.client.execute("show grant {0} {1} on column {2}.{3}.x"
@@ -528,8 +553,13 @@ class TestRanger(CustomClusterTestSuite):
           [kw, id, unique_database, unique_table, "x", "", "", "", "", 
"select", "false"]
       ])
 
-      # The all privilege on table masks the privileges of insert and select, 
but not the
-      # select privilege on column.
+      # Grant the all privilege on the table and verify that
+      # a) the all privilege on a table masks the insert and select privileges
+      # on the same table,
+      # b) the all privilege on a table masks the create privilege on the 
resource that
+      # contains this table (a database in this case),
+      # c) the select privilege on the column x masks the select privilege on 
the other
+      # two with resources containing the column x.
       admin_client.execute("grant all on table {0}.{1} to {2} {3}"
                            .format(unique_database, unique_table, kw, id))
       result = self.client.execute("show grant {0} {1} on column {2}.{3}.x"
@@ -539,6 +569,19 @@ class TestRanger(CustomClusterTestSuite):
           [kw, id, unique_database, unique_table, "x", "", "", "", "", 
"select", "false"]
       ])
 
+      # Grant the insert privilege on the column x and verify that
+      # the select privilege on the column x does not mask the insert 
privilege on the
+      # same column.
+      admin_client.execute("grant insert(x) on table {0}.{1} to {2} {3}"
+                           .format(unique_database, unique_table, kw, id))
+      result = self.client.execute("show grant {0} {1} on column {2}.{3}.x"
+                                   .format(kw, id, unique_database, 
unique_table))
+      TestRanger._check_privileges(result, [
+          [kw, id, unique_database, unique_table, "*", "", "", "", "", "all", 
"false"],
+          [kw, id, unique_database, unique_table, "x", "", "", "", "", 
"insert", "false"],
+          [kw, id, unique_database, unique_table, "x", "", "", "", "", 
"select", "false"]
+      ])
+
     finally:
       admin_client.execute("revoke select on server from {0} {1}".format(kw, 
id))
       admin_client.execute("revoke create on database {0} from {1} {2}"
@@ -551,6 +594,8 @@ class TestRanger(CustomClusterTestSuite):
                            .format(unique_database, unique_table, kw, id))
       admin_client.execute("revoke all on table {0}.{1} from {2} {3}"
                            .format(unique_database, unique_table, kw, id))
+      admin_client.execute("revoke insert(x) on table {0}.{1} from {2} {3}"
+                           .format(unique_database, unique_table, kw, id))
 
   @staticmethod
   def _grant_ranger_privilege(user, resource, access):
@@ -744,7 +789,7 @@ class TestRanger(CustomClusterTestSuite):
     return json.loads(r.content)["id"]
 
   @staticmethod
-  def _add_deny_policy(policy_name, user, db, table, column):
+  def _add_deny_policy(policy_name, user, db, table, column, access_type):
     """ Adds a deny policy and returns the policy id"""
     data = {
       "name": policy_name,
@@ -773,7 +818,7 @@ class TestRanger(CustomClusterTestSuite):
         {
           "accesses": [
             {
-              "type": "select",
+              "type": access_type,
               "isAllowed": True
             }
           ],
@@ -783,16 +828,20 @@ class TestRanger(CustomClusterTestSuite):
     }
     r = requests.post("{0}/service/public/v2/api/policy".format(RANGER_HOST),
                       auth=RANGER_AUTH, json=data, headers=REST_HEADERS)
-    assert 300 > r.status_code >= 200, r.content
+    assert 300 > r.status_code >= 200, bytes_to_str(r.content)
     return json.loads(r.content)["id"]
 
   @staticmethod
   def _remove_policy(policy_name):
+    """Removes a policy with the given name if it exists."""
     r = requests.delete(
         
"{0}/service/public/v2/api/policy?servicename=test_impala&policyname={1}".format(
             RANGER_HOST, policy_name),
         auth=RANGER_AUTH, headers=REST_HEADERS)
-    assert 300 > r.status_code >= 200, bytes_to_str(r.content)
+    # We do not return an AssertionError if the policy specified by 
'policy_name' is
+    # not found since this type of error should be considered benign.
+    if r.status_code != 404:
+      assert 300 > r.status_code >= 200, bytes_to_str(r.content)
 
   @staticmethod
   def _toggle_ranger_policy(self, policy_name, enable):
@@ -908,6 +957,31 @@ class TestRanger(CustomClusterTestSuite):
                 break
     return result
 
+  @staticmethod
+  def _get_ranger_policy_names(policies, db, tbl, col):
+    """Returns the set of Ranger policy names that match the given database 
'db',
+    table 'tbl', and column 'col' in the given 'policies'."""
+    result = set()
+    for policy in policies:
+      resources = policy["resources"]
+      policy_name = str(policy["name"])
+      if "database" in resources and db in resources["database"]["values"] \
+          and "table" in resources and tbl in resources["table"]["values"] \
+          and "column" in resources and col in resources["column"]["values"]:
+        result.add(policy_name)
+    return result
+
+  @staticmethod
+  def _remove_policies(db, tbl, col):
+    """Removes all policies that match the given database 'db', table 'tbl',
+    and column 'col'."""
+    policy_names = set()
+    policies = TestRanger._get_ranger_privileges()
+    policy_names = policy_names \
+        .union(TestRanger._get_ranger_policy_names(policies, db, tbl, col))
+    for name in policy_names:
+      TestRanger._remove_policy(name)
+
   def _test_grant_revoke_by_owner(self, unique_name):
     non_owner_user = NON_OWNER
     non_owner_group = NON_OWNER
@@ -1338,7 +1412,7 @@ class TestRanger(CustomClusterTestSuite):
           "alter database {0} set owner user {1}".format(test_db, ADMIN), 
ADMIN, True)
       result = self._run_query_as_user(
           "show tables in {0}".format(test_db), test_user, False)
-      err = "User '{0}' does not have privileges to access: {1}.*.*". \
+      err = "User '{0}' does not have privileges to access: {1}". \
           format(test_user, test_db)
       assert err in str(result)
       # test_user is still the owner of the table, so select should work fine.
@@ -1441,7 +1515,7 @@ class TestRanger(CustomClusterTestSuite):
       # the table 'test_tbl_2', on which 'grantee_user' had been granted the 
SELECT
       # privilege.
       TestRanger._add_deny_policy(unique_name, grantee_user, unique_database, 
test_tbl_2,
-          "id")
+          "id", "select")
       admin_client.execute("refresh authorization")
 
       # The query is not authorized since the SELECT privilege on the column 
'id' in the
@@ -1465,6 +1539,187 @@ class TestRanger(CustomClusterTestSuite):
           .format(unique_database))
       TestRanger._remove_policy(unique_name)
 
+  def _test_deny_insert_into_column_non_partitioned_table(self, unique_name):
+    grantee_user = "non_owner"
+    unique_database = unique_name + "_db"
+    table_prefix = "tbl"
+    tables = [table_prefix, table_prefix + "_iceberg"]
+    columns = ["id", "bigint_col"]
+    create_queries = [
+      ("create table {0}.{1} ({2} int, {3} bigint)"
+      .format(unique_database, tables[0], columns[0], columns[1]),
+       tables[0]),
+      ("create table {0}.{1} ({2} int, {3} bigint) stored as iceberg"
+      .format(unique_database, tables[1], columns[0], columns[1]),
+       tables[1])
+    ]
+    error_msg_prefix = ("User '{0}' does not have privileges to execute 
'INSERT' on:"
+        .format(grantee_user))
+
+    with self.create_impala_client(user=ADMIN) as admin_client, \
+        self.create_impala_client(user=NON_OWNER) as non_owner_client:
+      try:
+        # Set up a temporary database and a table.
+        admin_client.execute("create database {0}".format(unique_database))
+
+        for create_stmt, tbl in create_queries:
+          insert_queries = [
+            ("insert into {0}.{1}({2}) values (1)"
+             .format(unique_database, tbl, columns[0])),
+            ("insert into {0}.{1}({2}) values (1)"
+             .format(unique_database, tbl, columns[1]))]
+
+          try:
+            admin_client.execute(create_stmt)
+
+            # Grant the INSERT privilege on the table to 'grantee_user'.
+            admin_client.execute("grant insert on table {0}.{1} to user {2}"
+                .format(unique_database, tbl, grantee_user))
+            # Add a deny policy on 'columns[0]' to prevent 'grantee_user' from 
inserting
+            # data into this column.
+            TestRanger._add_deny_policy(unique_name + "_deny_" + columns[0],
+                grantee_user, unique_database, tbl, columns[0], "update")
+            admin_client.execute("refresh authorization")
+
+            # Verify inserting into 'columns[0]' by 'grantee_user' is denied.
+            result = self.execute_query_expect_failure(non_owner_client,
+                insert_queries[0])
+            assert error_msg_prefix in str(result)
+
+            # For 'insert_queries[1]', since Impala registered the INSERT 
privilege on
+            # 'columns[1]' but not on 'columns[0]', inserting into 
'columns[1]' by
+            # 'grantee_user' would be allowed.
+            self.execute_query_expect_success(non_owner_client, 
insert_queries[1])
+
+            # Add a column masking policy on 'columns[0]' to prevent 
'grantee_user' from
+            # inserting data into any column of the table.
+            TestRanger._add_column_masking_policy(unique_name + 
"_column_masking_"
+                + columns[0], grantee_user, unique_database, tbl, columns[0], 
"CUSTOM",
+                "id * 100")
+            admin_client.execute("refresh authorization")
+
+            # Verify inserting into 'columns[1]' by 'grantee_user' is denied 
even
+            # though the column masking policy is defined on 'columns[0]'.
+            result = self.execute_query_expect_failure(non_owner_client,
+                insert_queries[1])
+            assert error_msg_prefix in str(result)
+          finally:
+            TestRanger._remove_policy(unique_name + "_deny_" + columns[0])
+            TestRanger._remove_policy(unique_name + "_column_masking_" + 
columns[0])
+            admin_client.execute("revoke insert on table {0}.{1} from user {2}"
+                .format(unique_database, tbl, grantee_user))
+            # This removes the empty policy on the table 'unique_database.tbl' 
that is
+            # not associated with any principal. This policy was added due to 
the GRANT
+            # statement in the try block. Recall that the REVOKE statement 
above
+            # could only remove the associated user but not the entire policy. 
We have to
+            # remove the entire policy in order to add a deny policy on the 
same table in
+            # the next try block.
+            TestRanger._remove_policies(unique_database, tbl, "*")
+            admin_client.execute("refresh authorization")
+
+          try:
+            TestRanger._add_deny_policy(unique_name + "_deny_" + tbl, 
grantee_user,
+                unique_database, tbl, "*", "update")
+            # Grant the INSERT privilege on the column 'columns[1]' to 
'grantee_user'.
+            admin_client.execute("grant insert ({0}) on table {1}.{2} to user 
{3}"
+                .format(columns[1], unique_database, tbl, grantee_user))
+            admin_client.execute("refresh authorization")
+
+            # Verify inserting into the 'columns[1]' by 'grantee_user' is 
denied due to
+            # the deny policy on the table.
+            result = self.execute_query_expect_failure(non_owner_client,
+                insert_queries[1])
+            assert error_msg_prefix in str(result)
+          finally:
+            TestRanger._remove_policy(unique_name + "_deny_" + tbl)
+            admin_client.execute("revoke insert ({0}) on table {1}.{2} from 
user {3}"
+                .format(columns[1], unique_database, tbl, grantee_user))
+      finally:
+        admin_client.execute("drop database if exists {0} cascade"
+            .format(unique_database))
+
+  def _test_deny_insert_into_column_partitioned_table(self, unique_name):
+    grantee_user = "non_owner"
+    unique_database = unique_name + "_db"
+    unique_table = unique_name + "_tbl"
+    unique_tables = [unique_table, unique_table + "_iceberg"]
+    non_part_columns = ["id"]
+    part_columns = ["year"]
+    create_queries = [
+      ("create table {0}.{1} ({2} int) partitioned by ({3} int)"
+       .format(unique_database, unique_tables[0], non_part_columns[0], 
part_columns[0]),
+       unique_tables[0]),
+      ("create table {0}.{1} ({2} int) partitioned by ({3} int) stored as 
iceberg"
+       .format(unique_database, unique_tables[1], non_part_columns[0], 
part_columns[0]),
+       unique_tables[1])
+    ]
+    error_msg_prefix = ("User '{0}' does not have privileges to execute 
'INSERT' on:"
+        .format(grantee_user))
+
+    with self.create_impala_client(user=ADMIN) as admin_client, \
+        self.create_impala_client(user=NON_OWNER) as non_owner_client:
+      try:
+        # Set up a temporary database and a table.
+        admin_client.execute("drop database if exists {0} cascade"
+            .format(unique_database))
+        admin_client.execute("create database {0}".format(unique_database))
+
+        for create_stmt, tbl in create_queries:
+          insert_query = (
+            "insert into {0}.{1}({2}, {3}) values (1, 2025)"
+            .format(unique_database, tbl, non_part_columns[0],
+                part_columns[0]))
+          try:
+            admin_client.execute(create_stmt)
+
+            # Grant the INSERT privilege on the table to 'grantee_user'.
+            admin_client.execute("grant insert on table {0}.{1} to user {2}"
+                .format(unique_database, tbl, grantee_user))
+            # Verify 'grantee_user' is able to insert data into the table.
+            self.execute_query_expect_success(non_owner_client, insert_query)
+
+            # Add a deny policy on 'part_columns[0]' to prevent 'grantee_user' 
from
+            # inserting data into this column.
+            TestRanger._add_deny_policy(unique_name + "_deny_" + 
part_columns[0],
+                grantee_user, unique_database, tbl, part_columns[0],
+                "update")
+            admin_client.execute("refresh authorization")
+
+            # Verify inserting into 'part_columns[0]' by 'grantee_user' is 
denied.
+            result = self.execute_query_expect_failure(non_owner_client, 
insert_query)
+            assert error_msg_prefix in str(result)
+          finally:
+            TestRanger._remove_policy(unique_name + "_deny_" + part_columns[0])
+            admin_client.execute("revoke insert on table {0}.{1} from user {2}"
+                .format(unique_database, tbl, grantee_user))
+            admin_client.execute("refresh authorization")
+
+          try:
+            # Grant the INSERT privilege on the table to 'grantee_user'.
+            admin_client.execute("grant insert on table {0}.{1} to user {2}"
+                .format(unique_database, tbl, grantee_user))
+            # Verify 'grantee_user' is able to insert data into the table.
+            self.execute_query_expect_success(non_owner_client, insert_query)
+
+            # Add a column masking policy on 'part_columns[0]' to prevent 
'grantee_user'
+            # from inserting data into this column.
+            TestRanger._add_column_masking_policy(
+              unique_name + "_column_masking_" + part_columns[0], grantee_user,
+              unique_database, tbl, part_columns[0], "CUSTOM", "id * 100")
+            admin_client.execute("refresh authorization")
+
+            # Verify inserting into 'part_columns[0]' by 'grantee_user' is 
denied.
+            result = self.execute_query_expect_failure(non_owner_client, 
insert_query)
+            assert error_msg_prefix in str(result)
+          finally:
+            TestRanger._remove_policy(unique_name + "_column_masking_" + 
part_columns[0])
+            admin_client.execute("revoke insert on table {0}.{1} from user {2}"
+                .format(unique_database, tbl, grantee_user))
+            admin_client.execute("refresh authorization")
+      finally:
+        admin_client.execute("drop database if exists {0} cascade"
+            .format(unique_database))
+
   def _test_select_calcite_frontend(self, unique_name):
     grantee_user = "non_owner"
     with self.create_impala_client(user=ADMIN) as admin_client, \
@@ -2569,7 +2824,7 @@ class TestRangerLocalCatalog(TestRanger):
       # Check "show functions" with no privilege granted.
       result = self._run_query_as_user("show functions in 
{0}".format(unique_database),
           user1, False)
-      err = "User '{0}' does not have privileges to access: {1}.*.*". \
+      err = "User '{0}' does not have privileges to access: {1}". \
           format(user1, unique_database)
       assert err in str(result)
       for privilege in privileges:
@@ -3495,6 +3750,10 @@ class TestRangerLocalCatalog(TestRanger):
         TestRanger._toggle_ranger_policy(self, policy_name, True)
         admin_client.execute("refresh authorization")
 
+  def test_deny_insert_into_column_with_local_catalog(self, unique_name):
+    self._test_deny_insert_into_column_non_partitioned_table(unique_name)
+    self._test_deny_insert_into_column_partitioned_table(unique_name)
+
 
 class TestRangerColumnMaskingTpchNested(CustomClusterTestSuite):
   """

Reply via email to