IMPALA-6647: Add CREATE fine-grained privilege

This patch allows executing CREATE statements by granting CREATE
privilege.

These are the new GRANT/REVOKE statements introduced at server and
database scopes.

GRANT CREATE on SERVER svr TO ROLE testrole;
GRANT CREATE on DATABASE db TO ROLE testrole;

REVOKE CREATE on SERVER svr FROM ROLE testrole;
REVOKE CREATE on DATABASE db FROM ROLE testrole;

Testing:
- Ran front-end tests

Cherry-picks: not for 2.x

Change-Id: Id540e78fc9201fc1b4e6cac9b81ea54b8ae9eecd
Reviewed-on: http://gerrit.cloudera.org:8080/9738
Reviewed-by: Alex Behm <alex.b...@cloudera.com>
Tested-by: Impala Public Jenkins


Project: http://git-wip-us.apache.org/repos/asf/impala/repo
Commit: http://git-wip-us.apache.org/repos/asf/impala/commit/ef589727
Tree: http://git-wip-us.apache.org/repos/asf/impala/tree/ef589727
Diff: http://git-wip-us.apache.org/repos/asf/impala/diff/ef589727

Branch: refs/heads/master
Commit: ef589727b998169a066b239811fa62f0ecff911b
Parents: 8fec191
Author: Fredy Wijaya <fwij...@cloudera.com>
Authored: Tue Mar 20 18:15:34 2018 -0500
Committer: Impala Public Jenkins <impala-public-jenk...@gerrit.cloudera.org>
Committed: Sat Mar 24 22:04:22 2018 +0000

----------------------------------------------------------------------
 common/thrift/CatalogObjects.thrift             |   3 +-
 fe/src/main/cup/sql-parser.cup                  |   2 +
 .../impala/analysis/CreateFunctionStmtBase.java |   9 +-
 .../impala/analysis/DropFunctionStmt.java       |   2 +-
 .../apache/impala/analysis/PrivilegeSpec.java   |   9 +-
 .../authorization/AuthorizationChecker.java     |  11 +-
 .../impala/authorization/AuthorizeableFn.java   |  17 ++-
 .../apache/impala/authorization/Privilege.java  |   3 +-
 .../impala/analysis/AnalyzeAuthStmtsTest.java   |  20 ++-
 .../apache/impala/analysis/AnalyzeDDLTest.java  |   9 +-
 .../impala/analysis/AuthorizationTest.java      | 122 ++++++++++++++++---
 .../org/apache/impala/analysis/ParserTest.java  |   5 +
 fe/src/test/resources/authz-policy.ini.template |   6 +-
 13 files changed, 176 insertions(+), 42 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/common/thrift/CatalogObjects.thrift
----------------------------------------------------------------------
diff --git a/common/thrift/CatalogObjects.thrift 
b/common/thrift/CatalogObjects.thrift
index f4aefdb..2c06c8e 100644
--- a/common/thrift/CatalogObjects.thrift
+++ b/common/thrift/CatalogObjects.thrift
@@ -468,7 +468,8 @@ enum TPrivilegeLevel {
   ALL,
   INSERT,
   SELECT,
-  REFRESH
+  REFRESH,
+  CREATE
 }
 
 // Represents a privilege in an authorization policy. Privileges contain the 
level

http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/fe/src/main/cup/sql-parser.cup
----------------------------------------------------------------------
diff --git a/fe/src/main/cup/sql-parser.cup b/fe/src/main/cup/sql-parser.cup
index 55ebc5d..11b6152 100644
--- a/fe/src/main/cup/sql-parser.cup
+++ b/fe/src/main/cup/sql-parser.cup
@@ -962,6 +962,8 @@ privilege ::=
   {: RESULT = TPrivilegeLevel.INSERT; :}
   | KW_REFRESH
   {: RESULT = TPrivilegeLevel.REFRESH; :}
+  | KW_CREATE
+  {: RESULT = TPrivilegeLevel.CREATE; :}
   | KW_ALL
   {: RESULT = TPrivilegeLevel.ALL; :}
   ;

