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

stigahuang 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 b3b8e2103 IMPALA-12578: Pass owner user of database and table to 
Ranger in GRANT/REVOKE
b3b8e2103 is described below

commit b3b8e21038f952b83287d321a6e68c4d5e8cfebd
Author: Fang-Yu Rao <[email protected]>
AuthorDate: Mon Dec 11 16:01:10 2023 -0800

    IMPALA-12578: Pass owner user of database and table to Ranger in 
GRANT/REVOKE
    
    After RANGER-1200, Ranger allows the owner user of a resource to
    grant/revoke a privilege to/from a grantee/revokee, which requires the
    client of the Ranger server to provide the ownership information in the
    requests for granting and revoking accesses.
    
    Before this patch, Impala did not provide its Ranger plug-in with the
    owner user of resource in the GRANT and REVOKE statements and thus the
    owner user of a resource was not able to grant/revoke a privilege
    to/from other principals. This patch passes to the Ranger server the
    owner user of resource in the GRANT and REVOKE statements when the
    resource is a database, a table, or a column. This way the owner user
    does not have to be explicitly granted additional privileges on the
    resource to execute the GRANT and REVOKE statements for the
    aforementioned resource types.
    
    For user-defined functions, we will deal with this resource type in
    IMPALA-12685 in that it depends on IMPALA-11743 where we will have to
    make Impala load from Hive MetaStore the owner user of a user-defined
    function.
    
    The patch also fixes a potential bug in getOwnerUser() of Db, LocalDb,
    Table, and LocalTable. Specifically, before this patch, when
    determining the owner user of a database or a table, Impala returned
    the owner name without verifying the corresponding principal type is
    indeed a user. This was problematic because the principal type could be
    a group or a role. In addition, we note that Ranger assumes implicitly
    that the provided owner is a user. This could be seen from the
    definition of GrantRevokeRequest. Before Ranger adds an additional
    field in GrantRevokeRequest to distinguish an owner user from an owner
    group, Impala will not be able to support allowing a user in an owner
    group to grant or revoke privileges on the resources owned by the owner
    group.
    
    Testing:
     - Added an end-to-end test to verify that the owner user of a resource
       is able to execute the GRANT/REVOKE statements without being granted
       additional privileges if the resource is a database, a table, or a
       column.
    
    Change-Id: Ibac5335c65a860963ef0ccd890a926af80585ef3
    Reviewed-on: http://gerrit.cloudera.org:8080/20916
    Tested-by: Impala Public Jenkins <[email protected]>
    Reviewed-by: Fang-Yu Rao <[email protected]>
---
 common/thrift/JniCatalog.thrift                    |   3 +
 .../impala/analysis/GrantRevokePrivStmt.java       |   4 +
 .../org/apache/impala/analysis/PrivilegeSpec.java  |  25 +-
 .../ranger/RangerCatalogdAuthorizationManager.java |  25 +-
 fe/src/main/java/org/apache/impala/catalog/Db.java |   4 +-
 .../main/java/org/apache/impala/catalog/Table.java |   3 +-
 .../org/apache/impala/catalog/local/LocalDb.java   |   4 +-
 .../apache/impala/catalog/local/LocalTable.java    |   3 +-
 .../authorization/AuthorizationTestBase.java       |   6 +-
 tests/authorization/test_ranger.py                 | 321 +++++++++++++++++++++
 10 files changed, 375 insertions(+), 23 deletions(-)

diff --git a/common/thrift/JniCatalog.thrift b/common/thrift/JniCatalog.thrift
index 879bb610a..9f2e8139e 100755
--- a/common/thrift/JniCatalog.thrift
+++ b/common/thrift/JniCatalog.thrift
@@ -737,6 +737,9 @@ struct TGrantRevokePrivParams {
 
   // The type of principal
   5: required CatalogObjects.TPrincipalType principal_type
+
+  // The name of the resource owner.
+  6: optional string owner_name
 }
 
 // Parameters of DROP DATABASE commands
