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

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


The following commit(s) were added to refs/heads/master by this push:
     new 3f98d61a20e HIVE-29628: Incorrect objectName in PARTITION 
HivePrivilegeObject for view queries on partitioned tables (#6508)
3f98d61a20e is described below

commit 3f98d61a20e8c9fda63c46e23cf190129e2de1b1
Author: rtrivedi12 <[email protected]>
AuthorDate: Wed Jun 3 15:56:54 2026 -0500

    HIVE-29628: Incorrect objectName in PARTITION HivePrivilegeObject for view 
queries on partitioned tables (#6508)
---
 .../authorization/command/CommandAuthorizerV2.java |  88 ++++++++-
 .../plugin/TestViewPartitionPrivilegeObjects.java  | 206 +++++++++++++++++++++
 2 files changed, 287 insertions(+), 7 deletions(-)

diff --git 
a/ql/src/java/org/apache/hadoop/hive/ql/security/authorization/command/CommandAuthorizerV2.java
 
b/ql/src/java/org/apache/hadoop/hive/ql/security/authorization/command/CommandAuthorizerV2.java
index a4c3fd9288c..665a23a1fde 100644
--- 
a/ql/src/java/org/apache/hadoop/hive/ql/security/authorization/command/CommandAuthorizerV2.java
+++ 
b/ql/src/java/org/apache/hadoop/hive/ql/security/authorization/command/CommandAuthorizerV2.java
@@ -19,13 +19,13 @@
 package org.apache.hadoop.hive.ql.security.authorization.command;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.Map.Entry;
 
 import org.apache.hadoop.hive.conf.HiveConf;
-import org.apache.hadoop.hive.metastore.TableType;
 import org.apache.hadoop.hive.metastore.api.DataConnector;
 import org.apache.hadoop.hive.metastore.api.Database;
 import org.apache.hadoop.hive.metastore.api.Function;
@@ -105,6 +105,9 @@ private static List<HivePrivilegeObject> 
getHivePrivObjects(List<? extends Entit
       return hivePrivobjs;
     }
 
+    // Pre-scan once to collect base tables that are accessed via a regular 
view.
+    Set<String> baseTablesViaRegularView = 
buildBaseTablesViaRegularView(privObjects);
+
     for (Entity privObject : privObjects) {
       if (privObject.isDummy()) {
         //do not authorize dummy readEntity or writeEntity
@@ -148,12 +151,35 @@ private static List<HivePrivilegeObject> 
getHivePrivObjects(List<? extends Entit
           continue;
         }
       }
+      if (isPartitionAccessedViaRegularView(privObject, 
baseTablesViaRegularView)) {
+        // skip Partition Entity auth for regular view
+        continue;
+      }
 
       addHivePrivObject(privObject, tableName2Cols, hivePrivobjs, hiveOpType);
     }
     return hivePrivobjs;
   }
 
+  /**
+   * Pre-scans the entity list once and returns the set of "db.table" keys for 
base tables
+   * that are accessed indirectly via a regular view.
+   */
+  private static Set<String> buildBaseTablesViaRegularView(List<? extends 
Entity> entities) {
+    Set<String> result = new HashSet<>();
+    for (Entity entity : entities) {
+      if (!(entity instanceof ReadEntity) || entity.getTyp() != Type.TABLE) {
+        continue;
+      }
+      ReadEntity re = (ReadEntity) entity;
+      Table t = re.getTable();
+      if (!re.isDirect() && t != null && !hasDeferredViewParent(re) && 
hasRegularViewParent(re)) {
+        result.add(t.getDbName() + "." + t.getTableName());
+      }
+    }
+    return result;
+  }
+
   /**
    * A deferred authorization view is view created by non-super user like 
spark-user. This view contains a parameter "Authorized"
    * set to false, so ranger will not authorize it during view creation. When 
a select statement is issued, then the ranger authorizes
@@ -162,13 +188,8 @@ private static List<HivePrivilegeObject> 
getHivePrivObjects(List<? extends Entit
    * @return boolean value
    */
   private static boolean isDeferredAuthView(Table t){
-    String tableType = t.getTTable().getTableType();
     String authorizedKeyword = "Authorized";
-    boolean isView = false;
-    if (TableType.MATERIALIZED_VIEW.name().equals(tableType) || 
TableType.VIRTUAL_VIEW.name().equals(tableType)) {
-      isView = true;
-    }
-    if (isView) {
+    if (t.isView() || t.isMaterializedView()) {
       Map<String, String> params = t.getParameters();
       if (params != null && params.containsKey(authorizedKeyword)) {
         String authorizedValue = params.get(authorizedKeyword);
@@ -180,6 +201,59 @@ private static boolean isDeferredAuthView(Table t){
     return false;
   }
 
+  /**
+   * Returns true when a PARTITION entity should not produce its own privilege 
object
+   * because access is already covered by a view's TABLE_OR_VIEW object.
+   */
+  private static boolean isPartitionAccessedViaRegularView(Entity entity,
+      Set<String> baseTablesViaRegularView) {
+    if (!(entity instanceof ReadEntity)
+        || (entity.getTyp() != Type.PARTITION && entity.getTyp() != 
Type.DUMMYPARTITION)) {
+      return false;
+    }
+    ReadEntity re = (ReadEntity) entity;
+    // Deferred-auth views must still authorize the underlying base table.
+    if (hasDeferredViewParent(re)) {
+      return false;
+    }
+
+    if (hasRegularViewParent(re)) {
+      return true;
+    }
+    Table partTable = re.getTable();
+    return partTable != null
+        && baseTablesViaRegularView.contains(partTable.getDbName() + "." + 
partTable.getTableName());
+  }
+
+  private static boolean hasDeferredViewParent(ReadEntity entity) {
+    Set<ReadEntity> parents = entity.getParents();
+    if (parents == null || parents.isEmpty()) {
+      return false;
+    }
+    for (ReadEntity parent : parents) {
+      if (parent.getTyp() == Type.TABLE && parent.getTable() != null
+          && isDeferredAuthView(parent.getTable())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static boolean hasRegularViewParent(ReadEntity entity) {
+    Set<ReadEntity> parents = entity.getParents();
+    if (parents == null || parents.isEmpty()) {
+      return false;
+    }
+    for (ReadEntity parent : parents) {
+      if (parent.getTyp() == Type.TABLE && parent.getTable() != null
+          && (parent.getTable().isView() || 
parent.getTable().isMaterializedView())
+          && !isDeferredAuthView(parent.getTable())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private static void addHivePrivObject(Entity privObject, Map<String, 
List<String>> tableName2Cols,
       List<HivePrivilegeObject> hivePrivObjs, HiveOperationType hiveOpType) 
throws HiveException {
     HivePrivilegeObjectType privObjType = 
AuthorizationUtils.getHivePrivilegeObjectType(privObject.getType());
diff --git 
a/ql/src/test/org/apache/hadoop/hive/ql/security/authorization/plugin/TestViewPartitionPrivilegeObjects.java
 
b/ql/src/test/org/apache/hadoop/hive/ql/security/authorization/plugin/TestViewPartitionPrivilegeObjects.java
new file mode 100644
index 00000000000..ec7efd7d627
--- /dev/null
+++ 
b/ql/src/test/org/apache/hadoop/hive/ql/security/authorization/plugin/TestViewPartitionPrivilegeObjects.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hive.ql.security.authorization.plugin;
+
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
+import org.apache.hadoop.hive.conf.HiveConfForTest;
+import org.apache.hadoop.hive.metastore.utils.TestTxnDbUtil;
+import org.apache.hadoop.hive.ql.Driver;
+import org.apache.hadoop.hive.ql.lockmgr.DbTxnManager;
+import org.apache.hadoop.hive.ql.security.HiveAuthenticationProvider;
+import org.apache.hadoop.hive.ql.session.SessionState;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests the {@link HivePrivilegeObject} inputs passed to {@link 
HiveAuthorizer#checkPrivileges}
+ * for view queries over partitioned base tables.
+ */
+public class TestViewPartitionPrivilegeObjects {
+
+  protected static HiveConf conf;
+  protected static Driver driver;
+  static HiveAuthorizer mockedAuthorizer;
+
+  static class MockedHiveAuthorizerFactory implements HiveAuthorizerFactory {
+    @Override
+    public HiveAuthorizer createHiveAuthorizer(HiveMetastoreClientFactory 
metastoreClientFactory,
+        HiveConf conf, HiveAuthenticationProvider authenticator, 
HiveAuthzSessionContext ctx) {
+      TestViewPartitionPrivilegeObjects.mockedAuthorizer = 
Mockito.mock(HiveAuthorizer.class);
+      return TestViewPartitionPrivilegeObjects.mockedAuthorizer;
+    }
+  }
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    
UserGroupInformation.setLoginUser(UserGroupInformation.createRemoteUser("hive"));
+    conf = new HiveConfForTest(TestViewPartitionPrivilegeObjects.class);
+    conf.setVar(ConfVars.HIVE_AUTHORIZATION_MANAGER, 
MockedHiveAuthorizerFactory.class.getName());
+    conf.setBoolVar(ConfVars.HIVE_AUTHORIZATION_ENABLED, true);
+    conf.setBoolVar(ConfVars.HIVE_SERVER2_ENABLE_DOAS, false);
+    conf.setBoolVar(ConfVars.HIVE_SUPPORT_CONCURRENCY, true);
+    conf.setVar(ConfVars.HIVE_TXN_MANAGER, DbTxnManager.class.getName());
+    conf.setVar(ConfVars.DYNAMIC_PARTITIONING_MODE, "nonstrict");
+    conf.setVar(ConfVars.HIVE_FETCH_TASK_CONVERSION, "none");
+    conf.setVar(ConfVars.HIVE_EXECUTION_ENGINE, "mr");
+
+    TestTxnDbUtil.prepDb(conf);
+    SessionState.start(conf);
+    driver = new Driver(conf);
+
+    runCmd("CREATE DATABASE IF NOT EXISTS datadb");
+    runCmd("CREATE TABLE IF NOT EXISTS datadb.t1 (i INT) PARTITIONED BY (dept 
STRING)");
+    runCmd("ALTER TABLE datadb.t1 ADD IF NOT EXISTS PARTITION (dept='a')");
+    runCmd("CREATE DATABASE IF NOT EXISTS viewdb");
+    runCmd("CREATE VIEW IF NOT EXISTS viewdb.v1 AS SELECT * FROM datadb.t1");
+    // Target table used by INSERT OVERWRITE tests — non-partitioned to keep 
DML simple
+    runCmd("CREATE TABLE IF NOT EXISTS datadb.insert_target (i INT, dept 
STRING)");
+  }
+
+  @Before
+  public void resetMock() {
+    if (mockedAuthorizer != null) {
+      reset(mockedAuthorizer);
+    }
+  }
+
+  @AfterClass
+  public static void afterClass() throws Exception {
+    runCmd("DROP VIEW IF EXISTS viewdb.v1");
+    runCmd("DROP TABLE IF EXISTS datadb.t1");
+    runCmd("DROP TABLE IF EXISTS datadb.insert_target");
+    runCmd("DROP DATABASE IF EXISTS viewdb");
+    runCmd("DROP DATABASE IF EXISTS datadb");
+    driver.close();
+  }
+
+  @Test
+  public void testViewSelectNoPartitionPrivObj() throws Exception {
+    conf.setVar(ConfVars.HIVE_FETCH_TASK_CONVERSION, "none");
+    SessionState.get().setConf(conf);
+
+    HiveAuthenticationProvider user1Auth = 
Mockito.mock(HiveAuthenticationProvider.class);
+    Mockito.when(user1Auth.getUserName()).thenReturn("user1");
+    SessionState.get().setAuthenticator(user1Auth);
+
+    driver.compile("SELECT * FROM viewdb.v1", true);
+
+    List<HivePrivilegeObject> inputs = getInputPrivObjects();
+
+    Assert.assertTrue("Expected a TABLE_OR_VIEW object for the view",
+        inputs.stream().anyMatch(h ->
+            h.getType() == 
HivePrivilegeObject.HivePrivilegeObjectType.TABLE_OR_VIEW
+                && "v1".equalsIgnoreCase(h.getObjectName())
+                && "viewdb".equalsIgnoreCase(h.getDbname())));
+
+    Assert.assertFalse("HIVE-29628: view query must not send a PARTITION 
object on the base table",
+        inputs.stream().anyMatch(h ->
+            h.getType() == 
HivePrivilegeObject.HivePrivilegeObjectType.PARTITION
+                && "t1".equalsIgnoreCase(h.getObjectName())
+                && "datadb".equalsIgnoreCase(h.getDbname())));
+
+    Assert.assertFalse("HIVE-29628: view query must not send a base-table 
TABLE_OR_VIEW object",
+        inputs.stream().anyMatch(h ->
+            h.getType() == 
HivePrivilegeObject.HivePrivilegeObjectType.TABLE_OR_VIEW
+                && "t1".equalsIgnoreCase(h.getObjectName())
+                && "datadb".equalsIgnoreCase(h.getDbname())));
+  }
+
+  /**
+   * Direct reads on a partitioned table must still emit a PARTITION privilege 
object
+   * so table/partition policies (e.g. Ranger) can be enforced.
+   */
+  @Test
+  public void testDirectTableSelect() throws Exception {
+    conf.setVar(ConfVars.HIVE_FETCH_TASK_CONVERSION, "none");
+    SessionState.get().setConf(conf);
+
+    driver.compile("SELECT * FROM datadb.t1", true);
+
+    List<HivePrivilegeObject> inputs = getInputPrivObjects();
+
+    Assert.assertTrue("Expected a PARTITION privilege object for direct table 
access",
+        inputs.stream().anyMatch(h ->
+            h.getType() == 
HivePrivilegeObject.HivePrivilegeObjectType.PARTITION
+                && "t1".equalsIgnoreCase(h.getObjectName())
+                && "datadb".equalsIgnoreCase(h.getDbname())));
+  }
+
+  /**
+   * When a view over a partitioned table is the READ SOURCE of an INSERT 
OVERWRITE statement,
+   * {@code checkPrivileges} inputs must contain only {@code TABLE_OR_VIEW 
viewdb/v1}.
+   */
+  @Test
+  public void testInsertOverwriteFromView() throws Exception {
+    conf.setVar(ConfVars.HIVE_FETCH_TASK_CONVERSION, "none");
+    SessionState.get().setConf(conf);
+
+    driver.compile("INSERT OVERWRITE TABLE datadb.insert_target SELECT * FROM 
viewdb.v1", true);
+
+    List<HivePrivilegeObject> inputs = getInputPrivObjects();
+
+    Assert.assertTrue("Expected TABLE_OR_VIEW privilege object for view source 
in INSERT",
+        inputs.stream().anyMatch(h ->
+            h.getType() == 
HivePrivilegeObject.HivePrivilegeObjectType.TABLE_OR_VIEW
+                && "v1".equalsIgnoreCase(h.getObjectName())
+                && "viewdb".equalsIgnoreCase(h.getDbname())));
+
+    Assert.assertFalse(
+        "INSERT from view must not send a PARTITION privilege object on the 
base table",
+        inputs.stream().anyMatch(h ->
+            h.getType() == 
HivePrivilegeObject.HivePrivilegeObjectType.PARTITION
+                && "t1".equalsIgnoreCase(h.getObjectName())
+                && "datadb".equalsIgnoreCase(h.getDbname())));
+  }
+
+  @SuppressWarnings("unchecked")
+  private List<HivePrivilegeObject> getInputPrivObjects()
+      throws HiveAuthzPluginException, HiveAccessControlException {
+    Class<List<HivePrivilegeObject>> cls = (Class) List.class;
+    ArgumentCaptor<List<HivePrivilegeObject>> inputsCapturer = 
ArgumentCaptor.forClass(cls);
+    ArgumentCaptor<List<HivePrivilegeObject>> outputsCapturer = 
ArgumentCaptor.forClass(cls);
+
+    verify(mockedAuthorizer, atLeastOnce()).checkPrivileges(
+        any(HiveOperationType.class),
+        inputsCapturer.capture(),
+        outputsCapturer.capture(),
+        any(HiveAuthzContext.class));
+
+    List<List<HivePrivilegeObject>> all = inputsCapturer.getAllValues();
+    return all.get(all.size() - 1);
+  }
+
+  private static void runCmd(String cmd) throws Exception {
+    driver.run(cmd);
+  }
+}

Reply via email to