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 <[email protected]>
Authored: Mon Aug 4 11:37:59 2014 -0700
Committer: Sravya Tirukkovalur <[email protected]>
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));
+  }
+}

Reply via email to