diff --git 
a/fe/src/main/java/org/apache/impala/analysis/GrantRevokePrivStmt.java 
b/fe/src/main/java/org/apache/impala/analysis/GrantRevokePrivStmt.java
index 6372fe704..06ae0ea03 100644
--- a/fe/src/main/java/org/apache/impala/analysis/GrantRevokePrivStmt.java
+++ b/fe/src/main/java/org/apache/impala/analysis/GrantRevokePrivStmt.java
@@ -65,6 +65,10 @@ public class GrantRevokePrivStmt extends AuthorizationStmt {
     params.setPrincipal_type(principalType_);
     params.setHas_grant_opt(hasGrantOpt_);
     params.setPrivileges(privileges);
+    String ownerName = privilegeSpec_.getOwnerName();
+    if (ownerName != null) {
+      params.setOwner_name(ownerName);
+    }
     return params;
   }
 
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 5fda09710..aab0948c5 100644
--- a/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java
+++ b/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java
@@ -56,6 +56,7 @@ public class PrivilegeSpec extends StmtNode {
   // Set/modified during analysis
   private String dbName_;
   private String serverName_;
+  private String ownerName_;
 
   private PrivilegeSpec(TPrivilegeLevel privilegeLevel, TPrivilegeScope scope,
       String serverName, String dbName, TableName tableName, HdfsUri uri,
@@ -232,14 +233,7 @@ public class PrivilegeSpec extends StmtNode {
       case SERVER:
         break;
       case DATABASE:
-        Preconditions.checkState(!Strings.isNullOrEmpty(dbName_));
-        try {
-          analyzer.getDb(dbName_, true);
-        } catch (AnalysisException e) {
-          throw new AnalysisException(String.format("Error setting/showing 
privileges " +
-              "for database '%s'. Verify that the database exists and that you 
have " +
-              "permissions to issue a GRANT/REVOKE/SHOW GRANT statement.", 
dbName_));
-        }
+        analyzeTargetDatabase(analyzer);
         break;
       case URI:
         Preconditions.checkNotNull(uri_);
@@ -325,6 +319,7 @@ public class PrivilegeSpec extends StmtNode {
       dbName_ = analyzer.getTargetDbName(tableName_);
       Preconditions.checkNotNull(dbName_);
       table = analyzer.getTable(dbName_, tableName_.getTbl(), /* must_exist */ 
true);
+      ownerName_ = table.getOwnerUser();
     } catch (TableLoadingException e) {
       throw new AnalysisException(e.getMessage(), e);
     } catch (AnalysisException e) {
@@ -336,6 +331,18 @@ public class PrivilegeSpec extends StmtNode {
     return table;
   }
 
+  private void analyzeTargetDatabase(Analyzer analyzer) throws 
AnalysisException {
+    Preconditions.checkState(!Strings.isNullOrEmpty(dbName_));
+    try {
+      FeDb db = analyzer.getDb(dbName_, /* throwIfDoesNotExist */ true);
+      ownerName_ = db.getOwnerUser();
+    } catch (AnalysisException e) {
+      throw new AnalysisException(String.format("Error setting/showing 
privileges " +
+          "for database '%s'. Verify that the database exists and that you 
have " +
+          "permissions to issue a GRANT/REVOKE/SHOW GRANT statement.", 
dbName_));
+    }
+  }
+
   private void analyzeUdf(Analyzer analyzer) throws AnalysisException {
     Preconditions.checkState(scope_ == TPrivilegeScope.USER_DEFINED_FN);
 
@@ -373,4 +380,6 @@ public class PrivilegeSpec extends StmtNode {
   public String getDbName() { return dbName_; }
 
   public String getServerName() { return serverName_; }
+
+  public String getOwnerName() { return ownerName_; }
 }
diff --git 
a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerCatalogdAuthorizationManager.java
 
b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerCatalogdAuthorizationManager.java
index 22adcad1f..809f2928b 100644
--- 
a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerCatalogdAuthorizationManager.java
+++ 
b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerCatalogdAuthorizationManager.java
@@ -226,7 +226,8 @@ public class RangerCatalogdAuthorizationManager implements 
AuthorizationManager
         new User(header.getRequesting_user()).getShortName(), /*isGrant*/ true,
         /*user*/ null, Collections.emptyList(),
         Collections.singletonList(params.getPrincipal_name()),
-        plugin_.get().getClusterName(), header.getClient_ip(), 
params.getPrivileges());
+        plugin_.get().getClusterName(), header.getClient_ip(), 
params.getPrivileges(),
+        params.getOwner_name());
 
     grantPrivilege(requests, header.getRedacted_sql_stmt(), 
header.getClient_ip());
     // Update the authorization refresh marker so that the Impalads can 
refresh their
@@ -241,7 +242,8 @@ public class RangerCatalogdAuthorizationManager implements 
AuthorizationManager
         new User(header.getRequesting_user()).getShortName(), /*isGrant*/ 
false,
         /*user*/ null, Collections.emptyList(),
         Collections.singletonList(params.getPrincipal_name()),
-        plugin_.get().getClusterName(), header.getClient_ip(), 
params.getPrivileges());
+        plugin_.get().getClusterName(), header.getClient_ip(), 
params.getPrivileges(),
+        params.getOwner_name());
 
     revokePrivilege(requests, header.getRedacted_sql_stmt(), 
header.getClient_ip());
     // Update the authorization refresh marker so that the Impalads can 
refresh their
@@ -255,7 +257,8 @@ public class RangerCatalogdAuthorizationManager implements 
AuthorizationManager
     List<GrantRevokeRequest> requests = createGrantRevokeRequests(
         new User(header.getRequesting_user()).getShortName(), /*isGrant*/ true,
         params.getPrincipal_name(), Collections.emptyList(), 
Collections.emptyList(),
-        plugin_.get().getClusterName(), header.getClient_ip(), 
params.getPrivileges());
+        plugin_.get().getClusterName(), header.getClient_ip(), 
params.getPrivileges(),
+        params.getOwner_name());
 
     grantPrivilege(requests, header.getRedacted_sql_stmt(), 
header.getClient_ip());
     refreshAuthorization(response);
