This is an automated email from the ASF dual-hosted git repository. michaelsmith pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/impala.git
commit 4cbc295c48de1b369a78ec266137dfc3b363e7ec Author: Fang-Yu Rao <[email protected]> AuthorDate: Wed Nov 2 11:49:53 2022 -0700 IMPALA-10986: Require the SELECT privilege to execute a UDF This patch registers the SELECT privilege on a UDF during query analysis for a SELECT query that attempts to execute the UDF. Only the required privileges for UDF execution are changed. The required privileges for queries like CREATE/DROP FUNCTION or SHOW FUNCTIONS remain the same. More precisely, before this patch, to execute a UDF, a user only had to be granted any of the SELECT, INSERT, REFRESH privileges on all the tables, columns in the database where the UDF belongs to. After this patch, the user has to be granted the SELECT privilege on the UDF as well to execute the UDF. Note that currently in Hive, only the SELECT privilege on the UDF is required when Ranger is the authorization provider. IMPALA-11769 was also created to keep track of the difference in the required privileges between Impala and Hive. To facilitate managing privileges on UDF's, this patch also adds the keyword 'USER_DEFINED_FN'. For instance, we could use the following statement to grant the SELECT privilege on the UDF <udf_name> under the database <db_name> to the user <user_name>. GRANT SELECT ON USER_DEFINED_FN <db_name>.<udf_name> TO USER <user_name> A wildcard in the name of a UDF is also supported. If an administrator wants to grant to a user only the SELECT privilege on all the tables, columns but not all the UDF's in a database, after executing "GRANT SELECT ON DATABASE <db_name> TO USER <user_name>", the following could be done to revoke the SELECT privilege on all the UDF's in the database <db_name> from the user <user_name>. REVOKE SELECT ON USER_DEFINED_FN <db_name>.`*` FROM USER <user_name> Testing: - Added various FE and E2E tests to verify Impala's behavior with respect to UDF execution. - Verified that this patch passes the core tests in the DEBUG build. Change-Id: I5e58ba30545ce169786aac279b00c8f6e09ae740 Reviewed-on: http://gerrit.cloudera.org:8080/19194 Reviewed-by: Impala Public Jenkins <[email protected]> Tested-by: Impala Public Jenkins <[email protected]> --- common/thrift/CatalogObjects.thrift | 6 +- fe/src/main/cup/sql-parser.cup | 12 +- .../java/org/apache/impala/analysis/Analyzer.java | 4 + .../apache/impala/analysis/FunctionCallExpr.java | 3 + .../org/apache/impala/analysis/FunctionName.java | 11 +- .../org/apache/impala/analysis/PrivilegeSpec.java | 60 ++++- .../ranger/RangerCatalogdAuthorizationManager.java | 2 + .../ranger/RangerImpaladAuthorizationManager.java | 15 +- .../impala/authorization/ranger/RangerUtil.java | 2 +- fe/src/main/jflex/sql-scanner.flex | 1 + .../java/org/apache/impala/analysis/ToSqlTest.java | 20 ++ .../authorization/AuthorizationStmtTest.java | 71 +++++- .../authorization/AuthorizationTestBase.java | 21 ++ tests/authorization/test_ranger.py | 277 ++++++++++++++++++++- 14 files changed, 467 insertions(+), 38 deletions(-) diff --git a/common/thrift/CatalogObjects.thrift b/common/thrift/CatalogObjects.thrift index 0df4be794..5ba8c4edb 100644 --- a/common/thrift/CatalogObjects.thrift +++ b/common/thrift/CatalogObjects.thrift @@ -734,6 +734,7 @@ enum TPrivilegeScope { COLUMN = 4 STORAGE_TYPE = 5 STORAGEHANDLER_URI = 6 + USER_DEFINED_FN = 7 } // The privilege level allowed. @@ -783,7 +784,7 @@ struct TPrivilege { // Set if scope is SERVER, URI, DATABASE, or TABLE 7: optional string server_name - // Set if scope is DATABASE or TABLE + // Set if scope is DATABASE or TABLE or USER_DEFINED_FN 8: optional string db_name // Unqualified table name. Set if scope is TABLE. @@ -801,6 +802,9 @@ struct TPrivilege { 13: optional string storage_type 14: optional string storage_url + + // Set if scope is USER_DEFINED_FN + 15: optional string fn_name } // Thrift representation of an HdfsCachePool. diff --git a/fe/src/main/cup/sql-parser.cup b/fe/src/main/cup/sql-parser.cup index 32ca8ccbf..d0ea00956 100755 --- a/fe/src/main/cup/sql-parser.cup +++ b/fe/src/main/cup/sql-parser.cup @@ -329,7 +329,7 @@ terminal KW_STRING, KW_STRUCT, KW_SYMBOL, KW_SYSTEM_TIME, KW_SYSTEM_VERSION, KW_TABLE, KW_TABLES, KW_TABLESAMPLE, KW_TBLPROPERTIES, KW_TERMINATED, KW_TEXTFILE, KW_THEN, KW_TIMESTAMP, KW_TINYINT, KW_TRUNCATE, KW_STATS, - KW_TO, KW_TRUE, KW_UNBOUNDED, KW_UNCACHED, KW_UNION, KW_UNKNOWN, KW_UNNEST, KW_UNSET, + KW_TO, KW_TRUE, KW_UDF, KW_UNBOUNDED, KW_UNCACHED, KW_UNION, KW_UNKNOWN, KW_UNNEST, KW_UNSET, KW_UPDATE, KW_UPDATE_FN, KW_UPSERT, KW_USE, KW_USING, KW_VALIDATE, KW_VALUES, KW_VARCHAR, KW_VIEW, KW_WHEN, KW_WHERE, KW_WITH, KW_ZORDER; @@ -1046,6 +1046,12 @@ show_grant_principal_stmt ::= PrivilegeSpec.createStorageHandlerUriScopedPriv(TPrivilegeLevel.RWSTORAGE, new StorageHandlerUri(storage_handler_uri))); :} + | KW_SHOW KW_GRANT principal_type:type ident_or_default:name KW_ON KW_UDF + function_name:fn_name + {: + RESULT = new ShowGrantPrincipalStmt(name, type, + PrivilegeSpec.createUdfScopedPriv(TPrivilegeLevel.ALL, fn_name)); + :} ; create_drop_role_stmt ::= @@ -1114,6 +1120,8 @@ privilege_spec ::= {: RESULT = PrivilegeSpec.createDbScopedPriv(priv, db_name); :} | privilege:priv KW_ON KW_TABLE table_name:tbl_name {: RESULT = PrivilegeSpec.createTableScopedPriv(priv, tbl_name); :} + | privilege:priv KW_ON KW_UDF function_name:fn_name + {: RESULT = PrivilegeSpec.createUdfScopedPriv(priv, fn_name); :} | privilege:priv LPAREN opt_ident_list:cols RPAREN KW_ON KW_TABLE table_name:tbl_name {: RESULT = PrivilegeSpec.createColumnScopedPriv(priv, tbl_name, cols); :} | privilege:priv KW_ON uri_ident:uri_kw STRING_LITERAL:uri @@ -4498,6 +4506,8 @@ word ::= {: RESULT = r.toString(); :} | KW_TRUE:r {: RESULT = r.toString(); :} + | KW_UDF:r + {: RESULT = r.toString(); :} | KW_UNBOUNDED:r {: RESULT = r.toString(); :} | KW_UNCACHED:r 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 7d8d3a3ba..9328b5172 100644 --- a/fe/src/main/java/org/apache/impala/analysis/Analyzer.java +++ b/fe/src/main/java/org/apache/impala/analysis/Analyzer.java @@ -3541,6 +3541,10 @@ public class Analyzer { return tableName.isFullyQualified() ? tableName.getDb() : getDefaultDb(); } + public String getTargetDbName(FunctionName functionName) { + return functionName.isFullyQualified() ? functionName.getDb() : getDefaultDb(); + } + /** * Returns the fully-qualified table name of tableName. If tableName * is already fully qualified, returns tableName. diff --git a/fe/src/main/java/org/apache/impala/analysis/FunctionCallExpr.java b/fe/src/main/java/org/apache/impala/analysis/FunctionCallExpr.java index 3ffe61a51..90dde1450 100644 --- a/fe/src/main/java/org/apache/impala/analysis/FunctionCallExpr.java +++ b/fe/src/main/java/org/apache/impala/analysis/FunctionCallExpr.java @@ -572,6 +572,9 @@ public class FunctionCallExpr extends Expr { profile.appendInfoString(udfInfoStringKey, functionName); } } + analyzer.registerPrivReq(builder -> + builder.allOf(Privilege.SELECT) + .onFunction(fnName_.getDb(), fnName_.getFunction()).build()); } if (isMergeAggFn()) { diff --git a/fe/src/main/java/org/apache/impala/analysis/FunctionName.java b/fe/src/main/java/org/apache/impala/analysis/FunctionName.java index 7cb259c88..bc995392c 100644 --- a/fe/src/main/java/org/apache/impala/analysis/FunctionName.java +++ b/fe/src/main/java/org/apache/impala/analysis/FunctionName.java @@ -75,7 +75,9 @@ public class FunctionName { } public String getDb() { return db_; } + public void setDb(String db) { db_ = db; } public String getFunction() { return fn_; } + public void setFunction(String fn) { fn_ = fn; } public boolean isFullyQualified() { return db_ != null; } public boolean isBuiltin() { return isBuiltin_; } public List<String> getFnNamePath() { return fnNamePath_; } @@ -107,7 +109,8 @@ public class FunctionName { * database. * - Else, set the database name to the current session DB name. * - When preferBuiltinsDb is false: set the database name to current session DB name. - * Only for CREATE/DROP FUNCTION statements, preferBuiltinsDb is false. + * For CREATE/DROP FUNCTION and GRANT/REVOKE <privilege> ON USER_DEFINED_FN + * statements, preferBuiltinsDb is false. */ public void analyze(Analyzer analyzer, boolean preferBuiltinsDb) throws AnalysisException { @@ -148,8 +151,10 @@ public class FunctionName { return false; } // Execute a UDF of the fallback database in a SELECT statement, the requesting user - // has be to granted any one of the INSERT, REFRESH, SELECT privileges on the - // fallback database. + // has be to granted a) the SELECT privilege on the UDF, and b) any one of the + // INSERT, REFRESH, SELECT privileges on the fallback database. + analyzer.registerPrivReq(builder -> + builder.allOf(Privilege.SELECT).onFunction(dbName, getFunction()).build()); FeDb feDb = analyzer.getDb(dbName, Privilege.VIEW_METADATA, false); return feDb != null && feDb.containsFunction(fn_); } 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 3910cf69a..5fda09710 100644 --- a/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java +++ b/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java @@ -24,8 +24,10 @@ import java.util.List; import org.apache.impala.authorization.Privilege; import org.apache.impala.catalog.FeDataSourceTable; +import org.apache.impala.catalog.FeDb; import org.apache.impala.catalog.FeTable; import org.apache.impala.catalog.FeView; +import org.apache.impala.catalog.Function; import org.apache.impala.catalog.TableLoadingException; import org.apache.impala.common.AnalysisException; import org.apache.impala.thrift.TPrivilege; @@ -49,6 +51,7 @@ public class PrivilegeSpec extends StmtNode { private final String storageType_; private final String storageUri_; private final List<String> columnNames_; + private final FunctionName functionName_; // Set/modified during analysis private String dbName_; @@ -56,7 +59,8 @@ public class PrivilegeSpec extends StmtNode { private PrivilegeSpec(TPrivilegeLevel privilegeLevel, TPrivilegeScope scope, String serverName, String dbName, TableName tableName, HdfsUri uri, - String storageType, String storageUri, List<String> columnNames) { + String storageType, String storageUri, List<String> columnNames, + FunctionName functionName) { Preconditions.checkNotNull(scope); Preconditions.checkNotNull(privilegeLevel); privilegeLevel_ = privilegeLevel; @@ -68,6 +72,7 @@ public class PrivilegeSpec extends StmtNode { storageType_ = storageType; storageUri_ = storageUri; columnNames_ = columnNames; + functionName_= functionName; } public static PrivilegeSpec createServerScopedPriv(TPrivilegeLevel privilegeLevel) { @@ -77,21 +82,28 @@ public class PrivilegeSpec extends StmtNode { public static PrivilegeSpec createServerScopedPriv(TPrivilegeLevel privilegeLevel, String serverName) { return new PrivilegeSpec(privilegeLevel, TPrivilegeScope.SERVER, serverName, null, - null, null, null, null, null); + null, null, null, null, null, null); } public static PrivilegeSpec createDbScopedPriv(TPrivilegeLevel privilegeLevel, String dbName) { Preconditions.checkNotNull(dbName); return new PrivilegeSpec(privilegeLevel, TPrivilegeScope.DATABASE, null, dbName, - null, null, null, null, null); + null, null, null, null, null, null); } public static PrivilegeSpec createTableScopedPriv(TPrivilegeLevel privilegeLevel, TableName tableName) { Preconditions.checkNotNull(tableName); return new PrivilegeSpec(privilegeLevel, TPrivilegeScope.TABLE, null, null, - tableName, null, null, null, null); + tableName, null, null, null, null, null); + } + + public static PrivilegeSpec createUdfScopedPriv(TPrivilegeLevel privilegeLevel, + FunctionName functionName) { + Preconditions.checkNotNull(functionName); + return new PrivilegeSpec(privilegeLevel, TPrivilegeScope.USER_DEFINED_FN, null, null, + null, null, null, null, null, functionName); } public static PrivilegeSpec createColumnScopedPriv(TPrivilegeLevel privilegeLevel, @@ -99,14 +111,14 @@ public class PrivilegeSpec extends StmtNode { Preconditions.checkNotNull(tableName); Preconditions.checkNotNull(columnNames); return new PrivilegeSpec(privilegeLevel, TPrivilegeScope.COLUMN, null, null, - tableName, null, null, null, columnNames); + tableName, null, null, null, columnNames, null); } public static PrivilegeSpec createUriScopedPriv(TPrivilegeLevel privilegeLevel, HdfsUri uri) { Preconditions.checkNotNull(uri); return new PrivilegeSpec(privilegeLevel, TPrivilegeScope.URI, null, null, null, uri, - null, null, null); + null, null, null, null); } public static PrivilegeSpec createStorageHandlerUriScopedPriv( @@ -114,7 +126,7 @@ public class PrivilegeSpec extends StmtNode { Preconditions.checkNotNull(storageHandlerUri); return new PrivilegeSpec(privilegeLevel, TPrivilegeScope.STORAGEHANDLER_URI, null, null, null, null, storageHandlerUri.getStorageType(), - storageHandlerUri.getStoreUrl(), null); + storageHandlerUri.getStoreUrl(), null, null); } public List<TPrivilege> toThrift() { @@ -134,6 +146,10 @@ public class PrivilegeSpec extends StmtNode { * Helper function to construct a TPrivilege from this privilege spec. If the scope is * COLUMN, 'columnName' must be a non-null column name. Otherwise, 'columnName' is * null. + * + * Recall that for a GRANT/REVOKE statement, Frontend#createCatalogOpRequest() is + * called to create a TCatalogOpRequest which will be sent from the coordinator to the + * catalog daemon and that TPrivilege is one of the objects that has to be set up. */ private TPrivilege createTPrivilege(String columnName) { Preconditions.checkState(columnName == null ^ scope_ == TPrivilegeScope.COLUMN); @@ -148,6 +164,7 @@ public class PrivilegeSpec extends StmtNode { if (storageType_ != null) privilege.setStorage_type(storageType_); if (storageUri_ != null) privilege.setStorage_url(storageUri_); if (columnName != null) privilege.setColumn_name(columnName); + if (functionName_ != null) privilege.setFn_name(functionName_.getFunction()); privilege.setCreate_time_ms(-1); return privilege; } @@ -181,6 +198,8 @@ public class PrivilegeSpec extends StmtNode { sb.append(" " + dbName_); } else if (scope_ == TPrivilegeScope.TABLE) { sb.append(" " + tableName_.toString()); + } else if (scope_ == TPrivilegeScope.USER_DEFINED_FN) { + sb.append(" " + functionName_.toString()); } else if (scope_ == TPrivilegeScope.COLUMN) { sb.append(" ("); sb.append(Joiner.on(",").join(columnNames_)); @@ -242,6 +261,9 @@ public class PrivilegeSpec extends StmtNode { case COLUMN: analyzeColumnPrivScope(analyzer); break; + case USER_DEFINED_FN: + analyzeUdf(analyzer); + break; default: throw new IllegalStateException("Unknown TPrivilegeScope in privilege spec: " + scope_.toString()); @@ -314,6 +336,30 @@ public class PrivilegeSpec extends StmtNode { return table; } + private void analyzeUdf(Analyzer analyzer) throws AnalysisException { + Preconditions.checkState(scope_ == TPrivilegeScope.USER_DEFINED_FN); + + // Wildcard function names in the forms of `*`.`*`, <db_name>.`*`, or `*`.<fn_name> + // are supported. + List<String> path = functionName_.getFnNamePath(); + if (path.size() == 2 && + (path.get(0).equals("*") || path.get(1).equals("*"))) { + dbName_ = path.get(0); + functionName_.setDb(path.get(0)); + functionName_.setFunction(path.get(1)); + return; + } + + // Call analyze() to perform path resolution so that 'db_' and 'fn_' of + // 'functionName_' will be set up. + functionName_.analyze(analyzer, /* preferBuiltinsDb */ false); + Preconditions.checkArgument(!functionName_.getDb().equals("*")); + Preconditions.checkArgument(!functionName_.getFunction().equals("*")); + // Need to set up 'dbName_', which in turn is used to set up 'db_name' of the + // TPrivilege in createTPrivilege(). + dbName_ = analyzer.getTargetDbName(functionName_); + } + public TPrivilegeScope getScope() { return scope_; } public TableName getTableName() { return tableName_; } 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 2597ce4a6..22adcad1f 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 @@ -396,6 +396,8 @@ public class RangerCatalogdAuthorizationManager implements AuthorizationManager requests.add(createRequest.apply(RangerUtil.createColumnResource(p))); } else if (p.getUri() != null) { requests.add(createRequest.apply(RangerUtil.createUriResource(p))); + } else if (p.getFn_name() != null) { + requests.add(createRequest.apply(RangerUtil.createFunctionResource(p))); } else if (p.getDb_name() != null) { // DB is used by column and function resources. requests.add(createRequest.apply(RangerUtil.createColumnResource(p))); diff --git a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerImpaladAuthorizationManager.java b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerImpaladAuthorizationManager.java index 216d81a21..beab8a3f5 100644 --- a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerImpaladAuthorizationManager.java +++ b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerImpaladAuthorizationManager.java @@ -294,8 +294,8 @@ public class RangerImpaladAuthorizationManager implements AuthorizationManager { Optional<String> storageUri = getResourceName( RangerImpalaResourceBuilder.STORAGE_URL, privilege.getStorage_url(), accessResult); - Optional<String> udf = getResourceName(RangerImpalaResourceBuilder.UDF, ANY, - accessResult); + Optional<String> udf = getResourceName(RangerImpalaResourceBuilder.UDF, + privilege.getFn_name(), accessResult); return new RangerResultRow(type, principal, database.orElse(""), table.orElse(""), column.orElse(""), uri.orElse(""), storageType.orElse(""), storageUri.orElse(""), @@ -314,6 +314,8 @@ public class RangerImpaladAuthorizationManager implements AuthorizationManager { resources.add(RangerUtil.createColumnResource(privilege)); } else if (privilege.getUri() != null) { resources.add(RangerUtil.createUriResource(privilege)); + } else if (privilege.getFn_name() != null) { + resources.add(RangerUtil.createFunctionResource(privilege)); } else if (privilege.getDb_name() != null) { // DB is used by column and function resources. resources.add(RangerUtil.createColumnResource(privilege)); @@ -398,6 +400,8 @@ public class RangerImpaladAuthorizationManager implements AuthorizationManager { resourceResult.addColumnResult(row); } else if (!row.table_.equals("*") && !row.table_.isEmpty()) { resourceResult.addTableResult(row); + } else if (!row.udf_.equals("*") && !row.udf_.isEmpty()) { + resourceResult.addUdfResult(row); } else if (!row.database_.equals("*") && !row.database_.isEmpty()) { resourceResult.addDatabaseResult(row); } else { @@ -436,6 +440,7 @@ public class RangerImpaladAuthorizationManager implements AuthorizationManager { private List<RangerResultRow> database = new ArrayList<>(); private List<RangerResultRow> table = new ArrayList<>(); private List<RangerResultRow> column = new ArrayList<>(); + private List<RangerResultRow> udf = new ArrayList<>(); public RangerResourceResult() { } @@ -459,6 +464,11 @@ public class RangerImpaladAuthorizationManager implements AuthorizationManager { return this; } + public RangerResourceResult addUdfResult(RangerResultRow result) { + udf.add(result); + return this; + } + /** * For each disjoint List corresponding to a given resource, if there exists a * RangerResultRow indicating the specified principal's privilege of @@ -472,6 +482,7 @@ public class RangerImpaladAuthorizationManager implements AuthorizationManager { results.addAll(filterIfAll(database)); results.addAll(filterIfAll(table)); results.addAll(filterIfAll(column)); + results.addAll(filterIfAll(udf)); return results; } diff --git a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerUtil.java b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerUtil.java index 36004a1ac..a7bf26d50 100644 --- a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerUtil.java +++ b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerUtil.java @@ -67,7 +67,7 @@ public class RangerUtil { Map<String, String> resource = new HashMap<>(); resource.put(RangerImpalaResourceBuilder.DATABASE, getOrAll(privilege.getDb_name())); - resource.put(RangerImpalaResourceBuilder.UDF, "*"); + resource.put(RangerImpalaResourceBuilder.UDF, getOrAll(privilege.getFn_name())); return resource; } diff --git a/fe/src/main/jflex/sql-scanner.flex b/fe/src/main/jflex/sql-scanner.flex index 4acb7a136..8619cf324 100644 --- a/fe/src/main/jflex/sql-scanner.flex +++ b/fe/src/main/jflex/sql-scanner.flex @@ -270,6 +270,7 @@ import org.apache.impala.thrift.TReservedWordsVersion; keywordMap.put("to", SqlParserSymbols.KW_TO); keywordMap.put("true", SqlParserSymbols.KW_TRUE); keywordMap.put("truncate", SqlParserSymbols.KW_TRUNCATE); + keywordMap.put("user_defined_fn", SqlParserSymbols.KW_UDF); keywordMap.put("unbounded", SqlParserSymbols.KW_UNBOUNDED); keywordMap.put("uncached", SqlParserSymbols.KW_UNCACHED); keywordMap.put("union", SqlParserSymbols.KW_UNION); diff --git a/fe/src/test/java/org/apache/impala/analysis/ToSqlTest.java b/fe/src/test/java/org/apache/impala/analysis/ToSqlTest.java index 0c053ce68..3acbf93e8 100644 --- a/fe/src/test/java/org/apache/impala/analysis/ToSqlTest.java +++ b/fe/src/test/java/org/apache/impala/analysis/ToSqlTest.java @@ -1755,6 +1755,26 @@ public class ToSqlTest extends FrontendTestBase { pt, testRole)); } + privileges = Arrays.stream(Privilege.values()) + .filter(p -> p != Privilege.OWNER && + (p == Privilege.CREATE || + p == Privilege.DROP || + p == Privilege.SELECT)) + .collect(Collectors.toList()); + + for (Privilege p : privileges) { + testToSql(ctx, String.format("GRANT %s ON USER_DEFINED_FN " + + "functional.identity TO %s %s", p, pt, testRole)); + testToSql(ctx, String.format("GRANT %s ON USER_DEFINED_FN " + + "functional.identity TO %s %s WITH GRANT OPTION", p, pt, testRole)); + testToSql(ctx, String.format( + "REVOKE %s ON USER_DEFINED_FN functional.identity FROM %s %s", p, pt, + testRole)); + testToSql(ctx, String.format( + "REVOKE GRANT OPTION FOR %s ON USER_DEFINED_FN functional.identity " + + "FROM %s %s", p, pt, testRole)); + } + // Uri (Only ALL is supported) testToSql(ctx, String.format("GRANT ALL ON URI '%s' TO %s %s", testUri, pt, testRole)); 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 3e898b628..68b4f0db1 100644 --- a/fe/src/test/java/org/apache/impala/authorization/AuthorizationStmtTest.java +++ b/fe/src/test/java/org/apache/impala/authorization/AuthorizationStmtTest.java @@ -553,6 +553,7 @@ public class AuthorizationStmtTest extends AuthorizationTestBase { // Select a UDF. ScalarFunction fn = addFunction("functional", "f"); + ScalarFunction fn2 = addFunction("functional", "f2"); try { authorize("select functional.f()") .ok(onServer(TPrivilegeLevel.ALL)) @@ -561,13 +562,37 @@ public class AuthorizationStmtTest extends AuthorizationTestBase { .ok(onDatabase("functional", TPrivilegeLevel.ALL)) .ok(onDatabase("functional", TPrivilegeLevel.OWNER)) .ok(onDatabase("functional", viewMetadataPrivileges())) - .error(accessError("functional")) - .error(accessError("functional"), onServer(allExcept( + // We do not have to explicitly grant the SELECT privilege on the UDF if we + // already grant the SELECT privilege on the database where the UDF belongs + // in that granting the SELECT privilege on the database also implies granting + // the SELECT privilege on all the UDF's in the database. + .ok(onDatabase("functional", TPrivilegeLevel.SELECT)) + .ok(onUdf("functional", "f", TPrivilegeLevel.SELECT), + onDatabase("functional", TPrivilegeLevel.INSERT)) + .ok(onUdf("functional", "f", TPrivilegeLevel.SELECT), + onDatabase("functional", TPrivilegeLevel.REFRESH)) + .error(selectFunctionError("functional.f")) + .error(selectFunctionError("functional.f"), onServer(allExcept( + viewMetadataPrivileges()))) + .error(selectFunctionError("functional.f"), onDatabase("functional", allExcept( viewMetadataPrivileges()))) - .error(accessError("functional"), onDatabase("functional", allExcept( - viewMetadataPrivileges()))); + .error(accessError("functional"), + onUdf("functional", "f", TPrivilegeLevel.SELECT)) + .error(accessError("functional"), + onUdf("functional", "f", TPrivilegeLevel.SELECT), + onServer(allExcept(viewMetadataPrivileges()))) + .error(accessError("functional"), + onUdf("functional", "f", TPrivilegeLevel.SELECT), + onDatabase("functional", allExcept(viewMetadataPrivileges()))) + .error(selectFunctionError("functional.f"), + onDatabase("functional", TPrivilegeLevel.INSERT), + onUdf("functional", "f2", TPrivilegeLevel.SELECT)) + .error(selectFunctionError("functional.f"), + onDatabase("functional", TPrivilegeLevel.REFRESH), + onUdf("functional", "f2", TPrivilegeLevel.SELECT));; } finally { removeFunction(fn); + removeFunction(fn2); } // Select from non-existent database. @@ -2945,26 +2970,52 @@ public class AuthorizationStmtTest extends AuthorizationTestBase { .ok(onDatabase("functional", TPrivilegeLevel.ALL)) .ok(onDatabase("functional", TPrivilegeLevel.OWNER)) .ok(onDatabase("functional", viewMetadataPrivileges())) - .error(accessError("functional")) - .error(accessError("functional"), onDatabase("functional", - allExcept(viewMetadataPrivileges()))); + .error(selectFunctionError("functional.to_lower")) + .error(selectFunctionError("functional.to_lower"), onDatabase("functional", + allExcept(viewMetadataPrivileges()))) + .error(accessError("functional"), onUdf("functional", "to_lower", + TPrivilegeLevel.SELECT)) + .error(accessError("functional"), + onDatabase("functional", allExcept(viewMetadataPrivileges())), + onUdf("functional", "to_lower", TPrivilegeLevel.SELECT)); } } finally { removeFunction(fn); } - // IMPALA-11728: Make sure use of functions in the fallback database requires SELECT - // (or higher) privilege on the database. + // IMPALA-11728: Make sure use of a UDF in the fallback database requires a) any + // of the INSERT, REFRESH, SELECT privileges on all the tables and columns in the + // database, and b) the SELECT privilege on the UDF in the database. fn = addFunction("functional", "f"); try { TQueryOptions options = new TQueryOptions(); options.setFallback_db_for_functions("functional"); authorize(createAnalysisCtx(options, authzFactory_, user_.getName()), "select f()") + // When the scope of a privilege is database, all the tables, columns, as well + // as UDF's will be covered. A requesting user granted any of the ALL, OWNER, + // or SELECT privileges on the database will be allowed to execute the UDF. .ok(onDatabase("functional", TPrivilegeLevel.ALL)) .ok(onDatabase("functional", TPrivilegeLevel.OWNER)) .ok(onDatabase("functional", viewMetadataPrivileges())) - .error(accessError("functional")) + // We do not have to explicitly grant the SELECT privilege on the UDF if we + // already grant the SELECT privilege on the database where the UDF belongs + // in that granting the SELECT privilege on the database also implies granting + // the SELECT privilege on all the UDF's in the database. + .ok(onDatabase("functional", TPrivilegeLevel.SELECT)) + .ok(onDatabase("functional", TPrivilegeLevel.INSERT), + onUdf("functional", "f", TPrivilegeLevel.SELECT)) + .ok(onDatabase("functional", TPrivilegeLevel.REFRESH), + onUdf("functional", "f", TPrivilegeLevel.SELECT)) + .error(selectFunctionError("functional.f")) + .error(selectFunctionError("functional.f"), + onDatabase("functional", allExcept(viewMetadataPrivileges()))) + .error(accessError("functional"), + onUdf("functional", "f", TPrivilegeLevel.SELECT)) + .error(accessError("functional"), + onUdf("functional", "f", TPrivilegeLevel.SELECT), + onServer(allExcept(viewMetadataPrivileges()))) .error(accessError("functional"), + onUdf("functional", "f", TPrivilegeLevel.SELECT), onDatabase("functional", allExcept(viewMetadataPrivileges()))); } finally { removeFunction(fn); 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 32f237550..e0db29e3a 100644 --- a/fe/src/test/java/org/apache/impala/authorization/AuthorizationTestBase.java +++ b/fe/src/test/java/org/apache/impala/authorization/AuthorizationTestBase.java @@ -510,6 +510,23 @@ public abstract class AuthorizationTestBase extends FrontendTestBase { return privileges; } + protected TPrivilege[] onUdf(String db, String fn, TPrivilegeLevel... levels) { + return onUdf(false, db, fn, levels); + } + + protected TPrivilege[] onUdf(boolean grantOption, String db, String fn, + TPrivilegeLevel... levels) { + TPrivilege[] privileges = new TPrivilege[levels.length]; + for (int i = 0; i < levels.length; i++) { + privileges[i] = new TPrivilege(levels[i], TPrivilegeScope.USER_DEFINED_FN, false); + privileges[i].setServer_name(SERVER_NAME); + privileges[i].setDb_name(db); + privileges[i].setFn_name(fn); + privileges[i].setHas_grant_opt(grantOption); + } + return privileges; + } + private void authzOk(String stmt, WithPrincipal withPrincipal) throws ImpalaException { authzOk(authzCtx_, stmt, withPrincipal, /* expectAnalysisOk */ true); } @@ -699,6 +716,10 @@ public abstract class AuthorizationTestBase extends FrontendTestBase { return "User '%s' does not have privileges to ANY functions in: " + object; } + protected static String selectFunctionError(String object) { + return "User '%s' does not have privileges to SELECT functions in: " + object; + } + protected static String columnMaskError(String object) { return "Column masking is disabled by --enable_column_masking flag. Can't access " + "column " + object + " that has column masking policy."; diff --git a/tests/authorization/test_ranger.py b/tests/authorization/test_ranger.py index 249923f0b..9eda63d6e 100644 --- a/tests/authorization/test_ranger.py +++ b/tests/authorization/test_ranger.py @@ -199,6 +199,7 @@ class TestRanger(CustomClusterTestSuite): admin_client = self.create_impala_client() unique_db = unique_name + "_db" unique_table = unique_name + "_tbl" + udf = "identity" try: # Create test database/table @@ -211,7 +212,7 @@ class TestRanger(CustomClusterTestSuite): for data in test_data: # Test basic show grant functionality for user/group self._test_show_grant_basic(admin_client, data[1], data[0], unique_db, - unique_table) + unique_table, udf) # Test that omitting ON <resource> results in failure self._test_show_grant_without_on(data[1], data[0]) @@ -222,6 +223,9 @@ class TestRanger(CustomClusterTestSuite): # Test ALL privilege hides other privileges self._test_show_grant_mask(admin_client, user) + # Test ALL privilege on UDF hides other privileges + self._test_show_grant_mask_on_udf(admin_client, data[1], data[0], unique_db, udf) + # Test USER inherits privileges for their GROUP self._test_show_grant_user_group(admin_client, user, group, unique_db) finally: @@ -312,7 +316,54 @@ class TestRanger(CustomClusterTestSuite): for privilege in privs_excl_rwstorage: admin_client.execute("revoke {0} on server from user {1}".format(privilege, user)) - def _test_show_grant_basic(self, admin_client, kw, id, unique_database, unique_table): + def _test_show_grant_mask_on_udf(self, admin_client, kw, id, unique_database, udf): + try: + # Grant the CREATE privilege and verify. + admin_client.execute("grant create on user_defined_fn {0}.{1} to {2} {3}" + .format(unique_database, udf, kw, id)) + result = self.client.execute("show grant {0} {1} on user_defined_fn {2}.{3}" + .format(kw, id, unique_database, udf)) + TestRanger._check_privileges(result, [ + [kw, id, unique_database, "", "", "", "", "", udf, "create", "false"]]) + + # Grant the DROP privilege and verify. + admin_client.execute("grant drop on user_defined_fn {0}.{1} to {2} {3}" + .format(unique_database, udf, kw, id)) + result = self.client.execute("show grant {0} {1} on user_defined_fn {2}.{3}" + .format(kw, id, unique_database, udf)) + TestRanger._check_privileges(result, [ + [kw, id, unique_database, "", "", "", "", "", udf, "create", "false"], + [kw, id, unique_database, "", "", "", "", "", udf, "drop", "false"]]) + + # Grant the SELECT privilege and verify. + admin_client.execute("grant select on user_defined_fn {0}.{1} to {2} {3}" + .format(unique_database, udf, kw, id)) + result = self.client.execute("show grant {0} {1} on user_defined_fn {2}.{3}" + .format(kw, id, unique_database, udf)) + TestRanger._check_privileges(result, [ + [kw, id, unique_database, "", "", "", "", "", udf, "create", "false"], + [kw, id, unique_database, "", "", "", "", "", udf, "drop", "false"], + [kw, id, unique_database, "", "", "", "", "", udf, "select", "false"]]) + + # Grant the ALL privilege and verify other privileges are masked. + admin_client.execute("grant all on user_defined_fn {0}.{1} to {2} {3}" + .format(unique_database, udf, kw, id)) + result = self.client.execute("show grant {0} {1} on user_defined_fn {2}.{3}" + .format(kw, id, unique_database, udf)) + TestRanger._check_privileges(result, [ + [kw, id, unique_database, "", "", "", "", "", udf, "all", "false"]]) + finally: + admin_client.execute("revoke create on user_defined_fn {0}.{1} from {2} {3}" + .format(unique_database, udf, kw, id)) + admin_client.execute("revoke drop on user_defined_fn {0}.{1} from {2} {3}" + .format(unique_database, udf, kw, id)) + admin_client.execute("revoke select on user_defined_fn {0}.{1} from {2} {3}" + .format(unique_database, udf, kw, id)) + admin_client.execute("revoke all on user_defined_fn {0}.{1} from {2} {3}" + .format(unique_database, udf, kw, id)) + + def _test_show_grant_basic(self, admin_client, kw, id, unique_database, unique_table, + udf): uri = WAREHOUSE_PREFIX + '/tmp' database = 'functional' table = 'alltypes' @@ -412,6 +463,68 @@ class TestRanger(CustomClusterTestSuite): .format(kw, id, unique_database, unique_table)) TestRanger._check_privileges(result, []) + # Grant a privilege on a UDF with a wildcard for both database name and function + # name. + admin_client.execute("grant select on user_defined_fn `*`.`*` to {0} {1}" + .format(kw, id)) + result = self.client.execute("show grant {0} {1} on user_defined_fn `*`.`*`" + .format(kw, id)) + TestRanger._check_privileges(result, [ + [kw, id, "*", "", "", "", "", "", "*", "select", "false"]]) + + # Revoke the granted privilege and verify. + admin_client.execute("revoke select on user_defined_fn `*`.`*` from {0} {1}" + .format(kw, id)) + result = self.client.execute("show grant {0} {1} on user_defined_fn `*`.`*`" + .format(kw, id)) + TestRanger._check_privileges(result, []) + + # Grant a privilege on a UDF with a wildcard for functional name. + admin_client.execute("grant select on user_defined_fn {0}.`*` to {1} {2}" + .format(unique_database, kw, id)) + result = self.client.execute("show grant {0} {1} on user_defined_fn {2}.`*`" + .format(kw, id, unique_database)) + TestRanger._check_privileges(result, [ + [kw, id, unique_database, "", "", "", "", "", "*", "select", "false"]]) + + # Revoke the granted privilege and verify. + admin_client.execute("revoke select on user_defined_fn {0}.`*` from {1} {2}" + .format(unique_database, kw, id)) + result = self.client.execute("show grant {0} {1} on user_defined_fn {2}.`*`" + .format(kw, id, unique_database)) + TestRanger._check_privileges(result, []) + + # Grant a privilege on a UDF with a wildcard for database name but a + # non-wildcard for functional name. + admin_client.execute("grant select on user_defined_fn `*`.{0} to {1} {2}" + .format(udf, kw, id)) + result = self.client.execute("show grant {0} {1} on user_defined_fn `*`.{2}" + .format(kw, id, udf)) + TestRanger._check_privileges(result, [ + [kw, id, "*", "", "", "", "", "", udf, "select", "false"]]) + + # Revoke the granted privilege and verify. + admin_client.execute("revoke select on user_defined_fn `*`.{0} from {1} {2}" + .format(udf, kw, id)) + result = self.client.execute("show grant {0} {1} on user_defined_fn `*`.{2}" + .format(kw, id, udf)) + TestRanger._check_privileges(result, []) + + # Grant a privilege on a UDF and verify. + admin_client.execute("grant select on user_defined_fn {0}.{1} to {2} {3}" + .format(unique_database, udf, kw, id)) + result = self.client.execute("show grant {0} {1} on user_defined_fn {2}.{3}" + .format(kw, id, unique_database, udf)) + TestRanger._check_privileges(result, [ + [kw, id, unique_database, "", "", "", "", "", udf, "select", "false"]]) + + # Revoke the granted privilege and verify. + admin_client.execute("revoke select on user_defined_fn {0}.{1} from {2} {3}" + .format(unique_database, udf, kw, id)) + result = self.client.execute("show grant {0} {1} on user_defined_fn {2}.{3}" + .format(kw, id, unique_database, udf)) + TestRanger._check_privileges(result, []) + # Grant column privileges and verify admin_client.execute("grant select(x) on table {0}.{1} to {2} {3}" .format(unique_database, unique_table, kw, id)) @@ -437,6 +550,14 @@ class TestRanger(CustomClusterTestSuite): .format(unique_database, kw, id)) admin_client.execute("revoke select on table {0}.{1} from {2} {3}" .format(unique_database, unique_table, kw, id)) + admin_client.execute("revoke select on user_defined_fn `*`.`*` from {0} {1}" + .format(kw, id)) + admin_client.execute("revoke select on user_defined_fn {0}.`*` from {1} {2}" + .format(unique_database, kw, id)) + admin_client.execute("revoke select on user_defined_fn `*`.{0} from {1} {2}" + .format(udf, kw, id)) + admin_client.execute("revoke select on user_defined_fn {0}.{1} from {2} {3}" + .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)) @@ -1048,6 +1169,128 @@ class TestRanger(CustomClusterTestSuite): self._run_query_as_user("drop database {0} cascade".format(unique_database), ADMIN, True) + @pytest.mark.execute_serially + @CustomClusterTestSuite.with_args( + impalad_args=IMPALAD_ARGS, catalogd_args=CATALOGD_ARGS) + def test_select_function(self, unique_name): + """Verifies that to execute a UDF in a database, a user has to be granted a) the + SELECT privilege on the UDF, and b) any of the SELECT, INSERT, REFRESH privileges on + all the tables, columns in the database.""" + test_user = "non_owner" + admin_client = self.create_impala_client() + unique_database = unique_name + "_db" + fs_prefix = os.getenv("FILESYSTEM_PREFIX") or str() + try: + # Create a temporary database and a user-defined function. + admin_client.execute("drop database if exists {0} cascade".format(unique_database), + user=ADMIN) + admin_client.execute("create database {0}".format(unique_database), user=ADMIN) + admin_client.execute("create function {0}.identity(bigint) " + "RETURNS bigint " + "LOCATION " + "'{1}/test-warehouse/impala-hive-udfs.jar' " + "SYMBOL='org.apache.impala.TestUdf'" + .format(unique_database, fs_prefix), user=ADMIN) + + # A user not granted any privilege is not allowed to execute the UDF. + result = self._run_query_as_user("select {0}.identity(1)".format(unique_database), + test_user, False) + err = "User '{0}' does not have privileges to SELECT functions in: " \ + "{1}.identity".format(test_user, unique_database) + assert err in str(result) + + view_metadata_privileges = ["select", "insert", "refresh"] + for privilege_on_database in view_metadata_privileges: + try: + # A user is allowed to execute a UDF in a database if the user has been granted + # the SELECT privilege on the database. Such a privilege covers all the tables, + # columns, as well as UDFs in the database. + admin_client.execute("grant {0} on database {1} to user {2}" + .format(privilege_on_database, unique_database, test_user), + user=ADMIN) + result = admin_client.execute("show grant user {0} on database {1}" + .format(test_user, unique_database), user=ADMIN) + TestRanger._check_privileges(result, [ + ["USER", test_user, unique_database, "", "", "", "", "", "*", + privilege_on_database, "false"], + ["USER", test_user, unique_database, "*", "*", "", "", "", "", + privilege_on_database, "false"]]) + # Query succeeds only if 'privilege_on_database' is "select". + if privilege_on_database != "select": + result = self._run_query_as_user("select {0}.identity(1)" + .format(unique_database), test_user, False) + err = "User '{0}' does not have privileges to SELECT functions in: " \ + "{1}.identity".format(test_user, unique_database) + assert err in str(result) + else: + self._run_query_as_user("select {0}.identity(1)".format(unique_database), + test_user, True) + + # A user not being granted the SELECT privilege on any UDF in the database is + # not allowed to execute the UDF even though the user has + # the 'privilege_on_database' privilege on all the tables, columns in the + # database. + admin_client.execute("revoke {0} on user_defined_fn {1}.`*` from user {2}" + .format(privilege_on_database, unique_database, + test_user), user=ADMIN) + result = admin_client.execute("show grant user {0} on database {1}" + .format(test_user, unique_database), user=ADMIN) + TestRanger._check_privileges(result, [ + ["USER", test_user, unique_database, "*", "*", "", "", "", "", + privilege_on_database, "false"]]) + result = self._run_query_as_user("select {0}.identity(1)" + .format(unique_database), test_user, False) + err = "User '{0}' does not have privileges to SELECT functions in: " \ + "{1}.identity".format(test_user, unique_database) + assert err in str(result) + + # A user is allowed to execute the UDF if the user is explicitly granted the + # SELECT privilege on the UDF. + admin_client.execute("grant select on user_defined_fn {0}.identity to user {1}" + .format(unique_database, test_user), user=ADMIN) + result = admin_client.execute("show grant user {0} " + "on user_defined_fn {1}.identity" + .format(test_user, unique_database), user=ADMIN) + TestRanger._check_privileges(result, [ + ["USER", test_user, unique_database, "", "", "", "", "", "identity", + "select", "false"]]) + self._run_query_as_user("select {0}.identity(1)".format(unique_database), + test_user, True) + + # Even though a user is explicitly granted the SELECT privilege on the UDF, the + # user is not allowed to execute the UDF if the user is not granted any of the + # SELECT, INSERT, or REFRESH privileges on all the tables and columns in the + # database. + admin_client.execute("revoke {0} on database {1} from user {2}" + .format(privilege_on_database, unique_database, + test_user), user=ADMIN) + result = admin_client.execute("show grant user {0} on database {1}" + .format(test_user, unique_database), user=ADMIN) + TestRanger._check_privileges(result, []) + result = admin_client.execute("show grant user {0} " + "on user_defined_fn {1}.identity" + .format(test_user, unique_database), user=ADMIN) + TestRanger._check_privileges(result, [ + ["USER", test_user, unique_database, "", "", "", "", "", "identity", + "select", "false"]]) + result = self._run_query_as_user("select {0}.identity(1)" + .format(unique_database), test_user, False) + err = "User '{0}' does not have privileges to access: {1}"\ + .format(test_user, unique_database) + assert err in str(result) + finally: + # Revoke the granted privileges. + admin_client.execute("revoke {0} on database {1} from user {2}" + .format(privilege_on_database, unique_database, + test_user), user=ADMIN) + admin_client.execute("revoke select on user_defined_fn {0}.identity " + "from user {1}" + .format(unique_database, test_user), user=ADMIN) + finally: + # Drop the database. + self._run_query_as_user("drop database {0} cascade".format(unique_database), + ADMIN, True) + def _test_ownership(self): """Tests ownership privileges for databases and tables with ranger along with some known quirks in the implementation.""" @@ -1111,7 +1354,7 @@ class TestRanger(CustomClusterTestSuite): impalad_args=IMPALAD_ARGS, catalogd_args=CATALOGD_ARGS) def test_select_function_with_fallback_db(self, unique_name): """Verifies that Impala should not allow using functions in the fallback database - unless the user has some privileges on the given database.""" + unless the user has been granted sufficient privileges on the given database.""" test_user = "non_owner" admin_client = self.create_impala_client() non_owner_client = self.create_impala_client() @@ -1130,8 +1373,8 @@ class TestRanger(CustomClusterTestSuite): .format(unique_database, WAREHOUSE), user=ADMIN) # A user not granted any privilege is not allowed to execute the UDF. result = self._run_query_as_user("select identity(1)", test_user, False) - err = "User '{0}' does not have privileges to access: default".format( - test_user) + err = "User '{0}' does not have privileges to SELECT functions in: " \ + "default.identity".format(test_user) assert err in str(result) admin_client.execute( @@ -1148,19 +1391,25 @@ class TestRanger(CustomClusterTestSuite): result = self.execute_query_expect_failure( non_owner_client, "select identity(1)", query_options={ 'FALLBACK_DB_FOR_FUNCTIONS': unique_database}, user=test_user) - err = "User '{0}' does not have privileges to access: {1}".format( - test_user, unique_database) + err = "User '{0}' does not have privileges to SELECT functions in: " \ + "{1}.identity".format(test_user, unique_database) assert err in str(result) result = self.execute_query_expect_failure( non_owner_client, "select fn()", query_options={ 'FALLBACK_DB_FOR_FUNCTIONS': unique_database}, user=test_user) - err = "User '{0}' does not have privileges to access: {1}".format( - test_user, unique_database) + err = "User '{0}' does not have privileges to SELECT functions in: " \ + "{1}.fn".format(test_user, unique_database) assert err in str(result) + # A user has to be granted a) any of the INSERT, REFRESH, SELECT privileges on all + # the tables and columns in the fallback database, and b) the SELECT privilege on + # the UDF in the fallback database in order to execute the UDF. + admin_client.execute( + "grant insert on database {0} to user {1}".format( + unique_database, test_user), user=ADMIN) admin_client.execute( - "grant select on database {0} to user {1}".format( + "grant select on user_defined_fn {0}.identity to user {1}".format( unique_database, test_user), user=ADMIN) self._refresh_authorization(admin_client, refresh_stmt) @@ -1174,9 +1423,11 @@ class TestRanger(CustomClusterTestSuite): finally: # Revoke the granted privileges. admin_client.execute("revoke select on database default from user {0}" - .format(test_user), user=ADMIN) - admin_client.execute("revoke select on database {0} from user {1}" - .format(unique_database, test_user), user=ADMIN) + .format(test_user), user=ADMIN) + admin_client.execute("revoke insert on database {0} from user {1}" + .format(unique_database, test_user), user=ADMIN) + admin_client.execute("revoke select on user_defined_fn {0}.identity from user {1}" + .format(unique_database, test_user), user=ADMIN) # Drop the database. self._run_query_as_user("drop database {0} cascade".format(unique_database), ADMIN, True)
