You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sentry.apache.org by sr...@apache.org on 2014/08/04 22:23:41 UTC

[1/2] SENTRY-327: Support auth admin delegation via SQL construct 'with grant option' ( Xiaomeng Huang/ Dapeng Sun via Sravya Tirukkovalur)

Repository: incubator-sentry
Updated Branches:
  refs/heads/master 46c506d96 -> 7b17cef73


http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java
index 7e1ae58..985a73d 100644
--- a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java
+++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java
@@ -19,6 +19,7 @@
 package org.apache.sentry.provider.db.service.persistent;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
@@ -32,13 +33,16 @@ import org.apache.commons.io.FileUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.sentry.core.model.db.AccessConstants;
 import org.apache.sentry.provider.db.SentryAlreadyExistsException;
+import org.apache.sentry.provider.db.SentryGrantDeniedException;
 import org.apache.sentry.provider.db.SentryNoSuchObjectException;
 import org.apache.sentry.provider.db.service.model.MSentryPrivilege;
 import org.apache.sentry.provider.db.service.model.MSentryRole;
 import org.apache.sentry.provider.db.service.thrift.TSentryActiveRoleSet;
 import org.apache.sentry.provider.db.service.thrift.TSentryAuthorizable;
+import org.apache.sentry.provider.db.service.thrift.TSentryGrantOption;
 import org.apache.sentry.provider.db.service.thrift.TSentryGroup;
 import org.apache.sentry.provider.db.service.thrift.TSentryPrivilege;
+import org.apache.sentry.provider.file.PolicyFile;
 import org.apache.sentry.service.thrift.ServiceConstants.ServerConfig;
 import org.junit.After;
 import org.junit.Before;