@@ -267,7 +270,8 @@ public class RangerCatalogdAuthorizationManager implements 
AuthorizationManager
     List<GrantRevokeRequest> requests = createGrantRevokeRequests(
         new User(header.getRequesting_user()).getShortName(), /*isGrant*/ 
false,
         params.getPrincipal_name(), Collections.emptyList(), 
Collections.emptyList(),
-        plugin_.get().getClusterName(), header.getClient_ip(), 
params.getPrivileges());
+        plugin_.get().getClusterName(), header.getClient_ip(), 
params.getPrivileges(),
+        params.getOwner_name());
 
     revokePrivilege(requests, header.getRedacted_sql_stmt(), 
header.getClient_ip());
     refreshAuthorization(response);
@@ -280,7 +284,7 @@ public class RangerCatalogdAuthorizationManager implements 
AuthorizationManager
         new User(header.getRequesting_user()).getShortName(), /*isGrant*/ true,
         /*user*/ null, Collections.singletonList(params.getPrincipal_name()),
         Collections.emptyList(), plugin_.get().getClusterName(), 
header.getClient_ip(),
-        params.getPrivileges());
+        params.getPrivileges(), params.getOwner_name());
 
     grantPrivilege(requests, header.getRedacted_sql_stmt(), 
header.getClient_ip());
     refreshAuthorization(response);
@@ -293,7 +297,7 @@ public class RangerCatalogdAuthorizationManager implements 
AuthorizationManager
         new User(header.getRequesting_user()).getShortName(), /*isGrant*/ 
false,
         /*user*/ null, Collections.singletonList(params.getPrincipal_name()),
         Collections.emptyList(), plugin_.get().getClusterName(), 
header.getClient_ip(),
-        params.getPrivileges());
+        params.getPrivileges(), params.getOwner_name());
 
     revokePrivilege(requests, header.getRedacted_sql_stmt(), 
header.getClient_ip());
     // Update the authorization refresh marker so that the Impalads can 
