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);
+ }
+}