@@ -52,6 +56,9 @@ public class TestSentryStore {
 
   private File dataDir;
   private SentryStore sentryStore;
+  private String[] adminGroups = {"adminGroup1"};
+  private PolicyFile policyFile;
+  private File policyFilePath;
 
   @Before
   public void setup() throws Exception {
@@ -60,7 +67,18 @@ public class TestSentryStore {
     conf.set(ServerConfig.SENTRY_VERIFY_SCHEM_VERSION, "false");
     conf.set(ServerConfig.SENTRY_STORE_JDBC_URL,
         "jdbc:derby:;databaseName=" + dataDir.getPath() + ";create=true");
+    conf.setStrings(ServerConfig.ADMIN_GROUPS, adminGroups);
+    conf.set(ServerConfig.SENTRY_STORE_GROUP_MAPPING,
+        ServerConfig.SENTRY_STORE_LOCAL_GROUP_MAPPING);
+    policyFilePath = new File(dataDir, "local_policy_file.ini");
+    conf.set(ServerConfig.SENTRY_STORE_GROUP_MAPPING_RESOURCE,
+        policyFilePath.getPath());
+    policyFile = new PolicyFile();
     sentryStore = new SentryStore(conf);
+
+    String adminUser = "g1";
+    addGroupsToUser(adminUser, adminGroups);
+    writePolicyFile();
   }
 
   @After
@@ -104,13 +122,13 @@ public class TestSentryStore {
     sentryStore.createSentryRole(roleName, grantor);
     TSentryPrivilege tSentryPrivilege = new TSentryPrivilege("URI", "server1", "ALL");
     tSentryPrivilege.setURI(uri);
+    tSentryPrivilege.setGrantorPrincipal(grantor);
     sentryStore.alterSentryRoleGrantPrivilege(roleName, tSentryPrivilege);
 
     TSentryAuthorizable tSentryAuthorizable = new TSentryAuthorizable();
     tSentryAuthorizable.setUri(uri);
     tSentryAuthorizable.setServer("server1");
 
-
     Set<TSentryPrivilege> privileges =
         sentryStore.getTSentryPrivileges(new HashSet<String>(Arrays.asList(roleName)), tSentryAuthorizable);
 
@@ -148,6 +166,7 @@ public class TestSentryStore {
     long seqId = sentryStore.createSentryRole(roleName, grantor).getSequenceId();
     TSentryPrivilege sentryPrivilege = new TSentryPrivilege("Database", "server1", "all");
     sentryPrivilege.setDbName("db1");
+    sentryPrivilege.setGrantorPrincipal(grantor);
     assertEquals(seqId + 1, sentryStore.alterSentryRoleGrantPrivilege(roleName, sentryPrivilege).getSequenceId());
   }
   @Test
@@ -220,6 +239,334 @@ public class TestSentryStore {
     assertEquals(db, mPrivilege.getDbName());
     assertEquals(table, mPrivilege.getTableName());
     assertEquals(AccessConstants.INSERT, mPrivilege.getAction());
+    assertFalse(mPrivilege.getGrantOption());
+  }
+
+  @Test
+  public void testGrantRevokePrivilegeWithGrantOption() throws Exception {
+    String roleName = "test-grantOption-table";
+    String grantor = "g1";
+    String server = "server1";
+    String db = "db1";
+    String table = "tbl1";
+    TSentryGrantOption grantOption = TSentryGrantOption.TRUE;
+    long seqId = sentryStore.createSentryRole(roleName, grantor).getSequenceId();
+    TSentryPrivilege privilege = new TSentryPrivilege();
+    privilege.setPrivilegeScope("TABLE");
+    privilege.setServerName(server);
+    privilege.setDbName(db);
+    privilege.setTableName(table);
+    privilege.setAction(AccessConstants.ALL);
+    privilege.setGrantorPrincipal(grantor);
+    privilege.setCreateTime(System.currentTimeMillis());
+    privilege.setGrantOption(grantOption);
+    assertEquals(seqId + 1, sentryStore.alterSentryRoleGrantPrivilege(roleName, privilege)
+        .getSequenceId());
+    MSentryRole role = sentryStore.getMSentryRoleByName(roleName);
+    Set<MSentryPrivilege> privileges = role.getPrivileges();
+    assertEquals(privileges.toString(), 1, privileges.size());
+    assertEquals(Boolean.valueOf(privilege.getGrantOption().toString()), Iterables.get(privileges, 0).getGrantOption());
+    assertEquals(seqId + 2, sentryStore.alterSentryRoleRevokePrivilege(roleName, privilege)
+        .getSequenceId());
+    role = sentryStore.getMSentryRoleByName(roleName);
+    privileges = role.getPrivileges();
+    assertEquals(0, privileges.size());
+
+    roleName = "test-grantOption-db";
+    sentryStore.createSentryRole(roleName, grantor);
+    privilege = new TSentryPrivilege();
+    privilege.setPrivilegeScope("DATABASE");
+    privilege.setServerName(server);
+    privilege.setDbName(db);
+    privilege.setAction(AccessConstants.ALL);
+    privilege.setGrantorPrincipal(grantor);
+    privilege.setGrantOption(TSentryGrantOption.TRUE);
+    privilege.setCreateTime(System.currentTimeMillis());
+    privilege.setGrantOption(grantOption);
+    sentryStore.alterSentryRoleGrantPrivilege(roleName, privilege);
+    role = sentryStore.getMSentryRoleByName(roleName);
+    privileges = role.getPrivileges();
+    assertEquals(privileges.toString(), 1, privileges.size());
+
+    privilege.setAction(AccessConstants.SELECT);
+    privilege.setGrantOption(TSentryGrantOption.UNSET);
+    sentryStore.alterSentryRoleRevokePrivilege(roleName, privilege);
+    // after having ALL and revoking SELECT, we should have INSERT
+    role = sentryStore.getMSentryRoleByName(roleName);
+    privileges = role.getPrivileges();
+    assertEquals(privileges.toString(), 1, privileges.size());
+    MSentryPrivilege mPrivilege = Iterables.get(privileges, 0);
+    assertEquals(server, mPrivilege.getServerName());
+    assertEquals(db, mPrivilege.getDbName());
+    assertEquals(AccessConstants.INSERT, mPrivilege.getAction());
+  }
+
+  @Test
+  public void testGrantCheckWithGrantOption() throws Exception {
+    // 1. set local group mapping
+    // user0->group0->role0
+    // user1->group1->role1
+    // user2->group2->role2
+    // user3->group3->role3
+    // user4->group4->role4
+    String grantor = "g1";
+    String[] users = {"user0","user1","user2","user3","user4"};
+    String[] roles = {"role0","role1","role2","role3","role4"};
+    String[] groups = {"group0","group1","group2","group3","group4"};
+    for (int i = 0; i < users.length; i++) {
+      addGroupsToUser(users[i], groups[i]);
+      sentryStore.createSentryRole(roles[i], grantor);
+      Set<TSentryGroup> tGroups = Sets.newHashSet();
+      TSentryGroup tGroup = new TSentryGroup(groups[i]);
+      tGroups.add(tGroup);
+      sentryStore.alterSentryRoleAddGroups(grantor, roles[i], tGroups);
+    }
+    writePolicyFile();
+
+    // 2. g1 grant all on database db1 to role0 with grant option
+    String server = "server1";
+    String db = "db1";
+    String table = "tbl1";
+    String roleName = roles[0];
+    grantor = "g1";
+    TSentryPrivilege privilege1 = new TSentryPrivilege();
+    privilege1.setPrivilegeScope("DATABASE");
+    privilege1.setServerName(server);
+    privilege1.setDbName(db);
+    privilege1.setAction(AccessConstants.ALL);
+    privilege1.setGrantorPrincipal(grantor);
+    privilege1.setCreateTime(System.currentTimeMillis());
+    privilege1.setGrantOption(TSentryGrantOption.TRUE);
+    sentryStore.alterSentryRoleGrantPrivilege(roleName, privilege1);
+    MSentryRole role = sentryStore.getMSentryRoleByName(roleName);
+    Set<MSentryPrivilege> privileges = role.getPrivileges();
+    assertEquals(privileges.toString(), 1, privileges.size());
+
+    // 3. user0 grant select on database db1 to role1, with grant option
+    roleName = roles[1];
+    grantor = users[0];
+    TSentryPrivilege privilege2 = new TSentryPrivilege();
+    privilege2.setPrivilegeScope("DATABASE");
+    privilege2.setServerName(server);
+    privilege2.setDbName(db);
+    privilege2.setAction(AccessConstants.SELECT);
+    privilege2.setGrantorPrincipal(grantor);
+    privilege2.setCreateTime(System.currentTimeMillis());
+    privilege2.setGrantOption(TSentryGrantOption.TRUE);
+    sentryStore.alterSentryRoleGrantPrivilege(roleName, privilege2);
+
+    // 4. user0 grant all on table tb1 to role2, no grant option
+    roleName = roles[2];
+    grantor = users[0];
+    TSentryPrivilege privilege3 = new TSentryPrivilege();
+    privilege3.setPrivilegeScope("TABLE");
+    privilege3.setServerName(server);
+    privilege3.setDbName(db);
+    privilege3.setTableName(table);
+    privilege3.setAction(AccessConstants.ALL);
+    privilege3.setGrantorPrincipal(grantor);
+    privilege3.setCreateTime(System.currentTimeMillis());
+    privilege3.setGrantOption(TSentryGrantOption.FALSE);
+    sentryStore.alterSentryRoleGrantPrivilege(roleName, privilege3);
+
+    // 5. user1 has role1, no insert privilege,
+    // grant insert to role3, will throw no grant exception
+    roleName = roles[3];
+    grantor = users[1];
+    TSentryPrivilege privilege4 = new TSentryPrivilege();
+    privilege4.setPrivilegeScope("DATABASE");
+    privilege4.setServerName(server);
+    privilege4.setDbName(db);
+    privilege4.setAction(AccessConstants.INSERT);
+    privilege4.setGrantorPrincipal(grantor);
+    privilege4.setCreateTime(System.currentTimeMillis());
+    privilege4.setGrantOption(TSentryGrantOption.FALSE);
+    boolean isGrantOptionException = false;
+    try {
+      sentryStore.alterSentryRoleGrantPrivilege(roleName, privilege4);
+    } catch (SentryGrantDeniedException e) {
+      isGrantOptionException = true;
+      System.err.println(e.getMessage());
+    }
+    assertTrue(isGrantOptionException);
+
+    // 6. user2 has role2, no grant option,
+    // grant insert to role4, will throw no grant exception
+    roleName = roles[4];
+    grantor = users[2];
+    TSentryPrivilege privilege5 = new TSentryPrivilege();
+    privilege5.setPrivilegeScope("TABLE");
+    privilege5.setServerName(server);
+    privilege5.setDbName(db);
+    privilege5.setTableName(table);
+    privilege5.setAction(AccessConstants.INSERT);
+    privilege5.setGrantorPrincipal(grantor);
+    privilege5.setCreateTime(System.currentTimeMillis());
+    privilege5.setGrantOption(TSentryGrantOption.FALSE);
+    isGrantOptionException = false;
+    try {
+      sentryStore.alterSentryRoleGrantPrivilege(roleName, privilege5);
+    } catch (SentryGrantDeniedException e) {
+      isGrantOptionException = true;
+      System.err.println(e.getMessage());
+    }
+    assertTrue(isGrantOptionException);
+  }
+
+  @Test
+  public void testRevokeCheckWithGrantOption() throws Exception {
+    // 1. set local group mapping
+    // user0->group0->role0
+    // user1->group1->role1
+    // user2->group2->role2
+    String grantor = "g1";
+    String[] users = {"user0","user1","user2"};
+    String[] roles = {"role0","role1","role2"};
+    String[] groups = {"group0","group1","group2"};
+    for (int i = 0; i < users.length; i++) {
+      addGroupsToUser(users[i], groups[i]);
+      sentryStore.createSentryRole(roles[i], grantor);
+      Set<TSentryGroup> tGroups = Sets.newHashSet();
+      TSentryGroup tGroup = new TSentryGroup(groups[i]);
+      tGroups.add(tGroup);
+      sentryStore.alterSentryRoleAddGroups(grantor, roles[i], tGroups);
+    }
+    writePolicyFile();
+
+    // 2. g1 grant select on database db1 to role0, with grant option
+    String server = "server1";
+    String db = "db1";
+    String table = "tbl1";
+    String roleName = roles[0];
+    grantor = "g1";
+    TSentryPrivilege privilege1 = new TSentryPrivilege();
+    privilege1.setPrivilegeScope("DATABASE");
+    privilege1.setServerName(server);
+    privilege1.setDbName(db);
+    privilege1.setAction(AccessConstants.SELECT);
+    privilege1.setGrantorPrincipal(grantor);
+    privilege1.setCreateTime(System.currentTimeMillis());
+    privilege1.setGrantOption(TSentryGrantOption.TRUE);
+    sentryStore.alterSentryRoleGrantPrivilege(roleName, privilege1);
+    MSentryRole role = sentryStore.getMSentryRoleByName(roleName);
+    Set<MSentryPrivilege> privileges = role.getPrivileges();
+    assertEquals(privileges.toString(), 1, privileges.size());
+
+    // 3. g1 grant all on table tb1 to role1, no grant option
+    roleName = roles[1];
+    grantor = "g1";
+    TSentryPrivilege privilege2 = new TSentryPrivilege();
+    privilege2.setPrivilegeScope("TABLE");
+    privilege2.setServerName(server);
+    privilege2.setDbName(db);
+    privilege2.setTableName(table);
+    privilege2.setAction(AccessConstants.ALL);
+    privilege2.setGrantorPrincipal(grantor);
+    privilege2.setCreateTime(System.currentTimeMillis());
+    privilege2.setGrantOption(TSentryGrantOption.FALSE);
+    sentryStore.alterSentryRoleGrantPrivilege(roleName, privilege2);
+
+    // 4. g1 grant select on table tb1 to role2, no grant option
+    roleName = roles[2];
+    grantor = "g1";
+    TSentryPrivilege privilege3 = new TSentryPrivilege();
+    privilege3.setPrivilegeScope("TABLE");
+    privilege3.setServerName(server);
+    privilege3.setDbName(db);
+    privilege3.setTableName(table);
+    privilege3.setAction(AccessConstants.SELECT);
+    privilege3.setGrantorPrincipal(grantor);
+    privilege3.setCreateTime(System.currentTimeMillis());
+    privilege3.setGrantOption(TSentryGrantOption.FALSE);
+    sentryStore.alterSentryRoleGrantPrivilege(roleName, privilege3);
+
+    // 5. user1 has role1, no grant option,
+    // revoke from role2 will throw no grant exception
+    roleName = roles[2];
+    grantor = users[1];
+    privilege3.setGrantorPrincipal(grantor);
+    boolean isGrantOptionException = false;
+    try {
+      sentryStore.alterSentryRoleRevokePrivilege(roleName, privilege3);
+    } catch (SentryGrantDeniedException e) {
+      isGrantOptionException = true;
+      System.err.println(e.getMessage());
+    }
+    assertTrue(isGrantOptionException);
+
+    // 6. user0 has role0, only have select,
+    // revoke all from role1 will throw no grant exception
+    roleName = roles[1];
+    grantor = users[0];
+    privilege2.setGrantorPrincipal(grantor);
+    try {
+      sentryStore.alterSentryRoleRevokePrivilege(roleName, privilege2);
+    } catch (SentryGrantDeniedException e) {
+      isGrantOptionException = true;
+      System.err.println(e.getMessage());
+    }
+    assertTrue(isGrantOptionException);
+
+    // 7. user0 has role0, has select and grant option,
+    // revoke select from role2
+    roleName = roles[2];
+    grantor = users[0];
+    privilege3.setGrantorPrincipal(grantor);
+    sentryStore.alterSentryRoleRevokePrivilege(roleName, privilege3);
+    role = sentryStore.getMSentryRoleByName(roleName);
+    privileges = role.getPrivileges();
+    assertEquals(0, privileges.size());
+  }
+
+  @Test
+  public void testRevokeAllGrantOption() throws Exception {
+    // 1. set local group mapping
+    // user0->group0->role0
+    String grantor = "g1";
+    String[] users = {"user0"};
+    String[] roles = {"role0"};
+    String[] groups = {"group0"};
+    for (int i = 0; i < users.length; i++) {
+      addGroupsToUser(users[i], groups[i]);
+      sentryStore.createSentryRole(roles[i], grantor);
+      Set<TSentryGroup> tGroups = Sets.newHashSet();
+      TSentryGroup tGroup = new TSentryGroup(groups[i]);
+      tGroups.add(tGroup);
+      sentryStore.alterSentryRoleAddGroups(grantor, roles[i], tGroups);
+    }
+    writePolicyFile();
+
+    // 2. g1 grant select on table tb1 to role0, with grant option
+    String server = "server1";
+    String db = "db1";
+    String table = "tbl1";
+    String roleName = roles[0];
+    grantor = "g1";
+    TSentryPrivilege privilege = new TSentryPrivilege();
+    privilege.setPrivilegeScope("TABLE");
+    privilege.setServerName(server);
+    privilege.setDbName(db);
+    privilege.setTableName(table);
+    privilege.setAction(AccessConstants.SELECT);
+    privilege.setGrantorPrincipal(grantor);
+    privilege.setCreateTime(System.currentTimeMillis());
+    privilege.setGrantOption(TSentryGrantOption.TRUE);
+    sentryStore.alterSentryRoleGrantPrivilege(roleName, privilege);
+
+    // 3. g1 grant select on table tb1 to role0, no grant option
+    roleName = roles[0];
+    grantor = "g1";
+    privilege.setGrantOption(TSentryGrantOption.FALSE);
+    sentryStore.alterSentryRoleGrantPrivilege(roleName, privilege);
+
+    // 4. g1 revoke all privilege from role0
+    roleName = roles[0];
+    grantor = "g1";
+    privilege.setGrantOption(TSentryGrantOption.UNSET);
+    sentryStore.alterSentryRoleRevokePrivilege(roleName, privilege);
+    MSentryRole role = sentryStore.getMSentryRoleByName(roleName);
+    Set<MSentryPrivilege> privileges = role.getPrivileges();
+    assertEquals(privileges.toString(), 0, privileges.size());
   }
 
   @Test
@@ -519,4 +866,12 @@ public class TestSentryStore {
       }
     }
   }
+
+  protected void addGroupsToUser(String user, String... groupNames) {
+    policyFile.addGroupsToUser(user, groupNames);
+  }
+
+  protected void writePolicyFile() throws Exception {
+    policyFile.write(policyFilePath);
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryServiceIntegration.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryServiceIntegration.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryServiceIntegration.java
index e2f0a8d..5244094 100644
--- a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryServiceIntegration.java
+++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryServiceIntegration.java
@@ -24,7 +24,6 @@ import static org.junit.Assert.assertTrue;
 import java.util.Set;
 
 import org.apache.sentry.core.model.db.AccessConstants;
-import org.apache.sentry.provider.db.service.persistent.SentryStore;
 import org.apache.sentry.service.thrift.SentryServiceIntegrationBase;
 import org.junit.Test;
 
@@ -235,4 +234,57 @@ public class TestSentryServiceIntegration extends SentryServiceIntegrationBase {
     client.grantTablePrivilege(requestorUserName, roleName, "server", "db1", "table1", "ALL");
     assertEquals(1, client.listAllPrivilegesByRoleName(requestorUserName, roleName).size());
   }
+
+  @Test
+  public void testGrantRevokeWithGrantOption() throws Exception {
+    // Grant a privilege with Grant Option
+    String requestorUserName = ADMIN_USER;
+    Set<String> requestorUserGroupNames = Sets.newHashSet(ADMIN_GROUP);
+    setLocalGroupMapping(requestorUserName, requestorUserGroupNames);
+    writePolicyFile();
+    String roleName = "admin_r1";
+    boolean grantOption = true;
+    boolean withoutGrantOption = false;
+
+    client.dropRoleIfExists(requestorUserName,  roleName);
+    client.createRole(requestorUserName,  roleName);
+
+    client.grantTablePrivilege(requestorUserName, roleName, "server", "db1", "table1", "ALL", grantOption);
+    assertEquals(1, client.listAllPrivilegesByRoleName(requestorUserName, roleName).size());
+
+    // Try to revoke the privilege without grantOption and can't revoke the privilege.
+    client.revokeTablePrivilege(requestorUserName, roleName, "server", "db1", "table1", "ALL", withoutGrantOption);
+    assertEquals(1, client.listAllPrivilegesByRoleName(requestorUserName, roleName).size());
+
+    // Try to revoke the privilege with grantOption, the privilege will be revoked.
+    client.revokeTablePrivilege(requestorUserName, roleName, "server", "db1", "table1", "ALL", grantOption);
+    assertEquals(0, client.listAllPrivilegesByRoleName(requestorUserName, roleName).size());
+
+  }
+
+  @Test
+  public void testGrantTwoPrivilegeDiffInGrantOption() throws Exception {
+    // Grant a privilege with 'Grant Option'.
+    String requestorUserName = ADMIN_USER;
+    Set<String> requestorUserGroupNames = Sets.newHashSet(ADMIN_GROUP);
+    setLocalGroupMapping(requestorUserName, requestorUserGroupNames);
+    writePolicyFile();
+    String roleName = "admin_r1";
+    boolean grantOption = true;
+    boolean withoutGrantOption = false;
+
+    client.dropRoleIfExists(requestorUserName,  roleName);
+    client.createRole(requestorUserName,  roleName);
+
+    client.grantTablePrivilege(requestorUserName, roleName, "server", "db1", "table1", "ALL", grantOption);
+    assertEquals(1, client.listAllPrivilegesByRoleName(requestorUserName, roleName).size());
+
+    // Grant a privilege without 'Grant Option'.
+    client.grantTablePrivilege(requestorUserName, roleName, "server", "db1", "table1", "ALL", withoutGrantOption);
+    assertEquals(2, client.listAllPrivilegesByRoleName(requestorUserName, roleName).size());
+
+    // Use 'grantOption = null', the two privileges will be revoked.
+    client.revokeTablePrivilege(requestorUserName, roleName, "server", "db1", "table1", "ALL", null);
+    assertEquals(0, client.listAllPrivilegesByRoleName(requestorUserName, roleName).size());
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-file/pom.xml
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-file/pom.xml b/sentry-provider/sentry-provider-file/pom.xml
index b9a718c..84cdf3f 100644
--- a/sentry-provider/sentry-provider-file/pom.xml
+++ b/sentry-provider/sentry-provider-file/pom.xml
@@ -29,10 +29,6 @@ limitations under the License.
 
   <dependencies>
     <dependency>
-      <groupId>commons-lang</groupId>
-      <artifactId>commons-lang</artifactId>
-    </dependency>
-    <dependency>
       <groupId>org.apache.hadoop</groupId>
       <artifactId>hadoop-common</artifactId>
       <scope>provided</scope>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/dbprovider/TestPrivilegeWithGrantOption.java
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/dbprovider/TestPrivilegeWithGrantOption.java b/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/dbprovider/TestPrivilegeWithGrantOption.java
new file mode 100644
index 0000000..7cd667e
--- /dev/null
+++ b/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/dbprovider/TestPrivilegeWithGrantOption.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.sentry.tests.e2e.dbprovider;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.hadoop.hive.ql.plan.HiveOperation;
+import org.apache.sentry.binding.hive.conf.HiveAuthzConf;
+import org.apache.sentry.provider.db.SentryAccessDeniedException;
+import org.apache.sentry.tests.e2e.hive.DummySentryOnFailureHook;
+import org.apache.sentry.tests.e2e.hive.hiveserver.HiveServerFactory;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestPrivilegeWithGrantOption extends AbstractTestWithDbProvider {
+
+  Map<String, String > testProperties;
+
+  @Before
+  public void setup() throws Exception {
+    testProperties = new HashMap<String, String>();
+    testProperties.put(HiveAuthzConf.AuthzConfVars.AUTHZ_ONFAILURE_HOOKS.getVar(),
+        DummySentryOnFailureHook.class.getName());
+    createContext(testProperties);
+    DummySentryOnFailureHook.invoked = false;
+
+    // Do not run these tests if run with external HiveServer2
+    // This test checks for a static member, which will not
+    // be set if HiveServer2 and the test run in different JVMs
+    String hiveServer2Type = System
+        .getProperty(HiveServerFactory.HIVESERVER2_TYPE);
+    if(hiveServer2Type != null) {
+      Assume.assumeTrue(HiveServerFactory.isInternalServer(
+          HiveServerFactory.HiveServer2Type.valueOf(hiveServer2Type.trim())));
+    }
+  }
+
+  /*
+   * Admin grant DB_1 user1 without grant option, grant user3 with grant option,
+   * user1 tries to grant it to user2, but failed.
+   * user3 can grant it to user2.
+   * user1 tries to revoke, but failed.
+   * user3 tries to revoke user2, user3 and user1, user3 revoke user1 will failed.
+   * permissions for DB_1.
+   */
+  @Test
+  public void testOnGrantPrivilege() throws Exception {
+
+    // setup db objects needed by the test
+    Connection connection = context.createConnection(ADMIN1);
+    Statement statement = context.createStatement(connection);
+    statement.execute("CREATE ROLE admin_role");
+    statement.execute("GRANT ALL ON SERVER "
+        + HiveServerFactory.DEFAULT_AUTHZ_SERVER_NAME + " TO ROLE admin_role");
+    statement.execute("GRANT ROLE admin_role TO GROUP " + ADMINGROUP);
+    statement.execute("DROP DATABASE IF EXISTS db_1 CASCADE");
+    statement.execute("DROP DATABASE IF EXISTS db_2 CASCADE");
+    statement.execute("CREATE DATABASE db_1");
+    statement.execute("CREATE ROLE group1_role");
+    statement.execute("GRANT ALL ON DATABASE db_1 TO ROLE group1_role");
+    statement.execute("GRANT ROLE group1_role TO GROUP " + USERGROUP1);
+    statement.execute("CREATE ROLE group3_grant_role");
+    statement.execute("GRANT ALL ON DATABASE db_1 TO ROLE group3_grant_role WITH GRANT OPTION");
+    statement.execute("GRANT ROLE group3_grant_role TO GROUP " + USERGROUP3);
+    statement.execute("CREATE ROLE group2_role");
+    statement.execute("GRANT ROLE group2_role TO GROUP " + USERGROUP2);
+
+    connection.close();
+
+    connection = context.createConnection(USER1_1);
+    statement = context.createStatement(connection);
+
+    statement.execute("USE db_1");
+    statement.execute("CREATE TABLE foo (id int)");
+    verifyFailureHook(statement,"GRANT ALL ON DATABASE db_1 TO ROLE group2_role",HiveOperation.GRANT_PRIVILEGE,null,null,true);
+    verifyFailureHook(statement,"GRANT ALL ON DATABASE db_1 TO ROLE group2_role WITH GRANT OPTION",HiveOperation.GRANT_PRIVILEGE,null,null,true);
+    connection.close();
+
+    connection = context.createConnection(USER3_1);
+    statement = context.createStatement(connection);
+    statement.execute("GRANT ALL ON DATABASE db_1 TO ROLE group2_role");
+    connection.close();
+
+    connection = context.createConnection(USER1_1);
+    statement = context.createStatement(connection);
+    verifyFailureHook(statement,"REVOKE ALL ON Database db_1 FROM ROLE admin_role",HiveOperation.REVOKE_PRIVILEGE,null,null,true);
+    verifyFailureHook(statement,"REVOKE ALL ON Database db_1 FROM ROLE group2_role",HiveOperation.REVOKE_PRIVILEGE,null,null,true);
+    verifyFailureHook(statement,"REVOKE ALL ON Database db_1 FROM ROLE group3_grant_role",HiveOperation.REVOKE_PRIVILEGE,null,null,true);
+    connection.close();
+
+    connection = context.createConnection(USER3_1);
+    statement = context.createStatement(connection);
+    statement.execute("REVOKE ALL ON Database db_1 FROM ROLE group2_role");
+    statement.execute("REVOKE ALL ON Database db_1 FROM ROLE group3_grant_role");
+    verifyFailureHook(statement,"REVOKE ALL ON Database db_1 FROM ROLE group1_role",HiveOperation.REVOKE_PRIVILEGE,null,null,true);
+
+    connection.close();
+    context.close();
+  }
+
+  // run the given statement and verify that failure hook is invoked as expected
+  private void verifyFailureHook(Statement statement, String sqlStr, HiveOperation expectedOp,
+       String dbName, String tableName, boolean checkSentryAccessDeniedException) throws Exception {
+    // negative test case: non admin user can't create role
+    assertFalse(DummySentryOnFailureHook.invoked);
+    try {
+      statement.execute(sqlStr);
+      Assert.fail("Expected SQL exception for " + sqlStr);
+    } catch (SQLException e) {
+      assertTrue(DummySentryOnFailureHook.invoked);
+    } finally {
+      DummySentryOnFailureHook.invoked = false;
+    }
+    if (expectedOp != null) {
+      Assert.assertNotNull("Hive op is null for op: " + expectedOp, DummySentryOnFailureHook.hiveOp);
+      Assert.assertTrue(expectedOp.equals(DummySentryOnFailureHook.hiveOp));
+    }
+    if (checkSentryAccessDeniedException) {
+      Assert.assertTrue("Expected SentryDeniedException for op: " + expectedOp,
+          DummySentryOnFailureHook.exception.getCause() instanceof SentryAccessDeniedException);
+    }
+    if(tableName != null) {
+      Assert.assertNotNull("Table object is null for op: " + expectedOp, DummySentryOnFailureHook.table);
+      Assert.assertTrue(tableName.equalsIgnoreCase(DummySentryOnFailureHook.table.getName()));
+    }
+    if(dbName != null) {
+      Assert.assertNotNull("Database object is null for op: " + expectedOp, DummySentryOnFailureHook.db);
+      Assert.assertTrue(dbName.equalsIgnoreCase(DummySentryOnFailureHook.db.getName()));
+    }
+  }
+}


[2/2] git commit: SENTRY-327: Support auth admin delegation via SQL construct 'with grant option' ( Xiaomeng Huang/ Dapeng Sun via Sravya Tirukkovalur)

Posted by sr...@apache.org.
SENTRY-327: Support auth admin delegation via SQL construct 'with grant option' ( Xiaomeng Huang/ Dapeng Sun via Sravya Tirukkovalur)


Project: http://git-wip-us.apache.org/repos/asf/incubator-sentry/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-sentry/commit/7b17cef7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-sentry/tree/7b17cef7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-sentry/diff/7b17cef7

Branch: refs/heads/master
Commit: 7b17cef735e4d35070deb40c2f535c317a1b14ab
Parents: 46c506d
Author: Sravya Tirukkovalur <sr...@clouera.com>
Authored: Mon Aug 4 11:37:59 2014 -0700
Committer: Sravya Tirukkovalur <sr...@clouera.com>
Committed: Mon Aug 4 13:22:10 2014 -0700

----------------------------------------------------------------------
 .../apache/hadoop/hive/SentryHiveConstants.java |   1 -
 .../hive/ql/exec/SentryGrantRevokeTask.java     |  27 +-
 .../SentryHiveAuthorizationTaskFactoryImpl.java |   6 +-
 .../TestSentryHiveAuthorizationTaskFactory.java |  19 +-
 sentry-core/sentry-core-common/pom.xml          |   4 +
 .../sentry/core/common/utils/PathUtils.java     |  25 ++
 .../sentry/policy/db/DBWildcardPrivilege.java   |  26 +-
 .../db/service/thrift/TSentryGrantOption.java   |  48 +++
 .../db/service/thrift/TSentryPrivilege.java     | 129 ++++++-
 .../provider/db/SentryGrantDeniedException.java |  25 ++
 .../db/service/model/MSentryPrivilege.java      |  98 ++++-
 .../provider/db/service/model/package.jdo       |   4 +
 .../db/service/persistent/SentryStore.java      | 134 ++++++-
 .../thrift/SentryPolicyServiceClient.java       |  86 ++++-
 .../thrift/SentryPolicyStoreProcessor.java      |  73 ++--
 .../src/main/resources/sentry-db2-1.4.0.sql     |   5 +-
 .../src/main/resources/sentry-derby-1.4.0.sql   |   5 +-
 .../src/main/resources/sentry-mysql-1.4.0.sql   |   5 +-
 .../src/main/resources/sentry-oracle-1.4.0.sql  |   5 +-
 .../main/resources/sentry-postgres-1.4.0.sql    |   5 +-
 .../main/resources/sentry_policy_service.thrift |  12 +-
 .../service/persistent/TestSentryPrivilege.java | 168 +++++++++
 .../db/service/persistent/TestSentryStore.java  | 357 ++++++++++++++++++-
 .../thrift/TestSentryServiceIntegration.java    |  54 ++-
 sentry-provider/sentry-provider-file/pom.xml    |   4 -
 .../TestPrivilegeWithGrantOption.java           | 156 ++++++++
 26 files changed, 1368 insertions(+), 113 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-binding/sentry-binding-hive/src/main/java/org/apache/hadoop/hive/SentryHiveConstants.java
----------------------------------------------------------------------
diff --git a/sentry-binding/sentry-binding-hive/src/main/java/org/apache/hadoop/hive/SentryHiveConstants.java b/sentry-binding/sentry-binding-hive/src/main/java/org/apache/hadoop/hive/SentryHiveConstants.java
index 404d5c8..49922f9 100644
--- a/sentry-binding/sentry-binding-hive/src/main/java/org/apache/hadoop/hive/SentryHiveConstants.java
+++ b/sentry-binding/sentry-binding-hive/src/main/java/org/apache/hadoop/hive/SentryHiveConstants.java
@@ -30,5 +30,4 @@ public class SentryHiveConstants {
   public static final String PARTITION_PRIVS_NOT_SUPPORTED = "Sentry does not support partition level authorization";
   public static final String GRANT_REVOKE_NOT_SUPPORTED_ON_OBJECT = "Sentry does not allow grant/revoke on: ";
   public static final String GRANT_REVOKE_NOT_SUPPORTED_FOR_PRINCIPAL = "Sentry does not allow privileges to be granted/revoked to/from: ";
-  public static final String GRANT_OPTION_NOT_SUPPORTED = "Sentry does not allow WITH GRANT OPTION";
 }

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-binding/sentry-binding-hive/src/main/java/org/apache/hadoop/hive/ql/exec/SentryGrantRevokeTask.java
----------------------------------------------------------------------
diff --git a/sentry-binding/sentry-binding-hive/src/main/java/org/apache/hadoop/hive/ql/exec/SentryGrantRevokeTask.java b/sentry-binding/sentry-binding-hive/src/main/java/org/apache/hadoop/hive/ql/exec/SentryGrantRevokeTask.java
index 4303d88..0b26806 100644
--- a/sentry-binding/sentry-binding-hive/src/main/java/org/apache/hadoop/hive/ql/exec/SentryGrantRevokeTask.java
+++ b/sentry-binding/sentry-binding-hive/src/main/java/org/apache/hadoop/hive/ql/exec/SentryGrantRevokeTask.java
@@ -279,16 +279,17 @@ public class SentryGrantRevokeTask extends Task<DDLWork> implements Serializable
       SentryPolicyServiceClient sentryClient, String subject,
       String server, GrantDesc desc) throws SentryUserException {
     return processGrantRevokeDDL(console, sentryClient, subject,
-        server, true, desc.getPrincipals(), desc.getPrivileges(), desc.getPrivilegeSubjectDesc());
+        server, true, desc.getPrincipals(), desc.getPrivileges(),
+        desc.getPrivilegeSubjectDesc(), desc.isGrantOption());
   }
 
-
+  // For grant option, we use null to stand for revoke the privilege ignore the grant option
   private int processRevokeDDL(HiveConf conf, LogHelper console,
       SentryPolicyServiceClient sentryClient, String subject,
       String server, RevokeDesc desc) throws SentryUserException {
     return processGrantRevokeDDL(console, sentryClient, subject,
         server, false, desc.getPrincipals(), desc.getPrivileges(),
-        desc.getPrivilegeSubjectDesc());
+        desc.getPrivilegeSubjectDesc(), null);
   }
 
   private int processShowGrantDDL(HiveConf conf, LogHelper console, SentryPolicyServiceClient sentryClient,
@@ -485,8 +486,8 @@ public class SentryGrantRevokeTask extends Task<DDLWork> implements Serializable
   private static int processGrantRevokeDDL(LogHelper console,
       SentryPolicyServiceClient sentryClient, String subject, String server,
       boolean isGrant, List<PrincipalDesc> principals,
-      List<PrivilegeDesc> privileges,
-      PrivilegeObjectDesc privSubjectObjDesc) throws SentryUserException {
+      List<PrivilegeDesc> privileges, PrivilegeObjectDesc privSubjectObjDesc,
+      Boolean grantOption) throws SentryUserException {
     if (privileges == null || privileges.size() == 0) {
       console.printError("No privilege found.");
       return RETURN_CODE_FAILURE;
@@ -535,27 +536,27 @@ public class SentryGrantRevokeTask extends Task<DDLWork> implements Serializable
         for (PrivilegeDesc privDesc : privileges) {
           if (isGrant) {
             if (serverName != null) {
-              sentryClient.grantServerPrivilege(subject, princ.getName(), serverName);
+              sentryClient.grantServerPrivilege(subject, princ.getName(), serverName, grantOption);
             } else if (uriPath != null) {
-              sentryClient.grantURIPrivilege(subject, princ.getName(), server, uriPath);
+              sentryClient.grantURIPrivilege(subject, princ.getName(), server, uriPath, grantOption);
             } else if (tableName == null) {
               sentryClient.grantDatabasePrivilege(subject, princ.getName(), server, dbName,
-                  toDbSentryAction(privDesc.getPrivilege().getPriv()));
+                  toDbSentryAction(privDesc.getPrivilege().getPriv()), grantOption);
             } else {
               sentryClient.grantTablePrivilege(subject, princ.getName(), server, dbName,
-                  tableName, toSentryAction(privDesc.getPrivilege().getPriv()));
+                  tableName, toSentryAction(privDesc.getPrivilege().getPriv()), grantOption);
             }
           } else {
             if (serverName != null) {
-              sentryClient.revokeServerPrivilege(subject, princ.getName(), serverName);
+              sentryClient.revokeServerPrivilege(subject, princ.getName(), serverName, grantOption);
             } else if (uriPath != null) {
-              sentryClient.revokeURIPrivilege(subject, princ.getName(), server, uriPath);
+              sentryClient.revokeURIPrivilege(subject, princ.getName(), server, uriPath, grantOption);
             } else if (tableName == null) {
               sentryClient.revokeDatabasePrivilege(subject, princ.getName(), server, dbName,
-                  toDbSentryAction(privDesc.getPrivilege().getPriv()));
+                  toDbSentryAction(privDesc.getPrivilege().getPriv()), grantOption);
             } else {
               sentryClient.revokeTablePrivilege(subject, princ.getName(), server, dbName,
-                  tableName, toSentryAction(privDesc.getPrivilege().getPriv()));
+                  tableName, toSentryAction(privDesc.getPrivilege().getPriv()), grantOption);
             }
           }
         }

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-binding/sentry-binding-hive/src/main/java/org/apache/sentry/binding/hive/SentryHiveAuthorizationTaskFactoryImpl.java
----------------------------------------------------------------------
diff --git a/sentry-binding/sentry-binding-hive/src/main/java/org/apache/sentry/binding/hive/SentryHiveAuthorizationTaskFactoryImpl.java b/sentry-binding/sentry-binding-hive/src/main/java/org/apache/sentry/binding/hive/SentryHiveAuthorizationTaskFactoryImpl.java
index 56703a1..f38ee91 100644
--- a/sentry-binding/sentry-binding-hive/src/main/java/org/apache/sentry/binding/hive/SentryHiveAuthorizationTaskFactoryImpl.java
+++ b/sentry-binding/sentry-binding-hive/src/main/java/org/apache/sentry/binding/hive/SentryHiveAuthorizationTaskFactoryImpl.java
@@ -117,12 +117,12 @@ public class SentryHiveAuthorizationTaskFactoryImpl implements HiveAuthorization
     List<PrincipalDesc> principalDesc = analyzePrincipalListDef(
         (ASTNode) ast.getChild(1));
     SentryHivePrivilegeObjectDesc privilegeObj = null;