refresh their
@@ -376,13 +380,15 @@ public class RangerCatalogdAuthorizationManager 
implements AuthorizationManager
 
   public static List<GrantRevokeRequest> createGrantRevokeRequests(String 
grantor,
       boolean isGrant, String user, List<String> groups, List<String> roles,
-      String clusterName, String clientIp, List<TPrivilege> privileges) {
+      String clusterName, String clientIp, List<TPrivilege> privileges,
+      String resourceOwner) {
     List<GrantRevokeRequest> requests = new ArrayList<>();
 
     for (TPrivilege p: privileges) {
       Function<Map<String, String>, GrantRevokeRequest> createRequest = 
(resource) ->
           createGrantRevokeRequest(grantor, user, groups, roles, clusterName,
-              p.has_grant_opt, isGrant, p.privilege_level, resource, clientIp);
+              p.has_grant_opt, isGrant, p.privilege_level, resource, 
resourceOwner,
+              clientIp);
 
       // Ranger Impala service definition defines 4 resources:
       // [DB -> Table -> Column]
@@ -419,7 +425,7 @@ public class RangerCatalogdAuthorizationManager implements 
AuthorizationManager
   private static GrantRevokeRequest createGrantRevokeRequest(String grantor, 
String user,
       List<String> groups, List<String> roles, String clusterName, boolean 
withGrantOpt,
       boolean isGrant, TPrivilegeLevel level, Map<String, String> resource,
-      String clientIp) {
+      String resourceOwner, String clientIp) {
     GrantRevokeRequest request = new GrantRevokeRequest();
     request.setGrantor(grantor);
     // In a Kerberized environment, we also need to call setGrantorGroups() to 
provide
@@ -434,6 +440,7 @@ public class RangerCatalogdAuthorizationManager implements 
AuthorizationManager
     request.setReplaceExistingPermissions(Boolean.FALSE);
     request.setClusterName(clusterName);
     request.setResource(resource);
+    if (resourceOwner != null) request.setOwnerUser(resourceOwner);
     request.setClientIPAddress(clientIp);
 
     // For revoke grant option, omit the privilege
diff --git a/fe/src/main/java/org/apache/impala/catalog/Db.java 
b/fe/src/main/java/org/apache/impala/catalog/Db.java
index f767ba80a..919a71e3f 100644
--- a/fe/src/main/java/org/apache/impala/catalog/Db.java
+++ b/fe/src/main/java/org/apache/impala/catalog/Db.java
@@ -29,6 +29,7 @@ import java.util.concurrent.locks.ReentrantLock;
 import java.util.stream.Collectors;
 
 import org.apache.hadoop.hive.metastore.api.Database;
+import org.apache.hadoop.hive.metastore.api.PrincipalType;
 import org.apache.impala.analysis.ColumnDef;
 import org.apache.impala.analysis.KuduPartitionParam;
 import org.apache.impala.catalog.events.InFlightEvents;
@@ -619,7 +620,8 @@ public class Db extends CatalogObjectImpl implements FeDb {
   @Override // FeDb
   public String getOwnerUser() {
     org.apache.hadoop.hive.metastore.api.Database db = getMetaStoreDb();
-    return db == null ? null : db.getOwnerName();
+    return db == null ? null :
+        (db.getOwnerType() == PrincipalType.USER ? db.getOwnerName() : null);
   }
 
   /**
diff --git a/fe/src/main/java/org/apache/impala/catalog/Table.java 
b/fe/src/main/java/org/apache/impala/catalog/Table.java
index 0c641505b..dfe4bc87f 100644
--- a/fe/src/main/java/org/apache/impala/catalog/Table.java
+++ b/fe/src/main/java/org/apache/impala/catalog/Table.java
@@ -35,6 +35,7 @@ import org.apache.hadoop.hive.metastore.TableType;
 import org.apache.hadoop.hive.metastore.api.ColumnStatisticsData;
 import org.apache.hadoop.hive.metastore.api.ColumnStatisticsObj;
 import org.apache.hadoop.hive.metastore.api.FieldSchema;
+import org.apache.hadoop.hive.metastore.api.PrincipalType;
 import org.apache.impala.analysis.TableName;
 import org.apache.impala.catalog.events.InFlightEvents;
 import org.apache.impala.catalog.monitor.CatalogMonitor;
@@ -925,7 +926,7 @@ public abstract class Table extends CatalogObjectImpl 
implements FeTable {
       LOG.warn("Owner of {} is unknown due to table is unloaded", 
getFullName());
       return null;
     }
-    return msTable_.getOwner();
+    return msTable_.getOwnerType() == PrincipalType.USER ? msTable_.getOwner() 
: null;
   }
 
   public void setMetaStoreTable(org.apache.hadoop.hive.metastore.api.Table 
msTbl) {
diff --git a/fe/src/main/java/org/apache/impala/catalog/local/LocalDb.java 
b/fe/src/main/java/org/apache/impala/catalog/local/LocalDb.java
index a12e4aa9e..8669af064 100644
--- a/fe/src/main/java/org/apache/impala/catalog/local/LocalDb.java
+++ b/fe/src/main/java/org/apache/impala/catalog/local/LocalDb.java
@@ -26,6 +26,7 @@ import java.util.Map;
 import java.util.Set;
 
 import org.apache.hadoop.hive.metastore.api.Database;
+import org.apache.hadoop.hive.metastore.api.PrincipalType;
 import org.apache.hadoop.hive.metastore.api.Table;
 import org.apache.impala.analysis.ColumnDef;
 import org.apache.impala.analysis.KuduPartitionParam;
@@ -325,6 +326,7 @@ public class LocalDb implements FeDb {
   @Override // FeDb
   public String getOwnerUser() {
     Database db = getMetaStoreDb();
-    return db == null? null : db.getOwnerName();
+    return db == null ? null :
+        (db.getOwnerType() == PrincipalType.USER ? db.getOwnerName() : null);
   }
 }
diff --git a/fe/src/main/java/org/apache/impala/catalog/local/LocalTable.java 
b/fe/src/main/java/org/apache/impala/catalog/local/LocalTable.java
index 30c1270c1..15e1bda6a 100644
--- a/fe/src/main/java/org/apache/impala/catalog/local/LocalTable.java
+++ b/fe/src/main/java/org/apache/impala/catalog/local/LocalTable.java
@@ -27,6 +27,7 @@ import com.google.common.annotations.VisibleForTesting;
 import org.apache.hadoop.hive.common.ValidWriteIdList;
 import org.apache.hadoop.hive.metastore.TableType;
 import org.apache.hadoop.hive.metastore.api.ColumnStatisticsObj;
+import org.apache.hadoop.hive.metastore.api.PrincipalType;
 import org.apache.hadoop.hive.metastore.api.Table;
 import org.apache.impala.analysis.TableName;
 import org.apache.impala.catalog.ArrayType;
@@ -230,7 +231,7 @@ abstract class LocalTable implements FeTable {
       LOG.warn("Owner of {} is unknown due to msTable is unloaded", 
getFullName());
       return null;
     }
-    return msTable_.getOwner();
+    return msTable_.getOwnerType() == PrincipalType.USER ? msTable_.getOwner() 
: null;
   }
 
   @Override
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 3e551227a..364628456 100644
--- 
a/fe/src/test/java/org/apache/impala/authorization/AuthorizationTestBase.java
+++ 
b/fe/src/test/java/org/apache/impala/authorization/AuthorizationTestBase.java
@@ -207,7 +207,8 @@ public abstract class AuthorizationTestBase extends 
FrontendTestBase {
           // owner of the resource.
           getName(),
           Collections.emptyList(), Collections.emptyList(),
-          rangerImpalaPlugin_.getClusterName(), "127.0.0.1", privileges);
+          rangerImpalaPlugin_.getClusterName(), "127.0.0.1", privileges,
+          /*resourceOwner*/ null);
     }
   }
 