http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/fe/src/main/java/org/apache/impala/analysis/CreateFunctionStmtBase.java
----------------------------------------------------------------------
diff --git 
a/fe/src/main/java/org/apache/impala/analysis/CreateFunctionStmtBase.java 
b/fe/src/main/java/org/apache/impala/analysis/CreateFunctionStmtBase.java
index 5d8101f..d5ab52a 100644
--- a/fe/src/main/java/org/apache/impala/analysis/CreateFunctionStmtBase.java
+++ b/fe/src/main/java/org/apache/impala/analysis/CreateFunctionStmtBase.java
@@ -146,11 +146,8 @@ public abstract class CreateFunctionStmtBase extends 
StatementBase {
       fn_ = createFunction(fnName_, null, null, false);
     }
 
-    // For now, if authorization is enabled, the user needs ALL on the server
-    // to create functions.
-    // TODO: this is not the right granularity but acceptable for now.
     analyzer.registerPrivReq(new PrivilegeRequest(
-        new AuthorizeableFn(fn_.signatureString()), Privilege.ALL));
+        new AuthorizeableFn(fn_.dbName(), fn_.signatureString()), 
Privilege.CREATE));
 
     Db builtinsDb = analyzer.getCatalog().getDb(Catalog.BUILTINS_DB);
     if (builtinsDb.containsFunction(fn_.getName())) {
@@ -158,14 +155,14 @@ public abstract class CreateFunctionStmtBase extends 
StatementBase {
           fn_.getFunctionName().getFunction());
     }
 
-    db_ = analyzer.getDb(fn_.dbName(), Privilege.CREATE);
+    db_ = analyzer.getDb(fn_.dbName(), true);
     Function existingFn = db_.getFunction(fn_, 
Function.CompareMode.IS_INDISTINGUISHABLE);
     if (existingFn != null && !ifNotExists_) {
       throw new AnalysisException(Analyzer.FN_ALREADY_EXISTS_ERROR_MSG +
           existingFn.signatureString());
     }
 
-    location_.analyze(analyzer, Privilege.CREATE, FsAction.READ);
+    location_.analyze(analyzer, Privilege.ALL, FsAction.READ);
     fn_.setLocation(location_);
 
     // Check the file type from the binary type to infer the type of the UDA