-
+    boolean grantOption = false;
     if (ast.getChildCount() > 2) {
       for (int i = 2; i < ast.getChildCount(); i++) {
         ASTNode astChild = (ASTNode) ast.getChild(i);
         if (astChild.getType() == HiveParser.TOK_GRANT_WITH_OPTION) {
-          throw new SemanticException(SentryHiveConstants.GRANT_OPTION_NOT_SUPPORTED);
+          grantOption = true;
         } else if (astChild.getType() == HiveParser.TOK_PRIV_OBJECT) {
           privilegeObj = analyzePrivilegeObject(astChild);
         }
@@ -150,7 +150,7 @@ public class SentryHiveAuthorizationTaskFactoryImpl implements HiveAuthorization
       }
     }
     GrantDesc grantDesc = new GrantDesc(privilegeObj, privilegeDesc,
-        principalDesc, userName, PrincipalType.USER, false);
+        principalDesc, userName, PrincipalType.USER, grantOption);
     return createTask(new DDLWork(inputs, outputs, grantDesc));
   }
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-binding/sentry-binding-hive/src/test/java/org/apache/sentry/binding/hive/TestSentryHiveAuthorizationTaskFactory.java
----------------------------------------------------------------------
diff --git a/sentry-binding/sentry-binding-hive/src/test/java/org/apache/sentry/binding/hive/TestSentryHiveAuthorizationTaskFactory.java b/sentry-binding/sentry-binding-hive/src/test/java/org/apache/sentry/binding/hive/TestSentryHiveAuthorizationTaskFactory.java
index ac0d170..129a6b5 100644
--- a/sentry-binding/sentry-binding-hive/src/test/java/org/apache/sentry/binding/hive/TestSentryHiveAuthorizationTaskFactory.java
+++ b/sentry-binding/sentry-binding-hive/src/test/java/org/apache/sentry/binding/hive/TestSentryHiveAuthorizationTaskFactory.java
@@ -152,8 +152,21 @@ public class TestSentryHiveAuthorizationTaskFactory {
    */
   @Test
   public void testGrantRoleTableWithGrantOption() throws Exception {
-    expectSemanticException("GRANT " + ALL + " ON TABLE " + TABLE + " TO ROLE " + ROLE +
-        " WITH GRANT OPTION", "Sentry does not allow WITH GRANT OPTION");
+    DDLWork work = analyze(parse("GRANT " + ALL + " ON TABLE " + TABLE + " TO ROLE " + ROLE +
+        " WITH GRANT OPTION"));
+    GrantDesc grantDesc = work.getGrantDesc();
+    Assert.assertNotNull("Grant should not be null", grantDesc);
+    for (PrincipalDesc principal : assertSize(1, grantDesc.getPrincipals())) {
+      Assert.assertEquals(PrincipalType.ROLE, principal.getType());
+      Assert.assertEquals(ROLE, principal.getName());
+    }
+    for (PrivilegeDesc privilege : assertSize(1, grantDesc.getPrivileges())) {
+      Assert.assertEquals(Privilege.ALL, privilege.getPrivilege());
+    }
+    Assert.assertTrue("Expected table", grantDesc.getPrivilegeSubjectDesc()
+        .getTable());
+    Assert.assertTrue("Expected grantOption is true", grantDesc.isGrantOption());
+    Assert.assertEquals(TABLE, grantDesc.getPrivilegeSubjectDesc().getObject());
   }
 
   /**
@@ -391,4 +404,4 @@ public class TestSentryHiveAuthorizationTaskFactory {
     Assert.assertEquals(list.toString(), size, list.size());
     return list;
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-core/sentry-core-common/pom.xml
----------------------------------------------------------------------
diff --git a/sentry-core/sentry-core-common/pom.xml b/sentry-core/sentry-core-common/pom.xml
index f373394..e12469a 100644
--- a/sentry-core/sentry-core-common/pom.xml
+++ b/sentry-core/sentry-core-common/pom.xml
@@ -29,6 +29,10 @@ limitations under the License.
 
   <dependencies>
     <dependency>
+      <groupId>commons-lang</groupId>
+      <artifactId>commons-lang</artifactId>
+    </dependency>
+    <dependency>
       <groupId>commons-cli</groupId>
       <artifactId>commons-cli</artifactId>
     </dependency>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PathUtils.java
----------------------------------------------------------------------
diff --git a/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PathUtils.java b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PathUtils.java
index 962179f..2e211f8 100644
--- a/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PathUtils.java
+++ b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PathUtils.java
@@ -20,9 +20,15 @@ import java.io.File;
 import java.net.URI;
 import java.net.URISyntaxException;
 
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.common.base.Strings;
 
 public class PathUtils {
+  private static final Logger LOGGER = LoggerFactory
+      .getLogger(PathUtils.class);
   /**
    * URI is a a special case. For URI's, /a implies /a/b.
    * Therefore the test is "/a/b".startsWith("/a");
@@ -53,6 +59,25 @@ public class PathUtils {
     return false;
   }
 
+  public static boolean impliesURI(String privilege, String request) {
+    try {
+    URI privilegeURI = new URI(new StrSubstitutor(System.getProperties()).replace(privilege));
+    URI requestURI = new URI(request);
+    if(privilegeURI.getScheme() == null || privilegeURI.getPath() == null) {
+      LOGGER.warn("Privilege URI " + request + " is not valid. Either no scheme or no path.");
+      return false;
+    }
+    if(requestURI.getScheme() == null || requestURI.getPath() == null) {
+      LOGGER.warn("Request URI " + request + " is not valid. Either no scheme or no path.");
+      return false;
+    }
+      return PathUtils.impliesURI(privilegeURI, requestURI);
+    } catch (URISyntaxException e) {
+      LOGGER.warn("Request URI " + request + " is not a URI", e);
+      return false;
+    }
+  }
+
   /**
    * The URI must be a directory as opposed to a partial
    * path entry name. To ensure this is true we add a /

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-policy/sentry-policy-db/src/main/java/org/apache/sentry/policy/db/DBWildcardPrivilege.java
----------------------------------------------------------------------
diff --git a/sentry-policy/sentry-policy-db/src/main/java/org/apache/sentry/policy/db/DBWildcardPrivilege.java b/sentry-policy/sentry-policy-db/src/main/java/org/apache/sentry/policy/db/DBWildcardPrivilege.java
index 896283c..e2de7a7 100644
--- a/sentry-policy/sentry-policy-db/src/main/java/org/apache/sentry/policy/db/DBWildcardPrivilege.java
+++ b/sentry-policy/sentry-policy-db/src/main/java/org/apache/sentry/policy/db/DBWildcardPrivilege.java
@@ -21,19 +21,16 @@
 
 package org.apache.sentry.policy.db;
 
-import static org.apache.sentry.provider.file.PolicyFileConstants.AUTHORIZABLE_JOINER;
-import static org.apache.sentry.provider.file.PolicyFileConstants.AUTHORIZABLE_SPLITTER;
+import static org.apache.sentry.provider.common.ProviderConstants.AUTHORIZABLE_JOINER;
+import static org.apache.sentry.provider.common.ProviderConstants.AUTHORIZABLE_SPLITTER;
 
-import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.List;
 
-import org.apache.commons.lang.text.StrSubstitutor;
 import org.apache.sentry.core.common.utils.PathUtils;
 import org.apache.sentry.core.model.db.AccessConstants;
 import org.apache.sentry.core.model.db.DBModelAuthorizable.AuthorizableType;
-import org.apache.sentry.policy.common.PrivilegeFactory;
 import org.apache.sentry.policy.common.Privilege;
+import org.apache.sentry.policy.common.PrivilegeFactory;
 import org.apache.sentry.provider.file.KeyValue;
 import org.apache.sentry.provider.file.PolicyFileConstants;
 import org.slf4j.Logger;
@@ -142,22 +139,7 @@ public class DBWildcardPrivilege implements Privilege {
 
   @VisibleForTesting
   protected static boolean impliesURI(String privilege, String request) {
-    try {
-    URI privilegeURI = new URI(new StrSubstitutor(System.getProperties()).replace(privilege));
-    URI requestURI = new URI(request);
-    if(privilegeURI.getScheme() == null || privilegeURI.getPath() == null) {
-      LOGGER.warn("Privilege URI " + request + " is not valid. Either no scheme or no path.");
-      return false;
-    }
-    if(requestURI.getScheme() == null || requestURI.getPath() == null) {
-      LOGGER.warn("Request URI " + request + " is not valid. Either no scheme or no path.");
-      return false;
-    }
-      return PathUtils.impliesURI(privilegeURI, requestURI);
-    } catch (URISyntaxException e) {
-      LOGGER.warn("Request URI " + request + " is not a URI", e);
-      return false;
-    }
+    return PathUtils.impliesURI(privilege, request);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/gen/thrift/gen-javabean/org/apache/sentry/provider/db/service/thrift/TSentryGrantOption.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/gen/thrift/gen-javabean/org/apache/sentry/provider/db/service/thrift/TSentryGrantOption.java b/sentry-provider/sentry-provider-db/src/gen/thrift/gen-javabean/org/apache/sentry/provider/db/service/thrift/TSentryGrantOption.java
new file mode 100644
index 0000000..856ac21
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/gen/thrift/gen-javabean/org/apache/sentry/provider/db/service/thrift/TSentryGrantOption.java
@@ -0,0 +1,48 @@
+/**
+ * Autogenerated by Thrift Compiler (0.9.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+package org.apache.sentry.provider.db.service.thrift;
+
+
+import java.util.Map;
+import java.util.HashMap;
+import org.apache.thrift.TEnum;
+
+public enum TSentryGrantOption implements org.apache.thrift.TEnum {
+  TRUE(1),
+  FALSE(0),
+  UNSET(-1);
+
+  private final int value;
+
+  private TSentryGrantOption(int value) {
+    this.value = value;
+  }
+
+  /**
+   * Get the integer value of this enum value, as defined in the Thrift IDL.
+   */
+  public int getValue() {
+    return value;
+  }
+
+  /**
+   * Find a the enum type by its integer value, as defined in the Thrift IDL.
+   * @return null if the value is not found.
+   */
+  public static TSentryGrantOption findByValue(int value) { 
+    switch (value) {
+      case 1:
+        return TRUE;
+      case 0:
+        return FALSE;
+      case -1:
+        return UNSET;
+      default:
+        return null;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/gen/thrift/gen-javabean/org/apache/sentry/provider/db/service/thrift/TSentryPrivilege.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/gen/thrift/gen-javabean/org/apache/sentry/provider/db/service/thrift/TSentryPrivilege.java b/sentry-provider/sentry-provider-db/src/gen/thrift/gen-javabean/org/apache/sentry/provider/db/service/thrift/TSentryPrivilege.java
index c48e8cc..54b6204 100644
--- a/sentry-provider/sentry-provider-db/src/gen/thrift/gen-javabean/org/apache/sentry/provider/db/service/thrift/TSentryPrivilege.java
+++ b/sentry-provider/sentry-provider-db/src/gen/thrift/gen-javabean/org/apache/sentry/provider/db/service/thrift/TSentryPrivilege.java
@@ -42,6 +42,7 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
   private static final org.apache.thrift.protocol.TField ACTION_FIELD_DESC = new org.apache.thrift.protocol.TField("action", org.apache.thrift.protocol.TType.STRING, (short)7);
   private static final org.apache.thrift.protocol.TField CREATE_TIME_FIELD_DESC = new org.apache.thrift.protocol.TField("createTime", org.apache.thrift.protocol.TType.I64, (short)8);
   private static final org.apache.thrift.protocol.TField GRANTOR_PRINCIPAL_FIELD_DESC = new org.apache.thrift.protocol.TField("grantorPrincipal", org.apache.thrift.protocol.TType.STRING, (short)9);
+  private static final org.apache.thrift.protocol.TField GRANT_OPTION_FIELD_DESC = new org.apache.thrift.protocol.TField("grantOption", org.apache.thrift.protocol.TType.I32, (short)10);
 
   private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
   static {
@@ -57,6 +58,7 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
   private String action; // required
   private long createTime; // optional
   private String grantorPrincipal; // optional
+  private TSentryGrantOption grantOption; // optional
 
   /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
   public enum _Fields implements org.apache.thrift.TFieldIdEnum {
@@ -67,7 +69,12 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
     URI((short)6, "URI"),
     ACTION((short)7, "action"),
     CREATE_TIME((short)8, "createTime"),
-    GRANTOR_PRINCIPAL((short)9, "grantorPrincipal");
+    GRANTOR_PRINCIPAL((short)9, "grantorPrincipal"),
+    /**
+     * 
+     * @see TSentryGrantOption
+     */
+    GRANT_OPTION((short)10, "grantOption");
 
     private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
 
@@ -98,6 +105,8 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
           return CREATE_TIME;
         case 9: // GRANTOR_PRINCIPAL
           return GRANTOR_PRINCIPAL;
+        case 10: // GRANT_OPTION
+          return GRANT_OPTION;
         default:
           return null;
       }
@@ -140,7 +149,7 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
   // isset id assignments
   private static final int __CREATETIME_ISSET_ID = 0;
   private byte __isset_bitfield = 0;
-  private _Fields optionals[] = {_Fields.DB_NAME,_Fields.TABLE_NAME,_Fields.URI,_Fields.CREATE_TIME,_Fields.GRANTOR_PRINCIPAL};
+  private _Fields optionals[] = {_Fields.DB_NAME,_Fields.TABLE_NAME,_Fields.URI,_Fields.CREATE_TIME,_Fields.GRANTOR_PRINCIPAL,_Fields.GRANT_OPTION};
   public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
   static {
     Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
@@ -160,6 +169,8 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
         new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64)));
     tmpMap.put(_Fields.GRANTOR_PRINCIPAL, new org.apache.thrift.meta_data.FieldMetaData("grantorPrincipal", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
         new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    tmpMap.put(_Fields.GRANT_OPTION, new org.apache.thrift.meta_data.FieldMetaData("grantOption", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.EnumMetaData(org.apache.thrift.protocol.TType.ENUM, TSentryGrantOption.class)));
     metaDataMap = Collections.unmodifiableMap(tmpMap);
     org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(TSentryPrivilege.class, metaDataMap);
   }
@@ -173,6 +184,8 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
 
     this.action = "";
 
+    this.grantOption = org.apache.sentry.provider.db.service.thrift.TSentryGrantOption.FALSE;
+
   }
 
   public TSentryPrivilege(
@@ -213,6 +226,9 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
     if (other.isSetGrantorPrincipal()) {
       this.grantorPrincipal = other.grantorPrincipal;
     }
+    if (other.isSetGrantOption()) {
+      this.grantOption = other.grantOption;
+    }
   }
 
   public TSentryPrivilege deepCopy() {
@@ -234,6 +250,8 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
     setCreateTimeIsSet(false);
     this.createTime = 0;
     this.grantorPrincipal = null;
+    this.grantOption = org.apache.sentry.provider.db.service.thrift.TSentryGrantOption.FALSE;
+
   }
 
   public String getPrivilegeScope() {
@@ -419,6 +437,37 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
     }
   }
 
+  /**
+   * 
+   * @see TSentryGrantOption
+   */
+  public TSentryGrantOption getGrantOption() {
+    return this.grantOption;
+  }
+
+  /**
+   * 
+   * @see TSentryGrantOption
+   */
+  public void setGrantOption(TSentryGrantOption grantOption) {
+    this.grantOption = grantOption;
+  }
+
+  public void unsetGrantOption() {
+    this.grantOption = null;
+  }
+
+  /** Returns true if field grantOption is set (has been assigned a value) and false otherwise */
+  public boolean isSetGrantOption() {
+    return this.grantOption != null;
+  }
+
+  public void setGrantOptionIsSet(boolean value) {
+    if (!value) {
+      this.grantOption = null;
+    }
+  }
+
   public void setFieldValue(_Fields field, Object value) {
     switch (field) {
     case PRIVILEGE_SCOPE:
@@ -485,6 +534,14 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
       }
       break;
 
+    case GRANT_OPTION:
+      if (value == null) {
+        unsetGrantOption();
+      } else {
+        setGrantOption((TSentryGrantOption)value);
+      }
+      break;
+
     }
   }
 