@@ -221,7 +222,8 @@ public abstract class AuthorizationTestBase extends 
FrontendTestBase {
           // owner of the resource.
           (as_owner_ ? OWNER_GROUPS : GROUPS), Collections.emptyList(),
           // groups,
-          rangerImpalaPlugin_.getClusterName(), "127.0.0.1", privileges);
+          rangerImpalaPlugin_.getClusterName(), "127.0.0.1", privileges,
+          /*resourceOwner*/ null);
     }
   }
 
diff --git a/tests/authorization/test_ranger.py 
b/tests/authorization/test_ranger.py
index d681e77aa..73b5f2a05 100644
--- a/tests/authorization/test_ranger.py
+++ b/tests/authorization/test_ranger.py
@@ -38,6 +38,11 @@ from tests.util.filesystem_utils import WAREHOUSE_PREFIX, 
WAREHOUSE
 from tests.util.iceberg_util import get_snapshots
 
 ADMIN = "admin"
+OWNER_USER = getuser()
+NON_OWNER = "non_owner"
+ERROR_GRANT = "User doesn't have necessary permission to grant access"
+ERROR_REVOKE = "User doesn't have necessary permission to revoke access"
+
 RANGER_AUTH = ("admin", "admin")
 RANGER_HOST = "http://localhost:6080";
 REST_HEADERS = {"Content-Type": "application/json", "Accept": 
"application/json"}
@@ -1119,6 +1124,322 @@ class TestRanger(CustomClusterTestSuite):
       pytest.xfail("getTableIfCached() faulty behavior, known issue")
       self._test_ownership()
 