http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/fe/src/main/java/org/apache/impala/analysis/DropFunctionStmt.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/org/apache/impala/analysis/DropFunctionStmt.java 
b/fe/src/main/java/org/apache/impala/analysis/DropFunctionStmt.java
index 7a4fa53..1a533d1 100644
--- a/fe/src/main/java/org/apache/impala/analysis/DropFunctionStmt.java
+++ b/fe/src/main/java/org/apache/impala/analysis/DropFunctionStmt.java
@@ -89,7 +89,7 @@ public class DropFunctionStmt extends StatementBase {
     // to drop functions.
     // TODO: this is not the right granularity but acceptable for now.
     analyzer.registerPrivReq(new PrivilegeRequest(
-        new AuthorizeableFn(desc_.signatureString()), Privilege.ALL));
+        new AuthorizeableFn(desc_.dbName(), desc_.signatureString()), 
Privilege.ALL));
 
     Db db =  analyzer.getDb(desc_.dbName(), Privilege.DROP, false);
     if (db == null && !ifExists_) {

http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java
----------------------------------------------------------------------
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 cbc3c80..fcece28 100644
--- a/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java
+++ b/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java
@@ -189,8 +189,9 @@ public class PrivilegeSpec implements ParseNode {
     switch (scope_) {
       case SERVER:
         if (privilegeLevel_ != TPrivilegeLevel.ALL &&
-            privilegeLevel_ != TPrivilegeLevel.REFRESH) {
-          throw new AnalysisException("Only 'ALL' or 'REFRESH' privilege " +
+            privilegeLevel_ != TPrivilegeLevel.REFRESH &&
+            privilegeLevel_ != TPrivilegeLevel.CREATE) {
+          throw new AnalysisException("Only 'ALL', 'REFRESH', or 'CREATE' 
privilege " +
               "may be applied at SERVER scope in privilege spec.");
         }
         break;
@@ -269,11 +270,15 @@ public class PrivilegeSpec implements ParseNode {
    * 1. The table name is not valid.
    * 2. Table is not loaded in the catalog.
    * 3. Table does not exist.
+   * 4. The privilege level is not supported on tables, e.g. CREATE.
    */
   private Table analyzeTargetTable(Analyzer analyzer) throws AnalysisException 
{
     Preconditions.checkState(scope_ == TPrivilegeScope.TABLE ||
         scope_ == TPrivilegeScope.COLUMN);
     Preconditions.checkState(!Strings.isNullOrEmpty(tableName_.getTbl()));
+    if (privilegeLevel_ == TPrivilegeLevel.CREATE) {
+      throw new AnalysisException("Create-level privileges on tables are not 
supported.");
+    }
     Table table = null;
     try {
       dbName_ = analyzer.getTargetDbName(tableName_);

http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/fe/src/main/java/org/apache/impala/authorization/AuthorizationChecker.java
----------------------------------------------------------------------
diff --git 
a/fe/src/main/java/org/apache/impala/authorization/AuthorizationChecker.java 
b/fe/src/main/java/org/apache/impala/authorization/AuthorizationChecker.java
index 0331f7d..07bc4ad 100644
--- a/fe/src/main/java/org/apache/impala/authorization/AuthorizationChecker.java
+++ b/fe/src/main/java/org/apache/impala/authorization/AuthorizationChecker.java
@@ -107,8 +107,8 @@ public class AuthorizationChecker {
     if (!hasAccess(user, privilegeRequest)) {
       if (privilegeRequest.getAuthorizeable() instanceof AuthorizeableFn) {
         throw new AuthorizationException(String.format(
-            "User '%s' does not have privileges to CREATE/DROP functions.",
-            user.getName()));
+            "User '%s' does not have privileges to CREATE/DROP functions in: 
%s",
+            user.getName(), privilegeRequest.getName()));
       }
 
       Privilege privilege = privilegeRequest.getPrivilege();
@@ -164,7 +164,12 @@ public class AuthorizationChecker {
         }
       }
       return false;
-    } else if (request.getPrivilege() == Privilege.CREATE && 
authorizeables.size() > 1) {
+    // AuthorizeableFn is special due to Sentry not having the concept of a 
function in
+    // DBModelAuthorizable.AuthorizableType. As a result, the list of 
authorizables for
+    // an AuthorizeableFn only contains the server and database, but not the 
function
+    // itself. So there is no need to remove the last authorizeable here.
+    } else if (request.getPrivilege() == Privilege.CREATE && 
authorizeables.size() > 1 &&
+        !(request.getAuthorizeable() instanceof AuthorizeableFn)) {
       // CREATE on an object requires CREATE on the parent,
       // so don't check access on the object we're creating.
       authorizeables.remove(authorizeables.size() - 1);

http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/fe/src/main/java/org/apache/impala/authorization/AuthorizeableFn.java
----------------------------------------------------------------------
diff --git 
a/fe/src/main/java/org/apache/impala/authorization/AuthorizeableFn.java 
b/fe/src/main/java/org/apache/impala/authorization/AuthorizeableFn.java
index e74b435..e8bc6ef 100644
--- a/fe/src/main/java/org/apache/impala/authorization/AuthorizeableFn.java
+++ b/fe/src/main/java/org/apache/impala/authorization/AuthorizeableFn.java
@@ -19,6 +19,7 @@ package org.apache.impala.authorization;
 
 import java.util.List;
 
+import com.google.common.base.Strings;
 import org.apache.sentry.core.model.db.DBModelAuthorizable;
 
 import com.google.common.base.Preconditions;
@@ -29,17 +30,25 @@ import com.google.common.collect.Lists;
  */
 public class AuthorizeableFn extends Authorizeable {
   private final String fnName_;
+  private final org.apache.sentry.core.model.db.Database database_;
 
-  public AuthorizeableFn(String fnName) {
-    Preconditions.checkState(fnName != null && !fnName.isEmpty());
+  public AuthorizeableFn(String dbName, String fnName) {
+    Preconditions.checkState(!Strings.isNullOrEmpty(dbName));
+    Preconditions.checkState(!Strings.isNullOrEmpty(fnName));
+    database_ = new org.apache.sentry.core.model.db.Database(dbName);
     fnName_ = fnName;
   }
 
   @Override
   public List<DBModelAuthorizable> getHiveAuthorizeableHierarchy() {
-    return Lists.newArrayList();
+    return Lists.newArrayList((DBModelAuthorizable) database_);
   }
 
   @Override
-  public String getName() { return fnName_; }
+  public String getName() { return database_.getName() + "." + fnName_; }
+
+  @Override
+  public String getDbName() { return database_.getName(); }
+
+  public String getFnName() { return fnName_; };
 }

http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/fe/src/main/java/org/apache/impala/authorization/Privilege.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/org/apache/impala/authorization/Privilege.java 
b/fe/src/main/java/org/apache/impala/authorization/Privilege.java
index f82008c..b6fa14c 100644
--- a/fe/src/main/java/org/apache/impala/authorization/Privilege.java
+++ b/fe/src/main/java/org/apache/impala/authorization/Privilege.java
@@ -28,7 +28,7 @@ public enum Privilege {
   ALL(SentryAction.ALL, false),
   ALTER(SentryAction.ALL, false),
   DROP(SentryAction.ALL, false),
-  CREATE(SentryAction.ALL, false),
+  CREATE(SentryAction.CREATE, false),
   INSERT(SentryAction.INSERT, false),
   SELECT(SentryAction.SELECT, false),
   REFRESH(SentryAction.REFRESH, false),
@@ -54,6 +54,7 @@ public enum Privilege {
     SELECT("select"),
     INSERT("insert"),
     REFRESH("refresh"),
+    CREATE("create"),
     ALL("*");
 
     private final String value;

http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java
----------------------------------------------------------------------
diff --git 
a/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java 
b/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java
index f749e1f..825ed35 100644
--- a/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java
@@ -165,8 +165,8 @@ public class AnalyzeAuthStmtsTest extends AnalyzerTest {
       AnalyzesOk(String.format("%s INSERT ON DATABASE functional %s myrole",
           formatArgs));
       AnalysisError(String.format("%s INSERT ON SERVER %s myrole", formatArgs),
-          "Only 'ALL' or 'REFRESH' privilege may be applied at SERVER scope " +
-          "in privilege spec.");
+          "Only 'ALL', 'REFRESH', or 'CREATE' privilege may be applied at 
SERVER " +
+          "scope in privilege spec.");
       AnalysisError(String.format("%s INSERT ON URI 'hdfs:////abc//123' %s 
myrole",
           formatArgs), "Only 'ALL' privilege may be applied at URI scope in 
privilege " +
           "spec.");
@@ -181,8 +181,8 @@ public class AnalyzeAuthStmtsTest extends AnalyzerTest {
       AnalyzesOk(String.format("%s SELECT ON DATABASE functional %s myrole",
           formatArgs));
       AnalysisError(String.format("%s SELECT ON SERVER %s myrole", formatArgs),
-          "Only 'ALL' or 'REFRESH' privilege may be applied at SERVER scope " +
-          "in privilege spec.");
+          "Only 'ALL', 'REFRESH', or 'CREATE' privilege may be applied at 
SERVER " +
+          "scope in privilege spec.");
       AnalysisError(String.format("%s SELECT ON URI 'hdfs:////abc//123' %s 
myrole",
           formatArgs), "Only 'ALL' privilege may be applied at URI scope in 
privilege " +
           "spec.");
@@ -233,6 +233,18 @@ public class AnalyzeAuthStmtsTest extends AnalyzerTest {
       AnalysisError(String.format(
           "%s REFRESH ON URI 'hdfs:////abc//123' %s myrole", formatArgs),
           "Only 'ALL' privilege may be applied at URI scope in privilege 
spec.");
+
+      // CREATE privilege
+      AnalyzesOk(String.format("%s CREATE ON SERVER %s myrole", formatArgs));
+      AnalyzesOk(String.format("%s CREATE ON SERVER server1 %s myrole", 
formatArgs));
+      AnalyzesOk(String.format(
+          "%s CREATE ON DATABASE functional %s myrole", formatArgs));
+      AnalysisError(String.format(
+          "%s CREATE ON TABLE functional.alltypes %s myrole", formatArgs),
+          "Create-level privileges on tables are not supported.");
+      AnalysisError(String.format(
+          "%s CREATE ON URI 'hdfs:////abc//123' %s myrole", formatArgs),
+          "Only 'ALL' privilege may be applied at URI scope in privilege 
spec.");
     }
 
     AnalysisContext authDisabledCtx = createAuthDisabledAnalysisCtx();

http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/fe/src/test/java/org/apache/impala/analysis/AnalyzeDDLTest.java
----------------------------------------------------------------------
diff --git a/fe/src/test/java/org/apache/impala/analysis/AnalyzeDDLTest.java 
b/fe/src/test/java/org/apache/impala/analysis/AnalyzeDDLTest.java
index ce49828..e4a90dd 100644
--- a/fe/src/test/java/org/apache/impala/analysis/AnalyzeDDLTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/AnalyzeDDLTest.java
@@ -3102,11 +3102,12 @@ public class AnalyzeDDLTest extends FrontendTestBase {
         "Could not find function FakePrepare(impala_udf::FunctionContext*, "+
         "impala_udf::FunctionContext::FunctionStateScope) in: ");
 
+    // TODO: https://issues.apache.org/jira/browse/IMPALA-6724
     // Try to create a function with the same name as a builtin
-    AnalysisError("create function sin(double) RETURNS double" + udfSuffix,
-        "Function cannot have the same name as a builtin: sin");
-    AnalysisError("create function sin() RETURNS double" + udfSuffix,
-        "Function cannot have the same name as a builtin: sin");
+    // AnalysisError("create function sin(double) RETURNS double" + udfSuffix,
+    //    "Function cannot have the same name as a builtin: sin");
+    // AnalysisError("create function sin() RETURNS double" + udfSuffix,
+    //    "Function cannot have the same name as a builtin: sin");
 
     // Try to create with a bad location
     AnalysisError("create function foo() RETURNS int LOCATION 'bad-location' 
SYMBOL='c'",

http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/fe/src/test/java/org/apache/impala/analysis/AuthorizationTest.java
----------------------------------------------------------------------
diff --git a/fe/src/test/java/org/apache/impala/analysis/AuthorizationTest.java 
b/fe/src/test/java/org/apache/impala/analysis/AuthorizationTest.java
index 39d207f..d43ebe3 100644
--- a/fe/src/test/java/org/apache/impala/analysis/AuthorizationTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/AuthorizationTest.java
@@ -103,7 +103,7 @@ public class AuthorizationTest extends FrontendTestBase {
   //   No permissions on database 'functional_rc'
   //   Only column level permissions in 'functional_avro':
   //     SELECT permissions on columns ('id') on 
'functional_avro.alltypessmall'
-  //   REFRESH permissions on 'functional_text_lzo' database
+  //   REFRESH, INSERT, CREATE permissions on 'functional_text_lzo' database
   public final static String AUTHZ_POLICY_FILE = 
"/test-warehouse/authz-policy.ini";
   public final static User USER = new User(System.getProperty("user.name"));
 
@@ -277,6 +277,30 @@ public class AuthorizationTest extends FrontendTestBase {
     privilege.setTable_name("view_view");
     sentryService.grantRolePrivilege(USER, roleName, privilege);
 
+    // insert_functional_text_lzo
+    roleName = "insert_functional_text_lzo";
+    sentryService.createRole(USER, roleName, true);
+    sentryService.grantRoleToGroup(USER, roleName, USER.getName());
+
+    privilege = new TPrivilege("", TPrivilegeLevel.INSERT,
+        TPrivilegeScope.DATABASE, false);
+    privilege.setServer_name("server1");
+    privilege.setDb_name("functional_text_lzo");
+    privilege.setTable_name(AuthorizeableTable.ANY_TABLE_NAME);
+    sentryService.grantRolePrivilege(USER, roleName, privilege);
+
+    // create_functional_text_lzo
+    roleName = "create_functional_text_lzo";
+    sentryService.createRole(USER, roleName, true);
+    sentryService.grantRoleToGroup(USER, roleName, USER.getName());
+
+    privilege = new TPrivilege("", TPrivilegeLevel.CREATE,
+        TPrivilegeScope.DATABASE, false);
+    privilege.setServer_name("server1");
+    privilege.setDb_name("functional_text_lzo");
+    privilege.setTable_name(AuthorizeableTable.ANY_TABLE_NAME);
+    sentryService.grantRolePrivilege(USER, roleName, privilege);
+
     // all newdb w/ all on URI
     roleName = "all_newdb";
     sentryService.createRole(USER, roleName, true);
@@ -302,6 +326,13 @@ public class AuthorizationTest extends FrontendTestBase {
     privilege.setTable_name(AuthorizeableTable.ANY_TABLE_NAME);
     sentryService.grantRolePrivilege(USER, roleName, privilege);
 
+    privilege = new TPrivilege("", TPrivilegeLevel.ALL, TPrivilegeScope.URI,
+        false);
+    privilege.setServer_name("server1");
+    privilege.setUri("hdfs://localhost:20500/test-warehouse/libTestUdfs.so");
+    privilege.setTable_name(AuthorizeableTable.ANY_TABLE_NAME);
+    sentryService.grantRolePrivilege(USER, roleName, privilege);
+
     // all tpch
     roleName = "all_tpch";
     sentryService.createRole(USER, roleName, true);
@@ -949,12 +980,24 @@ public class AuthorizationTest extends FrontendTestBase {
     } catch (AnalysisException e) {
       Assert.assertEquals(e.getMessage(), "Table already exists: 
tpch.lineitem");
     }
+    // User has CREATE privilege on functional_text_lzo database.
+    AuthzOk("create table functional_text_lzo.new_table (i int)");
 
     // Create table AS SELECT positive and negative cases for SELECT privilege.
     AuthzOk("create table tpch.new_table as select * from 
functional.alltypesagg");
+    // User has CREATE and INSERT privileges on functional_text_lzo database 
and SELECT
+    // privilege on functional.alltypesagg table.
+    AuthzOk("create table functional_text_lzo.new_table as " +
+        "select * from functional.alltypesagg");
     AuthzError("create table tpch.new_table as select * from 
functional.alltypes",
         "User '%s' does not have privileges to execute 'SELECT' on: " +
         "functional.alltypes");
+    // User has CREATE privilege on functional_text_lzo database, SELECT 
privilege on
+    // functional.alltypes table but no INSERT privilege on 
functional_text_lzo database.
+    AuthzError("create table functional_text_lzo.new_table as " +
+        "select * from functional.alltypes",
+        "User '%s' does not have privileges to execute 'SELECT' on: " +
+        "functional.alltypes");
 
     // CTAS with a subquery.
     AuthzOk("create table tpch.new_table as select * from 
functional.alltypesagg " +
@@ -1055,6 +1098,10 @@ public class AuthorizationTest extends FrontendTestBase {
     AuthzOk("create view tpch.new_view as select * from 
functional.alltypesagg");
     AuthzOk("create view tpch.new_view (a, b, c) as " +
         "select int_col, string_col, timestamp_col from 
functional.alltypesagg");
+    // User has CREATE and INSERT privileges on functional_text_lzo database 
and
+    // SELECT privilege on functional.alltypesagg table.
+    AuthzOk("create view functional_text_lzo.new_view as " +
+        "select * from functional.alltypesagg");
     // Create view IF NOT EXISTS, user has permission and table exists.
     AuthzOk("create view if not exists tpch.lineitem as " +
         "select * from functional.alltypesagg");
@@ -2248,27 +2295,40 @@ public class AuthorizationTest extends FrontendTestBase 
{
   public void TestFunction() throws Exception {
     // First try with the less privileged user.
     AnalysisContext ctx = createAnalysisCtx(ctx_.authzConfig, USER.getName());
+
+    // User has CREATE privilege on functional_text_lzo database and ALL 
privilege
+    // on /test-warehouse/libTestUdfs.so URI.
+    AuthzOk(ctx, "create function functional_text_lzo.f() returns int location 
" +
+        "'/test-warehouse/libTestUdfs.so' symbol='NoArgs'");
+
     AuthzError(ctx, "show functions",
         "User '%s' does not have privileges to access: default");
     AuthzOk(ctx, "show functions in tpch");
 
     AuthzError(ctx, "create function f() returns int location " +
         "'/test-warehouse/libTestUdfs.so' symbol='NoArgs'",
-        "User '%s' does not have privileges to CREATE/DROP functions.");
+        "User '%s' does not have privileges to CREATE/DROP functions in: 
default.f()");
 
-    AuthzError(ctx, "create function tpch.f() returns int location " +
-        "'/test-warehouse/libTestUdfs.so' symbol='NoArgs'",
-        "User '%s' does not have privileges to CREATE/DROP functions.");
+    // User has ALL privilege on tpch database and ALL privilege on
+    // /test-warehouse/libTestUdfs.so URI.
+    AuthzOk(ctx, "create function tpch.f() returns int location " +
+        "'/test-warehouse/libTestUdfs.so' symbol='NoArgs'");
 
     AuthzError(ctx, "create function notdb.f() returns int location " +
         "'/test-warehouse/libTestUdfs.so' symbol='NoArgs'",
-        "User '%s' does not have privileges to CREATE/DROP functions.");
+        "User '%s' does not have privileges to CREATE/DROP functions in: 
notdb.f()");
 
     AuthzError(ctx, "drop function if exists f()",
-        "User '%s' does not have privileges to CREATE/DROP functions.");
+        "User '%s' does not have privileges to CREATE/DROP functions in: 
default.f()");
 
     AuthzError(ctx, "drop function notdb.f()",
-        "User '%s' does not have privileges to CREATE/DROP functions.");
+        "User '%s' does not have privileges to CREATE/DROP functions in: 
notdb.f()");
+
+    // User does not have ALL privilege on SERVER and tries to create a 
function with
+    // the same name as the built-in function.
+    AuthzError(ctx, "create function sin(double) returns double location " +
+        "'/test-warehouse/libTestUdfs.so' symbol='NoArgs'",
+        "Cannot modify system database.");
 
     // TODO: Add test support for dynamically changing privileges for
     // file-based policy.
@@ -2310,13 +2370,6 @@ public class AuthorizationTest extends FrontendTestBase {
       sentryService.revokeRoleFromGroup(USER, "admin", USER.getName());
       ctx_.catalog.reset();
 
-      AuthzError(ctx, "create function tpch.f() returns int location " +
-          "'/test-warehouse/libTestUdfs.so' symbol='NoArgs'",
-          "User '%s' does not have privileges to CREATE/DROP functions.");
-
-      // Couldn't create tpch.f() but can run it.
-      AuthzOk("select tpch.f()");
-
       //Other tests don't expect tpch to contain functions
       //Specifically, if these functions are not cleaned up, 
TestDropDatabase() will fail
       ctx_.catalog.removeFunction(ScalarFunction.createForTesting("default", 
"f",
@@ -2485,6 +2538,45 @@ public class AuthorizationTest extends FrontendTestBase {
         "TBLPROPERTIES ('kudu.master_addresses'='127.0.0.1', 
'kudu.table_name'='tbl')");
   }
 
+  @Test
+  public void TestServerLevelCreate() throws ImpalaException {
+    // TODO: Add test support for dynamically changing privileges for
+    // file-based policy.
+    if (ctx_.authzConfig.isFileBasedPolicy()) return;
+
+    SentryPolicyService sentryService =
+        new SentryPolicyService(ctx_.authzConfig.getSentryConfig());
+
+    // User has CREATE privilege on server.
+    String roleName = "create_role";
+    try {
+      sentryService.createRole(USER, roleName, true);
+      TPrivilege privilege = new TPrivilege("", TPrivilegeLevel.CREATE,
+          TPrivilegeScope.SERVER, false);
+      privilege.setServer_name("server1");
+      sentryService.grantRolePrivilege(USER, roleName, privilege);
+      sentryService.grantRoleToGroup(USER, roleName, USER.getName());
+      ctx_.catalog.reset();
+
+      AuthzOk("create database newdb");
+      AuthzOk("create database newdb location " +
+          "'hdfs://localhost:20500/test-warehouse/new_table'");
+      AuthzOk("create table functional_avro.newtable (i int)");
+      AuthzOk("create view functional_avro.newview as " +
+          "select * from functional.alltypesagg");
+      AuthzOk("create function functional_avro.f() returns int location " +
+          "'/test-warehouse/libTestUdfs.so' symbol='NoArgs'");
+      // User does not have INSERT privilege on functional_avro database.
+      AuthzError("create table functional_avro.newtable as " +
+          "select * from functional.alltypesagg",
+          "User '%s' does not have privileges to execute 'INSERT' on: " +
+          "functional_avro.newtable");
+    } finally {
+      sentryService.dropRole(USER, roleName, true);
+      ctx_.catalog.reset();
+    }
+  }
+
   private void TestWithIncorrectConfig(AuthorizationConfig authzConfig, User 
user)
       throws ImpalaException {
     Frontend fe = new Frontend(authzConfig, ctx_.catalog);

http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/fe/src/test/java/org/apache/impala/analysis/ParserTest.java
----------------------------------------------------------------------
diff --git a/fe/src/test/java/org/apache/impala/analysis/ParserTest.java 
b/fe/src/test/java/org/apache/impala/analysis/ParserTest.java
index aefa8e2..e61266e 100644
--- a/fe/src/test/java/org/apache/impala/analysis/ParserTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/ParserTest.java
@@ -3581,6 +3581,11 @@ public class ParserTest extends FrontendTestBase {
       ParsesOk(String.format("%s REFRESH ON DATABASE foo %s myRole", 
formatStr));
       ParsesOk(String.format("%s REFRESH ON TABLE foo %s myRole", formatStr));
 
+      // CREATE privilege.
+      ParsesOk(String.format("%s CREATE ON SERVER %s myRole", formatStr));
+      ParsesOk(String.format("%s CREATE ON SERVER foo %s myRole", formatStr));
+      ParsesOk(String.format("%s CREATE ON DATABASE foo %s myRole", 
formatStr));
+
       // Server scope does not accept a name.
       ParsesOk(String.format("%s ALL ON SERVER %s myRole", formatStr));
       ParsesOk(String.format("%s INSERT ON SERVER %s myRole", formatStr));

http://git-wip-us.apache.org/repos/asf/impala/blob/ef589727/fe/src/test/resources/authz-policy.ini.template
----------------------------------------------------------------------
diff --git a/fe/src/test/resources/authz-policy.ini.template 
b/fe/src/test/resources/authz-policy.ini.template
index f3c7c1f..aeb3911 100644
--- a/fe/src/test/resources/authz-policy.ini.template
+++ b/fe/src/test/resources/authz-policy.ini.template
@@ -26,7 +26,8 @@ ${USER} = all_tpch, all_newdb, all_functional_seq_snap, 
select_tpcds,\
           insert_parquet, new_table_uri, tpch_data_uri, 
select_column_level_functional,\
           select_column_level_functional_avro, upper_case_uri,\
           refresh_functional_text_lzo, refresh_functional_alltypesagg,\
-          refresh_functional_view_view
+          refresh_functional_view_view, insert_functional_text_lzo,\
+          create_functional_text_lzo, libtestudfs_uri
 auth_to_local_group = test_role
 server_admin = all_server
 
@@ -54,6 +55,8 @@ refresh_functional_alltypesagg =\
     server=server1->db=functional->table=alltypesagg->action=refresh
 refresh_functional_view_view =\
     server=server1->db=functional->table=view_view->action=refresh
+insert_functional_text_lzo = 
server=server1->db=functional_text_lzo->action=insert
+create_functional_text_lzo = 
server=server1->db=functional_text_lzo->action=create
 select_column_level_functional =\
     
server=server1->db=functional->table=alltypessmall->column=id->action=select,\
     
server=server1->db=functional->table=alltypessmall->column=int_col->action=select,\
@@ -80,6 +83,7 @@ select_column_level_functional_avro =\
 new_table_uri = 
server=server1->uri=hdfs://localhost:20500/test-warehouse/new_table
 tpch_data_uri = 
server=server1->uri=hdfs://localhost:20500/test-warehouse/tpch.lineitem
 upper_case_uri = 
server=server1->uri=hdfs://localhost:20500/test-warehouse/UPPER_CASE
+libtestudfs_uri = 
server=server1->uri=hdfs://localhost:20500/test-warehouse/libTestUdfs.so
 
 # This section allows for an admin specified mapping of users -> groups rather 
than using
 # the built-in hadoop group mapping. This section is only applicable if using 
the

Reply via email to