@@ -514,6 +571,9 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
     case GRANTOR_PRINCIPAL:
       return getGrantorPrincipal();
 
+    case GRANT_OPTION:
+      return getGrantOption();
+
     }
     throw new IllegalStateException();
   }
@@ -541,6 +601,8 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
       return isSetCreateTime();
     case GRANTOR_PRINCIPAL:
       return isSetGrantorPrincipal();
+    case GRANT_OPTION:
+      return isSetGrantOption();
     }
     throw new IllegalStateException();
   }
@@ -630,6 +692,15 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
         return false;
     }
 
+    boolean this_present_grantOption = true && this.isSetGrantOption();
+    boolean that_present_grantOption = true && that.isSetGrantOption();
+    if (this_present_grantOption || that_present_grantOption) {
+      if (!(this_present_grantOption && that_present_grantOption))
+        return false;
+      if (!this.grantOption.equals(that.grantOption))
+        return false;
+    }
+
     return true;
   }
 
@@ -677,6 +748,11 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
     if (present_grantorPrincipal)
       builder.append(grantorPrincipal);
 
+    boolean present_grantOption = true && (isSetGrantOption());
+    builder.append(present_grantOption);
+    if (present_grantOption)
+      builder.append(grantOption.getValue());
+
     return builder.toHashCode();
   }
 