+  @pytest.mark.execute_serially
+  @CustomClusterTestSuite.with_args(
+    impalad_args=IMPALAD_ARGS, catalogd_args=CATALOGD_ARGS)
+  def test_grant_revoke_by_owner_legacy_catalog(self, unique_name):
+    self._test_grant_revoke_by_owner(unique_name)
+
+  @pytest.mark.execute_serially
+  @CustomClusterTestSuite.with_args(impalad_args=LOCAL_CATALOG_IMPALAD_ARGS,
+    catalogd_args=LOCAL_CATALOG_CATALOGD_ARGS)
+  def test_grant_revoke_by_owner_local_catalog(self, unique_name):
+    self._test_grant_revoke_by_owner(unique_name)
+
+  def _test_grant_revoke_by_owner(self, unique_name):
+    non_owner_user = NON_OWNER
+    non_owner_group = NON_OWNER
+    grantee_role = "grantee_role"
+    resource_owner_role = OWNER_USER
+    admin_client = self.create_impala_client()
+    unique_database = unique_name + "_db"
+    table_name = "tbl"
+    column_names = ["a", "b"]
+    udf_uri = "/test-warehouse/impala-hive-udfs.jar"
+    udf_name = "identity"
+    test_data = [("USER", non_owner_user), ("GROUP", non_owner_group),
+        ("ROLE", grantee_role)]
+    privileges = ["alter", "drop", "create", "insert", "select", "refresh"]
+
+    try:
+      admin_client.execute("create role {}".format(grantee_role), user=ADMIN)
+      admin_client.execute("create role {}".format(resource_owner_role), 
user=ADMIN)
+      admin_client.execute("grant create on server to user 
{0}".format(OWNER_USER),
+          user=ADMIN)
+      # A user has to be granted the ALL privilege on the URI of the UDF as 
well to be
+      # able to create a UDF.
+      admin_client.execute("grant all on uri '{0}{1}' to user {2}"
+          .format(os.getenv("FILESYSTEM_PREFIX"), udf_uri, OWNER_USER), 
user=ADMIN)
+      self._run_query_as_user("create database {0}".format(unique_database), 
OWNER_USER,
+          True)
+      self._run_query_as_user("create table {0}.{1} ({2} int, {3} string)"
+          .format(unique_database, table_name, column_names[0], 
column_names[1]),
+          OWNER_USER, True)
+      self._run_query_as_user("create function {0}.{1} "
+          "location '{2}{3}' symbol='org.apache.impala.TestUdf'"
+          .format(unique_database, udf_name, os.getenv("FILESYSTEM_PREFIX"), 
udf_uri),
+          OWNER_USER, True)
+
+      for data in test_data:
+        grantee_type = data[0]
+        grantee = data[1]
+        for privilege in privileges:
+          # Case 1: when the resource is a database.
+          self._test_grant_revoke_by_owner_on_database(privilege, 
unique_database,
+              grantee_type, grantee, resource_owner_role)
+
+          # Case 2: when the resource is a table.
+          self._test_grant_revoke_by_owner_on_table(privilege, unique_database,
+              table_name, grantee_type, grantee, resource_owner_role)
+
+          # Case 3: when the resource is a column.
+          self._test_grant_revoke_by_owner_on_column(privilege, column_names,
+              unique_database, table_name, grantee_type, grantee, 
resource_owner_role)
+
+          # Case 4: when the resource is a UDF.
+          self._test_grant_revoke_by_owner_on_udf(privilege, unique_database, 
udf_name,
+              grantee_type, grantee)
+    finally:
+      admin_client.execute("drop database if exists {0} 
cascade".format(unique_database),
+          user=ADMIN)
+      admin_client.execute("drop role {}".format(grantee_role), user=ADMIN)
+      admin_client.execute("drop role {}".format(resource_owner_role), 
user=ADMIN)
+      admin_client.execute("revoke create on server from user 
{0}".format(OWNER_USER),
+          user=ADMIN)
+      admin_client.execute("revoke all on uri '{0}{1}' from user {2}"
+          .format(os.getenv("FILESYSTEM_PREFIX"), udf_uri, OWNER_USER), 
user=ADMIN)
+
+  def _test_grant_revoke_by_owner_on_database(self, privilege, unique_database,
+      grantee_type, grantee, resource_owner_role):
+    grant_database_stmt = "grant {0} on database {1} to {2} {3}"
+    revoke_database_stmt = "revoke {0} on database {1} from {2} {3}"
+    show_grant_database_stmt = "show grant {0} {1} on database {2}"
+    set_database_owner_user_stmt = "alter database {0} set owner user {1}"
+    set_database_owner_group_stmt = "alter database {0} set owner group {1}"
+    set_database_owner_role_stmt = "alter database {0} set owner role {1}"
+    resource_owner_group = OWNER_USER
+    admin_client = self.create_impala_client()
+
+    try:
+      self._run_query_as_user(grant_database_stmt
+          .format(privilege, unique_database, grantee_type, grantee), 
OWNER_USER,
+          True)
+      result = admin_client.execute(show_grant_database_stmt
+          .format(grantee_type, grantee, unique_database), user=ADMIN)
+      TestRanger._check_privileges(result, [
+          [grantee_type, grantee, unique_database, "", "", "", "", "", "*",
+           privilege, "false"],
+          [grantee_type, grantee, unique_database, "*", "*", "", "", "", "",
+           privilege, "false"]])
+
+      self._run_query_as_user(revoke_database_stmt
+          .format(privilege, unique_database, grantee_type, grantee), 
OWNER_USER,
+          True)
+      result = admin_client.execute(show_grant_database_stmt
+          .format(grantee_type, grantee, unique_database), user=ADMIN)
+      TestRanger._check_privileges(result, [])
+
+      # Set the owner of the database to a group that has the same name as 
'OWNER_USER'
+      # and verify that the user 'OWNER_USER' is not able to grant or revoke a 
privilege
+      # on the database.
+      # We run the statement in Hive because currently
+      # ALTER DATABASE SET OWNER GROUP is not supported in Impala. Note that we
+      # need to force Impala to reload the metadata of database since the 
change
+      # is made through Hive.
+      self.run_stmt_in_hive(set_database_owner_group_stmt
+          .format(unique_database, resource_owner_group))
+      admin_client.execute("invalidate metadata", user=ADMIN)
+
+      result = self._run_query_as_user(grant_database_stmt
+          .format(privilege, unique_database, grantee_type, grantee), 
OWNER_USER,
+          False)
+      assert ERROR_GRANT in str(result)
+      result = self._run_query_as_user(revoke_database_stmt
+          .format(privilege, unique_database, grantee_type, grantee), 
OWNER_USER,
+          False)
+      assert ERROR_REVOKE in str(result)
+
+      # Set the owner of the database to a role that has the same name as 
'OWNER_USER'
+      # and verify that the user 'OWNER_USER' is not able to grant or revoke a
+      # privilege on the database.
+      admin_client.execute(set_database_owner_role_stmt
+          .format(unique_database, resource_owner_role), user=ADMIN)
+
+      result = self._run_query_as_user(grant_database_stmt
+          .format(privilege, unique_database, grantee_type, grantee), 
OWNER_USER,
+          False)
+      assert ERROR_GRANT in str(result)
+      result = self._run_query_as_user(revoke_database_stmt
+          .format(privilege, unique_database, grantee_type, grantee), 
OWNER_USER,
+          False)
+      assert ERROR_REVOKE in str(result)
+      # Change the database owner back to the user 'OWNER_USER'.
+      admin_client.execute(set_database_owner_user_stmt
+          .format(unique_database, OWNER_USER), user=ADMIN)
+    finally:
+      # Revoke the privilege that was granted by 'OWNER_USER' in case any of 
the
+      # REVOKE statements submitted by 'owner_user' failed to prevent this test
+      # from interfering with other tests.
+      admin_client.execute(revoke_database_stmt
+          .format(privilege, unique_database, grantee_type, grantee), 
user=ADMIN)
+
+  def _test_grant_revoke_by_owner_on_table(self, privilege, unique_database, 
table_name,
+      grantee_type, grantee, resource_owner_role):
+    # The CREATE privilege on a table is not supported.
+    if privilege == "create":
+      return
+    grant_table_stmt = "grant {0} on table {1}.{2} to {3} {4}"
+    revoke_table_stmt = "revoke {0} on table {1}.{2} from {3} {4}"
+    show_grant_table_stmt = "show grant {0} {1} on table {2}.{3}"
+    resource_owner_group = OWNER_USER
+    admin_client = self.create_impala_client()
+    set_table_owner_user_stmt = "alter table {0}.{1} set owner user {2}"
+    set_table_owner_group_stmt = "alter table {0}.{1} set owner group {2}"
+    set_table_owner_role_stmt = "alter table {0}.{1} set owner role {2}"
+
+    try:
+      self._run_query_as_user(grant_table_stmt
+          .format(privilege, unique_database, table_name, grantee_type, 
grantee),
+          OWNER_USER, True)
+      result = admin_client.execute(show_grant_table_stmt
+          .format(grantee_type, grantee, unique_database, table_name),
+          user=ADMIN)
+      TestRanger._check_privileges(result, [
+          [grantee_type, grantee, unique_database, table_name, "*", "", "", "",
+          "", privilege, "false"]])
+
+      self._run_query_as_user(revoke_table_stmt
+          .format(privilege, unique_database, table_name, grantee_type, 
grantee),
+          OWNER_USER, True)
+      result = admin_client.execute(show_grant_table_stmt
+          .format(grantee_type, grantee, unique_database, table_name),
+          user=ADMIN)
+      TestRanger._check_privileges(result, [])
+
+      # Set the owner of the table to a group that has the same name as
+      # 'OWNER_USER' and verify that the user 'OWNER_USER' is not able to 
grant or
+      # revoke a privilege on the table.
+      # We run the statement in Hive because currently
+      # ALTER TABLE SET OWNER GROUP is not supported in Impala. Note that we
+      # need to force Impala to reload the metadata of table since the change
+      # is made through Hive.
+      self.run_stmt_in_hive(set_table_owner_group_stmt
+          .format(unique_database, table_name, resource_owner_group))
+      admin_client.execute("refresh {0}.{1}".format(unique_database, 
table_name),
+          user=ADMIN)
+
+      result = self._run_query_as_user(grant_table_stmt
+          .format(privilege, unique_database, table_name, grantee_type, 
grantee),
+          OWNER_USER, False)
+      assert ERROR_GRANT in str(result)
+      result = self._run_query_as_user(revoke_table_stmt
+          .format(privilege, unique_database, table_name, grantee_type, 
grantee),
+          OWNER_USER, False)
+      assert ERROR_REVOKE in str(result)
+
+      # Set the owner of the table to a role that has the same name as
+      # 'OWNER_USER' and verify that the user 'OWNER_USER' is not able to 
grant or
+      # revoke a privilege on the table.
+      admin_client.execute(set_table_owner_role_stmt
+          .format(unique_database, table_name, resource_owner_role), 
user=ADMIN)
+
+      result = self._run_query_as_user(grant_table_stmt
+          .format(privilege, unique_database, table_name, grantee_type, 
grantee),
+          OWNER_USER, False)
+      assert ERROR_GRANT in str(result)
+      result = self._run_query_as_user(revoke_table_stmt
+          .format(privilege, unique_database, table_name, grantee_type, 
grantee),
+          OWNER_USER, False)
+      assert ERROR_REVOKE in str(result)
+      # Change the table owner back to the user 'OWNER_USER'.
+      admin_client.execute(set_table_owner_user_stmt
+          .format(unique_database, table_name, OWNER_USER), user=ADMIN)
+    finally:
+      # Revoke the privilege that was granted by 'OWNER_USER' in case any of 
the
+      # REVOKE statements submitted by 'OWNER_USER' failed to prevent this test
+      # from interfering with other tests.
+      admin_client.execute(revoke_table_stmt
+          .format(privilege, unique_database, table_name, grantee_type, 
grantee),
+          user=ADMIN)
+
+  def _test_grant_revoke_by_owner_on_column(self, privilege, column_names,
+      unique_database, table_name, grantee_type, grantee, resource_owner_role):
+    # For a column, only the SELECT privilege is allowed.
+    if privilege != "select":
+      return
+    grant_column_stmt = "grant {0} ({1}) on table {2}.{3} to {4} {5}"
+    revoke_column_stmt = "revoke {0} ({1}) on table {2}.{3} from {4} {5}"
+    show_grant_column_stmt = "show grant {0} {1} on column {2}.{3}.{4}"
+    resource_owner_group = OWNER_USER
+    admin_client = self.create_impala_client()
+    set_table_owner_user_stmt = "alter table {0}.{1} set owner user {2}"
+    set_table_owner_group_stmt = "alter table {0}.{1} set owner group {2}"
+    set_table_owner_role_stmt = "alter table {0}.{1} set owner role {2}"
+
+    try:
+      self._run_query_as_user(grant_column_stmt
+          .format(privilege, column_names[0], unique_database, table_name,
+          grantee_type, grantee), OWNER_USER, True)
+      result = admin_client.execute(show_grant_column_stmt
+          .format(grantee_type, grantee, unique_database, table_name,
+          column_names[0]), user=ADMIN)
+      TestRanger._check_privileges(result, [
+          [grantee_type, grantee, unique_database, table_name, column_names[0],
+          "", "", "", "", privilege, "false"]])
+
+      self._run_query_as_user(revoke_column_stmt
+          .format(privilege, column_names[0], unique_database, table_name,
+          grantee_type, grantee), OWNER_USER, True)
+      result = admin_client.execute(show_grant_column_stmt
+          .format(grantee_type, grantee, unique_database, table_name,
+          column_names[0]), user=ADMIN)
+      TestRanger._check_privileges(result, [])
+
+      # Set the owner of the table to a group that has the same name as 
'OWNER_USER'
+      # and verify that the user 'OWNER_USER' is not able to grant or revoke a
+      # privilege on a column in the table.
+      self.run_stmt_in_hive(set_table_owner_group_stmt
+          .format(unique_database, table_name, resource_owner_group))
+      admin_client.execute("refresh {0}.{1}".format(unique_database, 
table_name),
+          user=ADMIN)
+
+      result = self._run_query_as_user(grant_column_stmt
+          .format(privilege, column_names[0], unique_database, table_name,
+          grantee_type, grantee), OWNER_USER, False)
+      assert ERROR_GRANT in str(result)
+      result = self._run_query_as_user(revoke_column_stmt
+          .format(privilege, column_names[0], unique_database, table_name,
+          grantee_type, grantee), OWNER_USER, False)
+      assert ERROR_REVOKE in str(result)
+
+      # Set the owner of the table to a role that has the same name as 
'OWNER_USER' and
+      # verify that the user 'OWNER_USER' is not able to grant or revoke a 
privilege on
+      # a column in the table.
+      admin_client.execute(set_table_owner_role_stmt
+          .format(unique_database, table_name, resource_owner_role), 
user=ADMIN)
+
+      result = self._run_query_as_user(grant_column_stmt
+          .format(privilege, column_names[0], unique_database, table_name,
+          grantee_type, grantee), OWNER_USER, False)
+      assert ERROR_GRANT in str(result)
+      result = self._run_query_as_user(revoke_column_stmt
+          .format(privilege, column_names[0], unique_database, table_name,
+          grantee_type, grantee), OWNER_USER, False)
+      assert ERROR_REVOKE in str(result)
+      # Change the table owner back to the user 'owner_user'.
+      admin_client.execute(set_table_owner_user_stmt
+          .format(unique_database, table_name, OWNER_USER), user=ADMIN)
+    finally:
+      # Revoke the privilege that was granted by 'OWNER_USER' in case any of 
the
+      # REVOKE statements submitted by 'OWNER_USER' failed to prevent this test
+      # from interfering with other tests.
+      admin_client.execute(revoke_column_stmt
+          .format(privilege, column_names[0], unique_database, table_name,
+          grantee_type, grantee), user=ADMIN)
+
+  def _test_grant_revoke_by_owner_on_udf(self, privilege, unique_database, 
udf_name,
+      grantee_type, grantee):
+    # Due to IMPALA-11743 and IMPALA-12685, the owner of a UDF could not grant
+    # or revoke the SELECT privilege.
+    result = self._run_query_as_user("grant {0} on user_defined_fn "
+        "{1}.{2} to {3} {4}".format(privilege, unique_database, udf_name,
+        grantee_type, grantee), OWNER_USER, False)
+    assert ERROR_GRANT in str(result)
+    result = self._run_query_as_user("revoke {0} on user_defined_fn "
+        "{1}.{2} from {3} {4}".format(privilege, unique_database, udf_name,
+        grantee_type, grantee), OWNER_USER, False)
+    assert ERROR_REVOKE in str(result)
+
   @pytest.mark.execute_serially
   @CustomClusterTestSuite.with_args(
     impalad_args=IMPALAD_ARGS, catalogd_args=CATALOGD_ARGS)


Reply via email to