@@ -768,6 +844,16 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
         return lastComparison;
       }
     }
+    lastComparison = Boolean.valueOf(isSetGrantOption()).compareTo(typedOther.isSetGrantOption());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetGrantOption()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.grantOption, typedOther.grantOption);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
     return 0;
   }
 
@@ -857,6 +943,16 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
       }
       first = false;
     }
+    if (isSetGrantOption()) {
+      if (!first) sb.append(", ");
+      sb.append("grantOption:");
+      if (this.grantOption == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.grantOption);
+      }
+      first = false;
+    }
     sb.append(")");
     return sb.toString();
   }
@@ -978,6 +1074,14 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
               org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
             }
             break;
+          case 10: // GRANT_OPTION
+            if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
+              struct.grantOption = TSentryGrantOption.findByValue(iprot.readI32());
+              struct.setGrantOptionIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
           default:
             org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
         }
@@ -1039,6 +1143,13 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
           oprot.writeFieldEnd();
         }
       }
+      if (struct.grantOption != null) {
+        if (struct.isSetGrantOption()) {
+          oprot.writeFieldBegin(GRANT_OPTION_FIELD_DESC);
+          oprot.writeI32(struct.grantOption.getValue());
+          oprot.writeFieldEnd();
+        }
+      }
       oprot.writeFieldStop();
       oprot.writeStructEnd();
     }
@@ -1075,7 +1186,10 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
       if (struct.isSetGrantorPrincipal()) {
         optionals.set(4);
       }
-      oprot.writeBitSet(optionals, 5);
+      if (struct.isSetGrantOption()) {
+        optionals.set(5);
+      }
+      oprot.writeBitSet(optionals, 6);
       if (struct.isSetDbName()) {
         oprot.writeString(struct.dbName);
       }
@@ -1091,6 +1205,9 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
       if (struct.isSetGrantorPrincipal()) {
         oprot.writeString(struct.grantorPrincipal);
       }
+      if (struct.isSetGrantOption()) {
+        oprot.writeI32(struct.grantOption.getValue());
+      }
     }
 
     @Override
@@ -1102,7 +1219,7 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
       struct.setServerNameIsSet(true);
       struct.action = iprot.readString();
       struct.setActionIsSet(true);
-      BitSet incoming = iprot.readBitSet(5);
+      BitSet incoming = iprot.readBitSet(6);
       if (incoming.get(0)) {
         struct.dbName = iprot.readString();
         struct.setDbNameIsSet(true);
@@ -1123,6 +1240,10 @@ public class TSentryPrivilege implements org.apache.thrift.TBase<TSentryPrivileg
         struct.grantorPrincipal = iprot.readString();
         struct.setGrantorPrincipalIsSet(true);
       }
+      if (incoming.get(5)) {
+        struct.grantOption = TSentryGrantOption.findByValue(iprot.readI32());
+        struct.setGrantOptionIsSet(true);
+      }
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/SentryGrantDeniedException.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/SentryGrantDeniedException.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/SentryGrantDeniedException.java
new file mode 100644
index 0000000..a470b99
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/SentryGrantDeniedException.java
@@ -0,0 +1,25 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sentry.provider.db;
+
+public class SentryGrantDeniedException extends SentryAccessDeniedException {
+  private static final long serialVersionUID = 1962330785835L;
+  public SentryGrantDeniedException(String msg) {
+    super(msg);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/MSentryPrivilege.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/MSentryPrivilege.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/MSentryPrivilege.java
index d359abc..5328fff 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/MSentryPrivilege.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/MSentryPrivilege.java
@@ -23,10 +23,9 @@ import java.util.Set;
 
 import javax.jdo.annotations.PersistenceCapable;
 
+import org.apache.sentry.core.common.utils.PathUtils;
 import org.apache.sentry.provider.db.service.persistent.SentryStore;
 
-import com.google.common.base.Strings;
-
 /**
  * Database backed Sentry Privilege. Any changes to this object
  * require re-running the maven build so DN an re-enhance.
@@ -43,6 +42,7 @@ public class MSentryPrivilege {
   private String tableName = "";
   private String URI = "";
   private String action = "";
+  private Boolean grantOption = false;
   // roles this privilege is a part of
   private Set<MSentryRole> roles;
   private long createTime;
@@ -54,16 +54,38 @@ public class MSentryPrivilege {
 
   public MSentryPrivilege(String privilegeName, String privilegeScope,
       String serverName, String dbName, String tableName, String URI,
-      String action) {
+      String action, Boolean grantOption) {
     this.privilegeScope = privilegeScope;
     this.serverName = serverName;
     this.dbName = SentryStore.toNULLCol(dbName);
     this.tableName = SentryStore.toNULLCol(tableName);
     this.URI = SentryStore.toNULLCol(URI);
     this.action = SentryStore.toNULLCol(action);
+    this.grantOption = grantOption;
     this.roles = new HashSet<MSentryRole>();
   }
 
+  public MSentryPrivilege(String privilegeName, String privilegeScope,
+      String serverName, String dbName, String tableName, String URI,
+      String action) {
+    this(privilegeName, privilegeScope, serverName, dbName, tableName,
+        URI, action, false);
+  }
+
+  public MSentryPrivilege(MSentryPrivilege other) {
+    this.privilegeScope = other.privilegeScope;
+    this.serverName = other.serverName;
+    this.dbName = SentryStore.toNULLCol(other.dbName);
+    this.tableName = SentryStore.toNULLCol(other.tableName);
+    this.URI = SentryStore.toNULLCol(other.URI);
+    this.action = SentryStore.toNULLCol(other.action);
+    this.grantOption = other.grantOption;
+    this.roles = new HashSet<MSentryRole>();
+    for (MSentryRole role : other.roles) {
+      roles.add(role);
+    }
+  }
+
   public String getServerName() {
     return serverName;
   }
@@ -128,6 +150,14 @@ public class MSentryPrivilege {
     this.privilegeScope = privilegeScope;
   }
 
+   public Boolean getGrantOption() {
+     return grantOption;
+   }
+
+   public void setGrantOption(Boolean grantOption) {
+     this.grantOption = grantOption;
+   }
+
   public void appendRole(MSentryRole role) {
     roles.add(role);
   }
@@ -144,10 +174,11 @@ public class MSentryPrivilege {
   @Override
   public String toString() {
     return "MSentryPrivilege [privilegeScope=" + privilegeScope
-        + ", serverName=" + serverName + ", dbName=" + dbName 
+        + ", serverName=" + serverName + ", dbName=" + dbName
         + ", tableName=" + tableName + ", URI=" + URI
         + ", action=" + action + ", roles=[...]" + ", createTime="
-        + createTime + ", grantorPrincipal=" + grantorPrincipal + "]";
+        + createTime + ", grantorPrincipal=" + grantorPrincipal
+        + ", grantOption=" + grantOption +"]";
   }
 
 @Override
@@ -160,6 +191,7 @@ public int hashCode() {
   result = prime * result
 		+ ((serverName == null) ? 0 : serverName.hashCode());
   result = prime * result + ((tableName == null) ? 0 : tableName.hashCode());
+  result = prime * result + ((grantOption == null) ? 0 : grantOption.hashCode());
   return result;
 }
 
@@ -197,8 +229,64 @@ public boolean equals(Object obj) {
 			return false;
 	} else if (!tableName.equals(other.tableName))
 		return false;
+	if (grantOption == null) {
+	  if (other.grantOption != null)
+	    return false;
+	} else if (!grantOption.equals(other.grantOption))
+	  return false;
 	return true;
 }
 
+  /**
+   * Return true if this privilege implies other privilege
+   * Otherwise, return false
+   * @param other, other privilege
+   */
+  public boolean implies(MSentryPrivilege other) {
+    // serverName never be null
+    if (isNULL(serverName) || isNULL(other.serverName)) {
+      return false;
+    } else if (!serverName.equals(other.serverName)) {
+      return false;
+    }
+
+    // check URI implies
+    if (!isNULL(URI) && !isNULL(other.URI)) {
+      if (!PathUtils.impliesURI(URI, other.URI)) {
+        return false;
+      }
+      // if URI is NULL, check dbName and tableName
+    } else if (isNULL(URI) && isNULL(other.URI)) {
+      if (!isNULL(dbName)) {
+        if (isNULL(other.dbName)) {
+          return false;
+        } else if (!dbName.equals(other.dbName)) {
+          return false;
+        }
+      }
+      if (!isNULL(tableName)) {
+        if (isNULL(other.tableName)) {
+          return false;
+        } else if (!tableName.equals(other.tableName)) {
+          return false;
+        }
+      }
+      // if URI is not equals, return false
+    } else {
+      return false;
+    }
+
+    // check action implies
+    if (!action.equalsIgnoreCase("*") &&
+        !action.equalsIgnoreCase(other.action)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  private boolean isNULL(String s) {
+    return SentryStore.isNULL(s);
+  }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/package.jdo
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/package.jdo b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/package.jdo
index e3f1372..b39cb18 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/package.jdo
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/package.jdo
@@ -93,6 +93,7 @@
         <field name="tableName"/>
         <field name="URI"/>
         <field name="action"/>
+        <field name="grantOption"/>
 	  </index>
       <field name="privilegeScope">  
         <column name="PRIVILEGE_SCOPE" length="40" jdbc-type="VARCHAR"/>
@@ -118,6 +119,9 @@
       <field name="grantorPrincipal">  
         <column name="GRANTOR_PRINCIPAL" length="4000" jdbc-type="VARCHAR"/>
       </field>
+      <field name="grantOption">
+        <column name="WITH_GRANT_OPTION" length="1" jdbc-type="CHAR"/>
+      </field>
       <field name="roles" mapped-by="privileges">
          <collection element-type="org.apache.sentry.provider.db.service.model.MSentryRole"/>
       </field>  

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java
index a9fe01e..33600e9 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java
@@ -40,19 +40,23 @@ import javax.jdo.Transaction;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.hadoop.conf.Configuration;
+import org.apache.sentry.SentryUserException;
 import org.apache.sentry.core.model.db.AccessConstants;
 import org.apache.sentry.core.model.db.DBModelAuthorizable.AuthorizableType;
 import org.apache.sentry.provider.common.ProviderConstants;
 import org.apache.sentry.provider.db.SentryAccessDeniedException;
 import org.apache.sentry.provider.db.SentryAlreadyExistsException;
+import org.apache.sentry.provider.db.SentryGrantDeniedException;
 import org.apache.sentry.provider.db.SentryInvalidInputException;
 import org.apache.sentry.provider.db.SentryNoSuchObjectException;
 import org.apache.sentry.provider.db.service.model.MSentryGroup;
 import org.apache.sentry.provider.db.service.model.MSentryPrivilege;
 import org.apache.sentry.provider.db.service.model.MSentryRole;
 import org.apache.sentry.provider.db.service.model.MSentryVersion;
+import org.apache.sentry.provider.db.service.thrift.SentryPolicyStoreProcessor;
 import org.apache.sentry.provider.db.service.thrift.TSentryActiveRoleSet;
 import org.apache.sentry.provider.db.service.thrift.TSentryAuthorizable;
+import org.apache.sentry.provider.db.service.thrift.TSentryGrantOption;
 import org.apache.sentry.provider.db.service.thrift.TSentryGroup;
 import org.apache.sentry.provider.db.service.thrift.TSentryPrivilege;
 import org.apache.sentry.provider.db.service.thrift.TSentryRole;
@@ -89,10 +93,12 @@ public class SentryStore {
    */
   private long commitSequenceId;
   private final PersistenceManagerFactory pmf;
+  private Configuration conf;
 
   public SentryStore(Configuration conf) throws SentryNoSuchObjectException,
   SentryAccessDeniedException {
     commitSequenceId = 0;
+    this.conf = conf;
     Properties prop = new Properties();
     prop.putAll(ServerConfig.SENTRY_STORE_DEFAULTS);
     String jdbcUrl = conf.get(ServerConfig.SENTRY_STORE_JDBC_URL, "").trim();
@@ -266,12 +272,15 @@ public class SentryStore {
   }
 
   public CommitContext alterSentryRoleGrantPrivilege(String roleName, TSentryPrivilege privilege)
-      throws SentryNoSuchObjectException, SentryInvalidInputException {
+      throws SentryUserException {
     boolean rollbackTransaction = true;
     PersistenceManager pm = null;
     roleName = trimAndLower(roleName);
     try {
       pm = openTransaction();
+      // first do grant check
+      grantOptionCheck(pm, privilege);
+
       alterSentryRoleGrantPrivilegeCore(pm, roleName, privilege);
       CommitContext commit = commitUpdateTransaction(pm);
       rollbackTransaction = false;
@@ -332,12 +341,15 @@ public class SentryStore {
   }
 
   public CommitContext alterSentryRoleRevokePrivilege(String roleName,
-      TSentryPrivilege tPrivilege) throws SentryNoSuchObjectException, SentryInvalidInputException {
+      TSentryPrivilege tPrivilege) throws SentryUserException {
     boolean rollbackTransaction = true;
     PersistenceManager pm = null;
     roleName = safeTrimLower(roleName);
     try {
       pm = openTransaction();
+      // first do revoke check
+      grantOptionCheck(pm, tPrivilege);
+
       alterSentryRoleRevokePrivilegeCore(pm, roleName, tPrivilege);
 
       CommitContext commit = commitUpdateTransaction(pm);
@@ -369,7 +381,17 @@ public class SentryStore {
         mPrivilege = (MSentryPrivilege) pm.detachCopy(mPrivilege);
       }
 
-      Set<MSentryPrivilege> privilegeGraph = Sets.newHashSet(mPrivilege);
+      Set<MSentryPrivilege> privilegeGraph = Sets.newHashSet();
+      if (mPrivilege.getGrantOption() != null) {
+        privilegeGraph.add(mPrivilege);
+      } else {
+        MSentryPrivilege mTure = new MSentryPrivilege(mPrivilege);
+        mTure.setGrantOption(true);
+        privilegeGraph.add(mTure);
+        MSentryPrivilege mFalse = new MSentryPrivilege(mPrivilege);
+        mFalse.setGrantOption(false);
+        privilegeGraph.add(mFalse);
+      }
       // Get the privilege graph
       populateChildren(Sets.newHashSet(roleName), mPrivilege, privilegeGraph);
       for (MSentryPrivilege childPriv : privilegeGraph) {
@@ -472,9 +494,10 @@ public class SentryStore {
       } else {
         filters.append(" && (dbName != \"__NULL__\" || URI != \"__NULL__\")");
       }
+
       query.setFilter(filters.toString());
       query
-          .setResult("privilegeScope, serverName, dbName, tableName, URI, action, grantorPrincipal");
+          .setResult("privilegeScope, serverName, dbName, tableName, URI, action, grantorPrincipal, grantOption");
       Set<MSentryPrivilege> privileges = new HashSet<MSentryPrivilege>();
       for (Object[] privObj : (List<Object[]>) query.execute()) {
         MSentryPrivilege priv = new MSentryPrivilege();
@@ -485,6 +508,7 @@ public class SentryStore {
         priv.setURI((String) privObj[4]);
         priv.setAction((String) privObj[5]);
         priv.setGrantorPrincipal((String) privObj[6]);
+        priv.setGrantOption((Boolean) privObj[7]);
         privileges.add(priv);
       }
       rollbackTransaction = false;
@@ -498,14 +522,22 @@ public class SentryStore {
   }
 
   private MSentryPrivilege getMSentryPrivilege(TSentryPrivilege tPriv, PersistenceManager pm) {
-    Query query = pm.newQuery(MSentryPrivilege.class);    
+    Query query = pm.newQuery(MSentryPrivilege.class);
     query.setFilter("this.serverName == \"" + toNULLCol(tPriv.getServerName()) + "\" "
 				+ "&& this.dbName == \"" + toNULLCol(tPriv.getDbName()) + "\" "
 				+ "&& this.tableName == \"" + toNULLCol(tPriv.getTableName()) + "\" "
 				+ "&& this.URI == \"" + toNULLCol(tPriv.getURI()) + "\" "
+				+ "&& this.grantOption == grantOption "
 				+ "&& this.action == \"" + toNULLCol(tPriv.getAction().toLowerCase()) + "\"");
+    query.declareParameters("Boolean grantOption");
     query.setUnique(true);
-    Object obj = query.execute();
+    Boolean grantOption = null;
+    if (tPriv.getGrantOption().equals(TSentryGrantOption.TRUE)) {
+      grantOption = true;
+    } else if (tPriv.getGrantOption().equals(TSentryGrantOption.FALSE)) {
+      grantOption = false;
+    }
+    Object obj = query.execute(grantOption);
     if (obj != null)
       return (MSentryPrivilege) obj;
     return null;
@@ -887,6 +919,21 @@ public class SentryStore {
     }
   }
 
+  private Set<MSentryRole> getRolesForGroups(PersistenceManager pm, Set<String> groups) {
+    Set<MSentryRole> result = new HashSet<MSentryRole>();
+    Query query = pm.newQuery(MSentryGroup.class);
+    query.setFilter("this.groupName == t");
+    query.declareParameters("java.lang.String t");
+    query.setUnique(true);
+    for (String group : groups) {
+      MSentryGroup sentryGroup = (MSentryGroup) query.execute(group.trim());
+      if (sentryGroup != null) {
+        result = sentryGroup.getRoles();
+      }
+    }
+    return result;
+  }
+
   public Set<String> listAllSentryPrivilegesForProvider(Set<String> groups, TSentryActiveRoleSet roleSet) throws SentryInvalidInputException {
     return listSentryPrivilegesForProvider(groups, roleSet, null);
   }
@@ -1013,6 +1060,11 @@ public class SentryStore {
     privilege.setTableName(fromNULLCol(mSentryPrivilege.getTableName()));
     privilege.setURI(fromNULLCol(mSentryPrivilege.getURI()));
     privilege.setGrantorPrincipal(mSentryPrivilege.getGrantorPrincipal());
+    if (mSentryPrivilege.getGrantOption() != null) {
+      privilege.setGrantOption(TSentryGrantOption.valueOf(mSentryPrivilege.getGrantOption().toString().toUpperCase()));
+    } else {
+      privilege.setGrantOption(TSentryGrantOption.UNSET);
+    }
     return privilege;
   }
 
@@ -1032,6 +1084,11 @@ public class SentryStore {
     mSentryPrivilege.setCreateTime(System.currentTimeMillis());
     mSentryPrivilege.setGrantorPrincipal(safeTrim(privilege.getGrantorPrincipal()));
     mSentryPrivilege.setURI(toNULLCol(safeTrim(privilege.getURI())));
+    if ( !privilege.getGrantOption().equals(TSentryGrantOption.UNSET) ) {
+      mSentryPrivilege.setGrantOption(Boolean.valueOf(privilege.getGrantOption().toString()));
+    } else {
+      mSentryPrivilege.setGrantOption(null);
+    }
     return mSentryPrivilege;
   }
   private static String safeTrim(String s) {
@@ -1281,5 +1338,68 @@ public class SentryStore {
 
   public static boolean isNULL(String s) {
 	return Strings.isNullOrEmpty(s) || s.equals(NULL_COL);
-  }  
+  }
+
+  /**
+   * Grant option check
+   * @param pm
+   * @param privilege
+   * @throws SentryUserException
+   */
+  private void grantOptionCheck(PersistenceManager pm, TSentryPrivilege privilege)
+      throws SentryUserException {
+    MSentryPrivilege mPrivilege = convertToMSentryPrivilege(privilege);
+    String grantorPrincipal = mPrivilege.getGrantorPrincipal();
+    if (grantorPrincipal == null) {
+      throw new SentryInvalidInputException("grantorPrincipal should not be null");
+    }
+    Set<String> groups = SentryPolicyStoreProcessor.getGroupsFromUserName(conf, grantorPrincipal);
+    if (groups == null || groups.isEmpty()) {
+      throw new SentryGrantDeniedException(grantorPrincipal
+          + " has no grant!");
+    }
+
+    // if grantor is in adminGroup, don't need to do check
+    Set<String> admins = getAdminGroups();
+    boolean isAdminGroup = false;
+    if (admins != null && !admins.isEmpty()) {
+      for (String g : groups) {
+        if (admins.contains(g)) {
+          isAdminGroup = true;
+          break;
+        }
+      }
+    }
+
+    if (!isAdminGroup) {
+      boolean hasGrant = false;
+      Set<MSentryRole> roles = getRolesForGroups(pm, groups);
+      if (roles != null && !roles.isEmpty()) {
+        for (MSentryRole role: roles) {
+          Set<MSentryPrivilege> privilegeSet = role.getPrivileges();
+          if (privilegeSet != null && !privilegeSet.isEmpty()) {
+            // if role has a privilege p with grant option
+            // and mPrivilege is a child privilege of p
+            for (MSentryPrivilege p : privilegeSet) {
+              if (p.getGrantOption() && p.implies(mPrivilege)) {
+                hasGrant = true;
+                break;
+              }
+            }
+          }
+        }
+      }
+
+      if (!hasGrant) {
+        throw new SentryGrantDeniedException(grantorPrincipal
+            + " has no grant!");
+      }
+    }
+  }
+
+  // get adminGroups from conf
+  private Set<String> getAdminGroups() {
+    return Sets.newHashSet(conf.getStrings(
+        ServerConfig.ADMIN_GROUPS, new String[]{}));
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyServiceClient.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyServiceClient.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyServiceClient.java
index 5fd4f8f..5b532ce 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyServiceClient.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyServiceClient.java
@@ -233,7 +233,7 @@ public class SentryPolicyServiceClient {
   }
 
   public Set<TSentryPrivilege> listAllPrivilegesByRoleName(String requestorUserName, String roleName)
-		  throws SentryUserException {
+                 throws SentryUserException {
     return listPrivilegesByRoleName(requestorUserName, roleName, null);
   }
 
@@ -284,6 +284,13 @@ public class SentryPolicyServiceClient {
         PrivilegeScope.URI, server, uri, null, null, AccessConstants.ALL);
   }
 
+  public void grantURIPrivilege(String requestorUserName,
+      String roleName, String server, String uri, Boolean grantOption)
+  throws SentryUserException {
+    grantPrivilege(requestorUserName, roleName,
+        PrivilegeScope.URI, server, uri, null, null, AccessConstants.ALL, grantOption);
+  }
+
   public void grantServerPrivilege(String requestorUserName,
       String roleName, String server)
   throws SentryUserException {
@@ -291,6 +298,13 @@ public class SentryPolicyServiceClient {
         PrivilegeScope.SERVER, server, null, null, null, AccessConstants.ALL);
   }
 
+  public void grantServerPrivilege(String requestorUserName,
+      String roleName, String server, Boolean grantOption)
+  throws SentryUserException {
+    grantPrivilege(requestorUserName, roleName,
+        PrivilegeScope.SERVER, server, null, null, null, AccessConstants.ALL, grantOption);
+  }
+
   public void grantDatabasePrivilege(String requestorUserName,
       String roleName, String server, String db, String action)
   throws SentryUserException {
@@ -298,6 +312,13 @@ public class SentryPolicyServiceClient {
         PrivilegeScope.DATABASE, server, null, db, null, action);
   }
 
+  public void grantDatabasePrivilege(String requestorUserName,
+      String roleName, String server, String db, String action, Boolean grantOption)
+  throws SentryUserException {
+    grantPrivilege(requestorUserName, roleName,
+        PrivilegeScope.DATABASE, server, null, db, null, action, grantOption);
+  }
+
   public void grantTablePrivilege(String requestorUserName,
       String roleName, String server, String db, String table, String action)
   throws SentryUserException {
@@ -306,6 +327,13 @@ public class SentryPolicyServiceClient {
         db, table, action);
   }
 
+  public void grantTablePrivilege(String requestorUserName,
+      String roleName, String server, String db, String table, String action, Boolean grantOption)
+  throws SentryUserException {
+    grantPrivilege(requestorUserName, roleName, PrivilegeScope.TABLE, server,
+        null, db, table, action, grantOption);
+  }
+
   private TSentryAuthorizable setupSentryAuthorizable(
       List<? extends Authorizable> authorizable) {
     TSentryAuthorizable tSentryAuthorizable = new TSentryAuthorizable();
@@ -328,9 +356,15 @@ public class SentryPolicyServiceClient {
     return tSentryAuthorizable;
   }
 
+  private void grantPrivilege(String requestorUserName, String roleName,
+      PrivilegeScope scope, String serverName, String uri, String db,
+      String table, String action)  throws SentryUserException {
+    grantPrivilege(requestorUserName, roleName, scope, serverName, uri,
+    db, table, action, false);
+  }
 
   private void grantPrivilege(String requestorUserName,
-      String roleName, PrivilegeScope scope, String serverName, String uri, String db, String table, String action)
+      String roleName, PrivilegeScope scope, String serverName, String uri, String db, String table, String action, Boolean grantOption)
   throws SentryUserException {
     TAlterSentryRoleGrantPrivilegeRequest request = new TAlterSentryRoleGrantPrivilegeRequest();
     request.setProtocol_version(ThriftConstants.TSENTRY_SERVICE_VERSION_CURRENT);
@@ -345,6 +379,7 @@ public class SentryPolicyServiceClient {
     privilege.setAction(action);
     privilege.setGrantorPrincipal(requestorUserName);
     privilege.setCreateTime(System.currentTimeMillis());
+    privilege.setGrantOption(convertTSentryGrantOption(grantOption));
     request.setPrivilege(privilege);
     try {
       TAlterSentryRoleGrantPrivilegeResponse response = client.alter_sentry_role_grant_privilege(request);
@@ -361,6 +396,13 @@ public class SentryPolicyServiceClient {
         PrivilegeScope.URI, server, uri, null, null, AccessConstants.ALL);
   }
 
+  public void revokeURIPrivilege(String requestorUserName,
+      String roleName, String server, String uri, Boolean grantOption)
+  throws SentryUserException {
+    revokePrivilege(requestorUserName, roleName,
+        PrivilegeScope.URI, server, uri, null, null, AccessConstants.ALL, grantOption);
+  }
+
   public void revokeServerPrivilege(String requestorUserName,
       String roleName, String server)
   throws SentryUserException {
@@ -368,6 +410,13 @@ public class SentryPolicyServiceClient {
         PrivilegeScope.SERVER, server, null, null, null, AccessConstants.ALL);
   }
 
+  public void revokeServerPrivilege(String requestorUserName,
+      String roleName, String server, Boolean grantOption)
+  throws SentryUserException {
+    revokePrivilege(requestorUserName, roleName,
+        PrivilegeScope.SERVER, server, null, null, null, AccessConstants.ALL, grantOption);
+  }
+
   public void revokeDatabasePrivilege(String requestorUserName,
       String roleName, String server, String db, String action)
   throws SentryUserException {
@@ -375,6 +424,13 @@ public class SentryPolicyServiceClient {
         PrivilegeScope.DATABASE, server, null, db, null, action);
   }
 
+  public void revokeDatabasePrivilege(String requestorUserName,
+      String roleName, String server, String db, String action, Boolean grantOption)
+  throws SentryUserException {
+    revokePrivilege(requestorUserName, roleName,
+        PrivilegeScope.DATABASE, server, null, db, null, action, grantOption);
+  }
+
   public void revokeTablePrivilege(String requestorUserName,
       String roleName, String server, String db, String table, String action)
   throws SentryUserException {
@@ -383,9 +439,23 @@ public class SentryPolicyServiceClient {
         db, table, action);
   }
 
+  public void revokeTablePrivilege(String requestorUserName,
+      String roleName, String server, String db, String table, String action, Boolean grantOption)
+  throws SentryUserException {
+    revokePrivilege(requestorUserName, roleName,
+        PrivilegeScope.TABLE, server, null,
+        db, table, action, grantOption);
+  }
+
   private void revokePrivilege(String requestorUserName,
       String roleName, PrivilegeScope scope, String serverName, String uri, String db, String table, String action)
   throws SentryUserException {
+    this.revokePrivilege(requestorUserName, roleName, scope, serverName, uri, db, table, action, false);
+  }
+
+  private void revokePrivilege(String requestorUserName, String roleName,
+      PrivilegeScope scope, String serverName, String uri, String db, String table, String action, Boolean grantOption)
+  throws SentryUserException {
     TAlterSentryRoleRevokePrivilegeRequest request = new TAlterSentryRoleRevokePrivilegeRequest();
     request.setProtocol_version(ThriftConstants.TSENTRY_SERVICE_VERSION_CURRENT);
     request.setRequestorUserName(requestorUserName);
@@ -399,6 +469,7 @@ public class SentryPolicyServiceClient {
     privilege.setAction(action);
     privilege.setGrantorPrincipal(requestorUserName);
     privilege.setCreateTime(System.currentTimeMillis());
+    privilege.setGrantOption(convertTSentryGrantOption(grantOption));
     request.setPrivilege(privilege);
     try {
       TAlterSentryRoleRevokePrivilegeResponse response = client.alter_sentry_role_revoke_privilege(request);
@@ -408,6 +479,17 @@ public class SentryPolicyServiceClient {
     }
   }
 
+  private TSentryGrantOption convertTSentryGrantOption(Boolean grantOption) {
+    if (grantOption == null) {
+      return TSentryGrantOption.UNSET;
+    } else if (grantOption.equals(true)) {
+      return TSentryGrantOption.TRUE;
+    } else if (grantOption.equals(false)) {
+      return TSentryGrantOption.FALSE;
+    }
+    return TSentryGrantOption.FALSE;
+  }
+
   public Set<String> listPrivilegesForProvider(Set<String> groups, ActiveRoleSet roleSet, Authorizable... authorizable)
   throws SentryUserException {
     TSentryActiveRoleSet thriftRoleSet = new TSentryActiveRoleSet(roleSet.isAll(), roleSet.getRoles());

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java
index 5848e30..f227a02 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java
@@ -173,8 +173,6 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface {
 
     TAlterSentryRoleGrantPrivilegeResponse response = new TAlterSentryRoleGrantPrivilegeResponse();
     try {
-      authorize(request.getRequestorUserName(),
-          getRequestorGroups(request.getRequestorUserName()));
       CommitContext commitContext = sentryStore.alterSentryRoleGrantPrivilege(request.getRoleName(),
                                     request.getPrivilege());
       response.setStatus(Status.OK());
@@ -207,18 +205,16 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface {
   (TAlterSentryRoleRevokePrivilegeRequest request) throws TException {
     TAlterSentryRoleRevokePrivilegeResponse response = new TAlterSentryRoleRevokePrivilegeResponse();
     try {
-      authorize(request.getRequestorUserName(),
-          getRequestorGroups(request.getRequestorUserName()));
       CommitContext commitContext = sentryStore.alterSentryRoleRevokePrivilege(request.getRoleName(),
                                     request.getPrivilege());
       response.setStatus(Status.OK());
       notificationHandlerInvoker.alter_sentry_role_revoke_privilege(commitContext,
           request, response);
     } catch (SentryNoSuchObjectException e) {
-      String msg = "Privilege: [server=" + request.getPrivilege().getServerName() + 
-    		  ",db=" + request.getPrivilege().getDbName() + 
-    		  ",table=" + request.getPrivilege().getTableName() + 
-    		  ",URI=" + request.getPrivilege().getURI() + 
+      String msg = "Privilege: [server=" + request.getPrivilege().getServerName() +
+    		  ",db=" + request.getPrivilege().getDbName() +
+    		  ",table=" + request.getPrivilege().getTableName() +
+    		  ",URI=" + request.getPrivilege().getURI() +
     		  ",action=" + request.getPrivilege().getAction() + "] doesn't exist.";
       LOGGER.error(msg, e);
       response.setStatus(Status.NoSuchObject(msg, e));
@@ -442,35 +438,40 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface {
   // retrieve the group mapping for the given user name
   private Set<String> getRequestorGroups(String userName)
       throws SentryUserException {
-      String groupMapping = conf.get(ServerConfig.SENTRY_STORE_GROUP_MAPPING,
-          ServerConfig.SENTRY_STORE_GROUP_MAPPING_DEFAULT);
-      String authResoruce = conf
-          .get(ServerConfig.SENTRY_STORE_GROUP_MAPPING_RESOURCE);
+    return getGroupsFromUserName(this.conf, userName);
+  }
 
-      // load the group mapping provider class
-      GroupMappingService groupMappingService;
-      try {
-        Constructor<?> constrctor = Class.forName(groupMapping).getDeclaredConstructor(
-            Configuration.class, String.class);
-        constrctor.setAccessible(true);
-        groupMappingService = (GroupMappingService) constrctor.newInstance(new Object[] { conf,
-            authResoruce });
-      } catch (NoSuchMethodException e) {
-        throw new SentryUserException("Unable to instantiate group mapping", e);
-      } catch (SecurityException e) {
-        throw new SentryUserException("Unable to instantiate group mapping", e);
-      } catch (ClassNotFoundException e) {
-        throw new SentryUserException("Unable to instantiate group mapping", e);
-      } catch (InstantiationException e) {
-        throw new SentryUserException("Unable to instantiate group mapping", e);
-      } catch (IllegalAccessException e) {
-        throw new SentryUserException("Unable to instantiate group mapping", e);
-      } catch (IllegalArgumentException e) {
-        throw new SentryUserException("Unable to instantiate group mapping", e);
-      } catch (InvocationTargetException e) {
-        throw new SentryUserException("Unable to instantiate group mapping", e);
-      }
-      return groupMappingService.getGroups(userName);
+  public static Set<String> getGroupsFromUserName(Configuration conf,
+      String userName) throws SentryUserException {
+    String groupMapping = conf.get(ServerConfig.SENTRY_STORE_GROUP_MAPPING,
+        ServerConfig.SENTRY_STORE_GROUP_MAPPING_DEFAULT);
+    String authResoruce = conf
+        .get(ServerConfig.SENTRY_STORE_GROUP_MAPPING_RESOURCE);
+
+    // load the group mapping provider class
+    GroupMappingService groupMappingService;
+    try {
+      Constructor<?> constrctor = Class.forName(groupMapping)
+          .getDeclaredConstructor(Configuration.class, String.class);
+      constrctor.setAccessible(true);
+      groupMappingService = (GroupMappingService) constrctor
+          .newInstance(new Object[] { conf, authResoruce });
+    } catch (NoSuchMethodException e) {
+      throw new SentryUserException("Unable to instantiate group mapping", e);
+    } catch (SecurityException e) {
+      throw new SentryUserException("Unable to instantiate group mapping", e);
+    } catch (ClassNotFoundException e) {
+      throw new SentryUserException("Unable to instantiate group mapping", e);
+    } catch (InstantiationException e) {
+      throw new SentryUserException("Unable to instantiate group mapping", e);
+    } catch (IllegalAccessException e) {
+      throw new SentryUserException("Unable to instantiate group mapping", e);
+    } catch (IllegalArgumentException e) {
+      throw new SentryUserException("Unable to instantiate group mapping", e);
+    } catch (InvocationTargetException e) {
+      throw new SentryUserException("Unable to instantiate group mapping", e);
+    }
+    return groupMappingService.getGroups(userName);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/main/resources/sentry-db2-1.4.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-db2-1.4.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-db2-1.4.0.sql
index 3886d29..c1a2778 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-db2-1.4.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-db2-1.4.0.sql
@@ -24,7 +24,8 @@ CREATE TABLE SENTRY_DB_PRIVILEGE
     GRANTOR_PRINCIPAL VARCHAR(4000),
     PRIVILEGE_SCOPE VARCHAR(40),
     "SERVER_NAME" VARCHAR(4000),
-    "TABLE_NAME" VARCHAR(4000)
+    "TABLE_NAME" VARCHAR(4000),
+    WITH_GRANT_OPTION CHAR(1) NOT NULL
 );
 
 ALTER TABLE SENTRY_DB_PRIVILEGE ADD CONSTRAINT SENTRY_DB_PRIVILEGE_PK PRIMARY KEY (DB_PRIVILEGE_ID);
@@ -78,7 +79,7 @@ CREATE TABLE "SENTRY_VERSION" (
 ALTER TABLE SENTRY_VERSION ADD CONSTRAINT SENTRY_VERSION_PK PRIMARY KEY (VER_ID);
 
 -- Constraints for table SENTRY_DB_PRIVILEGE for class(es) [org.apache.sentry.provider.db.service.model.MSentryPrivilege]
-CREATE UNIQUE INDEX SENTRYPRIVILEGENAME ON SENTRY_DB_PRIVILEGE ("SERVER_NAME",DB_NAME,"TABLE_NAME",URI,"ACTION");
+CREATE UNIQUE INDEX SENTRYPRIVILEGENAME ON SENTRY_DB_PRIVILEGE ("SERVER_NAME",DB_NAME,"TABLE_NAME",URI,"ACTION",WITH_GRANT_OPTION);
 
 
 -- Constraints for table SENTRY_ROLE for class(es) [org.apache.sentry.provider.db.service.model.MSentryRole]

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/main/resources/sentry-derby-1.4.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-derby-1.4.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-derby-1.4.0.sql
index 3886d29..c1a2778 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-derby-1.4.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-derby-1.4.0.sql
@@ -24,7 +24,8 @@ CREATE TABLE SENTRY_DB_PRIVILEGE
     GRANTOR_PRINCIPAL VARCHAR(4000),
     PRIVILEGE_SCOPE VARCHAR(40),
     "SERVER_NAME" VARCHAR(4000),
-    "TABLE_NAME" VARCHAR(4000)
+    "TABLE_NAME" VARCHAR(4000),
+    WITH_GRANT_OPTION CHAR(1) NOT NULL
 );
 
 ALTER TABLE SENTRY_DB_PRIVILEGE ADD CONSTRAINT SENTRY_DB_PRIVILEGE_PK PRIMARY KEY (DB_PRIVILEGE_ID);
@@ -78,7 +79,7 @@ CREATE TABLE "SENTRY_VERSION" (
 ALTER TABLE SENTRY_VERSION ADD CONSTRAINT SENTRY_VERSION_PK PRIMARY KEY (VER_ID);
 
 -- Constraints for table SENTRY_DB_PRIVILEGE for class(es) [org.apache.sentry.provider.db.service.model.MSentryPrivilege]
-CREATE UNIQUE INDEX SENTRYPRIVILEGENAME ON SENTRY_DB_PRIVILEGE ("SERVER_NAME",DB_NAME,"TABLE_NAME",URI,"ACTION");
+CREATE UNIQUE INDEX SENTRYPRIVILEGENAME ON SENTRY_DB_PRIVILEGE ("SERVER_NAME",DB_NAME,"TABLE_NAME",URI,"ACTION",WITH_GRANT_OPTION);
 
 
 -- Constraints for table SENTRY_ROLE for class(es) [org.apache.sentry.provider.db.service.model.MSentryRole]

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/main/resources/sentry-mysql-1.4.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-mysql-1.4.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-mysql-1.4.0.sql
index fee5028..d7ce57c 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-mysql-1.4.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-mysql-1.4.0.sql
@@ -34,7 +34,8 @@ CREATE TABLE `SENTRY_DB_PRIVILEGE` (
   `URI` VARCHAR(4000) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
   `ACTION` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
   `CREATE_TIME` BIGINT NOT NULL,
-  `GRANTOR_PRINCIPAL` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL
+  `GRANTOR_PRINCIPAL` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
+  `WITH_GRANT_OPTION` CHAR(1) NOT NULL
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 CREATE TABLE `SENTRY_ROLE` (
@@ -80,7 +81,7 @@ ALTER TABLE `SENTRY_VERSION`
   ADD CONSTRAINT `SENTRY_VERSION` PRIMARY KEY (`VER_ID`);
 
 ALTER TABLE `SENTRY_DB_PRIVILEGE`
-  ADD UNIQUE `SENTRY_DB_PRIV_PRIV_NAME_UNIQ` (`SERVER_NAME`,`DB_NAME`,`TABLE_NAME`,`URI`(250),`ACTION`);
+  ADD UNIQUE `SENTRY_DB_PRIV_PRIV_NAME_UNIQ` (`SERVER_NAME`,`DB_NAME`,`TABLE_NAME`,`URI`(250),`ACTION`,`WITH_GRANT_OPTION`);
 
 ALTER TABLE `SENTRY_DB_PRIVILEGE`
   ADD INDEX `SENTRY_PRIV_SERV_IDX` (`SERVER_NAME`);

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/main/resources/sentry-oracle-1.4.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-oracle-1.4.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-oracle-1.4.0.sql
index cbdd337..3f01cda 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-oracle-1.4.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-oracle-1.4.0.sql
@@ -22,7 +22,8 @@ CREATE TABLE "SENTRY_DB_PRIVILEGE" (
   "URI" VARCHAR2(4000) NULL,
   "ACTION" VARCHAR2(128) NOT NULL,
   "CREATE_TIME" NUMBER NOT NULL,
-  "GRANTOR_PRINCIPAL" VARCHAR(128) NOT NULL
+  "GRANTOR_PRINCIPAL" VARCHAR(128) NOT NULL,
+  "WITH_GRANT_OPTION" CHAR(1) NOT NULL
 );
 
 CREATE TABLE "SENTRY_ROLE" (
@@ -67,7 +68,7 @@ ALTER TABLE "SENTRY_GROUP"
 ALTER TABLE "SENTRY_VERSION" ADD CONSTRAINT "SENTRY_VERSION_PK" PRIMARY KEY ("VER_ID");
 
 ALTER TABLE "SENTRY_DB_PRIVILEGE"
-  ADD CONSTRAINT "SENTRY_DB_PRIV_PRIV_NAME_UNIQ" UNIQUE ("SERVER_NAME","DB_NAME","TABLE_NAME","URI","ACTION");
+  ADD CONSTRAINT "SENTRY_DB_PRIV_PRIV_NAME_UNIQ" UNIQUE ("SERVER_NAME","DB_NAME","TABLE_NAME","URI","ACTION","WITH_GRANT_OPTION");
 
 CREATE INDEX "SENTRY_SERV_PRIV_IDX" ON "SENTRY_DB_PRIVILEGE" ("SERVER_NAME");
 

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/main/resources/sentry-postgres-1.4.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-postgres-1.4.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-postgres-1.4.0.sql
index 5a30aa7..186c968 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-postgres-1.4.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-postgres-1.4.0.sql
@@ -34,7 +34,8 @@ CREATE TABLE "SENTRY_DB_PRIVILEGE" (
   "URI" character varying(4000) DEFAULT NULL::character varying,
   "ACTION" character varying(128) NOT NULL,
   "CREATE_TIME" BIGINT NOT NULL,
-  "GRANTOR_PRINCIPAL" VARCHAR(128) NOT NULL
+  "GRANTOR_PRINCIPAL" VARCHAR(128) NOT NULL,
+  "WITH_GRANT_OPTION" CHAR(1) NOT NULL
 );
 
 CREATE TABLE "SENTRY_ROLE" (
@@ -80,7 +81,7 @@ ALTER TABLE ONLY "SENTRY_GROUP"
 ALTER TABLE ONLY "SENTRY_VERSION" ADD CONSTRAINT "SENTRY_VERSION_PK" PRIMARY KEY ("VER_ID");
 
 ALTER TABLE ONLY "SENTRY_DB_PRIVILEGE"
-  ADD CONSTRAINT "SENTRY_DB_PRIV_PRIV_NAME_UNIQ" UNIQUE ("SERVER_NAME","DB_NAME","TABLE_NAME","URI", "ACTION");
+  ADD CONSTRAINT "SENTRY_DB_PRIV_PRIV_NAME_UNIQ" UNIQUE ("SERVER_NAME","DB_NAME","TABLE_NAME","URI", "ACTION","WITH_GRANT_OPTION");
 
 CREATE INDEX "SENTRY_PRIV_SERV_IDX" ON "SENTRY_DB_PRIVILEGE" USING btree ("SERVER_NAME");
 

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift b/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift
index eb3e73e..b14616b 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift
@@ -29,6 +29,15 @@ namespace java org.apache.sentry.provider.db.service.thrift
 namespace php sentry.provider.db.service.thrift
 namespace cpp Apache.Sentry.Provider.Db.Service.Thrift
 
+enum TSentryGrantOption {
+  TRUE = 1,
+  FALSE = 0,
+  # UNSET is used for revoke privilege, the component like 'hive'
+  # didn't support getting grant option, so use UNSET is stand
+  # for revoke both privileges with grant option and without grant
+  # option.
+  UNSET = -1
+}
 
 # Represents a Privilege in transport from the client to the server
 struct TSentryPrivilege {
@@ -39,7 +48,8 @@ struct TSentryPrivilege {
 6: optional string URI = "",
 7: required string action = "",
 8: optional i64 createTime, # Set on server side
-9: optional string grantorPrincipal # Set on server side
+9: optional string grantorPrincipal, # Set on server side
+10: optional TSentryGrantOption grantOption = TSentryGrantOption.FALSE
 }
 
 # TODO can this be deleted? it's not adding value to TAlterSentryRoleAddGroupsRequest

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7b17cef7/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryPrivilege.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryPrivilege.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryPrivilege.java
new file mode 100644
index 0000000..91d3171
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryPrivilege.java
@@ -0,0 +1,168 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.sentry.provider.db.service.persistent;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import org.apache.sentry.core.model.db.AccessConstants;
+import org.apache.sentry.provider.db.service.model.MSentryPrivilege;
+import org.junit.Test;
+
+public class TestSentryPrivilege {
+  @Test
+  public void testImpliesPrivilegePositive() throws Exception {
+    // 1.test server+database+table+action
+    MSentryPrivilege my = new MSentryPrivilege();
+    MSentryPrivilege your = new MSentryPrivilege();
+    my.setServerName("server1");
+    my.setDbName("db1");
+    my.setTableName("tb1");
+    my.setAction(AccessConstants.SELECT);
+    your.setServerName("server1");
+    your.setDbName("db1");
+    your.setTableName("tb1");
+    your.setAction(AccessConstants.SELECT);
+    assertTrue(my.implies(your));
+
+    my.setAction(AccessConstants.ALL);
+    assertTrue(my.implies(your));
+
+    my.setTableName("");
+    assertTrue(my.implies(your));
+
+    my.setDbName("");
+    assertTrue(my.implies(your));
+
+    // 2.test server+URI+action
+    my = new MSentryPrivilege();
+    your = new MSentryPrivilege();
+    my.setServerName("server1");
+    my.setAction(AccessConstants.ALL);
+    your.setServerName("server1");
+    your.setAction(AccessConstants.ALL);
+    my.setURI("hdfs://namenode:9000/path");
+    your.setURI("hdfs://namenode:9000/path");
+    assertTrue(my.implies(your));
+
+    my.setURI("hdfs://namenode:9000/path");
+    your.setURI("hdfs://namenode:9000/path/to/some/dir");
+    assertTrue(my.implies(your));
+
+    my.setURI("file:///path");
+    your.setURI("file:///path");
+    assertTrue(my.implies(your));
+
+    my.setURI("file:///path");
+    your.setURI("file:///path/to/some/dir");
+    assertTrue(my.implies(your));
+  }
+
+  @Test
+  public void testImpliesPrivilegeNegative() throws Exception {
+    // 1.test server+database+table+action
+    MSentryPrivilege my = new MSentryPrivilege();
+    MSentryPrivilege your = new MSentryPrivilege();
+    // bad action
+    my.setServerName("server1");
+    my.setDbName("db1");
+    my.setTableName("tb1");
+    my.setAction(AccessConstants.SELECT);
+    your.setServerName("server1");
+    your.setDbName("db1");
+    your.setTableName("tb1");
+    your.setAction(AccessConstants.INSERT);
+    assertFalse(my.implies(your));
+
+    // bad action
+    your.setAction(AccessConstants.ALL);
+    assertFalse(my.implies(your));
+
+    // bad table
+    your.setTableName("tb2");
+    assertFalse(my.implies(your));
+
+    // bad database
+    your.setTableName("tb1");
+    your.setDbName("db2");
+    assertFalse(my.implies(your));
+
+    // bad server
+    your.setTableName("tb1");
+    your.setDbName("db1");
+    your.setServerName("server2");
+    assertFalse(my.implies(your));
+
+    // 2.test server+URI+action
+    my = new MSentryPrivilege();
+    your = new MSentryPrivilege();
+    my.setServerName("server1");
+    my.setAction(AccessConstants.ALL);
+    your.setServerName("server2");
+    your.setAction(AccessConstants.ALL);
+
+    // relative path
+    my.setURI("hdfs://namenode:9000/path");
+    your.setURI("hdfs://namenode:9000/path/to/../../other");
+    assertFalse(my.implies(your));
+    my.setURI("file:///path");
+    your.setURI("file:///path/to/../../other");
+    assertFalse(my.implies(your));
+
+    // bad uri
+    my.setURI("blah");
+    your.setURI("hdfs://namenode:9000/path/to/some/dir");
+    assertFalse(my.implies(your));
+    my.setURI("hdfs://namenode:9000/path/to/some/dir");
+    your.setURI("blah");
+    assertFalse(my.implies(your));
+
+    // bad scheme
+    my.setURI("hdfs://namenode:9000/path");
+    your.setURI("file:///path/to/some/dir");
+    assertFalse(my.implies(your));
+    my.setURI("hdfs://namenode:9000/path");
+    your.setURI("file://namenode:9000/path/to/some/dir");
+    assertFalse(my.implies(your));
+
+    // bad hostname
+    my.setURI("hdfs://namenode1:9000/path");
+    your.setURI("hdfs://namenode2:9000/path");
+    assertFalse(my.implies(your));
+
+    // bad port
+    my.setURI("hdfs://namenode:9000/path");
+    your.setURI("hdfs://namenode:9001/path");
+    assertFalse(my.implies(your));
+
+    // bad path
+    my.setURI("hdfs://namenode:9000/path1");
+    your.setURI("hdfs://namenode:9000/path2");
+    assertFalse(my.implies(your));
+    my.setURI("file:///path1");
+    your.setURI("file:///path2");
+    assertFalse(my.implies(your));
+
+    // bad server
+    your.setServerName("server2");
+    my.setURI("hdfs://namenode:9000/path1");
+    your.setURI("hdfs://namenode:9000/path1");
+    assertFalse(my.implies(your));
+  }
+}