Repository: sentry
Updated Branches:
  refs/heads/master 315a8913a -> 212eb0ccd


SENTRY-2162: Retrieve and list user privileges for authorization (Sergio Pena, 
reviewed by Na Li, kalyan kumar kalvagadda)


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

Branch: refs/heads/master
Commit: 212eb0ccd43c63ba1b5b4846ecaa4ae1c48c9e9d
Parents: 315a891
Author: Sergio Pena <[email protected]>
Authored: Thu May 31 17:42:42 2018 -0500
Committer: Sergio Pena <[email protected]>
Committed: Wed Jun 6 16:51:13 2018 -0500

----------------------------------------------------------------------
 .../authz/DefaultSentryAccessController.java    |  26 ++-
 sentry-service/sentry-service-api/pom.xml       |   5 +
 .../thrift/SentryPolicyServiceClient.java       |  46 ++++++
 .../SentryPolicyServiceClientDefaultImpl.java   |  58 ++++++-
 ...estSentryPolicyServiceClientDefaultImpl.java | 162 +++++++++++++++++++
 .../api/service/thrift/SentryMetrics.java       |   2 +
 .../thrift/SentryPolicyStoreProcessor.java      | 109 ++++++++++++-
 .../service/thrift/MockGroupMappingService.java |  48 ++++++
 .../thrift/TestSentryPolicyStoreProcessor.java  |  97 ++++++++++-
 9 files changed, 538 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/sentry/blob/212eb0cc/sentry-binding/sentry-binding-hive/src/main/java/org/apache/sentry/binding/hive/authz/DefaultSentryAccessController.java
----------------------------------------------------------------------
diff --git 
a/sentry-binding/sentry-binding-hive/src/main/java/org/apache/sentry/binding/hive/authz/DefaultSentryAccessController.java
 
b/sentry-binding/sentry-binding-hive/src/main/java/org/apache/sentry/binding/hive/authz/DefaultSentryAccessController.java
index fc2427c..f0b4b44 100644
--- 
a/sentry-binding/sentry-binding-hive/src/main/java/org/apache/sentry/binding/hive/authz/DefaultSentryAccessController.java
+++ 
b/sentry-binding/sentry-binding-hive/src/main/java/org/apache/sentry/binding/hive/authz/DefaultSentryAccessController.java
@@ -201,7 +201,7 @@ public class DefaultSentryAccessController extends 
SentryHiveAccessController {
   @Override
   public List<HivePrivilegeInfo> showPrivileges(HivePrincipal principal, 
HivePrivilegeObject privObj)
       throws HiveAuthzPluginException, HiveAccessControlException {
-    if (principal.getType() != HivePrincipalType.ROLE) {
+    if (principal.getType() != HivePrincipalType.ROLE && principal.getType() 
!= HivePrincipalType.USER) {
       String msg =
           SentryHiveConstants.SHOW_NOT_SUPPORTED_FOR_PRINCIPAL + 
principal.getType();
       throw new HiveAuthzPluginException(msg);
@@ -214,12 +214,28 @@ public class DefaultSentryAccessController extends 
SentryHiveAccessController {
       Set<TSentryPrivilege> tPrivilges = new HashSet<TSentryPrivilege>();
       if (authorizables != null && !authorizables.isEmpty()) {
         for (List<? extends Authorizable> authorizable : authorizables) {
-          
tPrivilges.addAll(sentryClient.listPrivilegesByRoleName(authenticator.getUserName(),
-              principal.getName(), authorizable));
+          switch (principal.getType()) {
+            case ROLE:
+              
tPrivilges.addAll(sentryClient.listPrivilegesByRoleName(authenticator.getUserName(),
+                principal.getName(), authorizable));
+              break;
+            case USER:
+              
tPrivilges.addAll(sentryClient.listPrivilegesByUserName(authenticator.getUserName(),
+                principal.getName(), authorizable));
+              break;
+          }
         }
       } else {
-        
tPrivilges.addAll(sentryClient.listPrivilegesByRoleName(authenticator.getUserName(),
-            principal.getName(), null));
+        switch (principal.getType()) {
+          case ROLE:
+            
tPrivilges.addAll(sentryClient.listPrivilegesByRoleName(authenticator.getUserName(),
+              principal.getName(), null));
+            break;
+          case USER:
+            
tPrivilges.addAll(sentryClient.listPrivilegesByUserName(authenticator.getUserName(),
+              principal.getName(), null));
+            break;
+        }
       }
 
       if (tPrivilges != null && !tPrivilges.isEmpty()) {

http://git-wip-us.apache.org/repos/asf/sentry/blob/212eb0cc/sentry-service/sentry-service-api/pom.xml
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-api/pom.xml 
b/sentry-service/sentry-service-api/pom.xml
index ba7a7ce..a64a7e7 100644
--- a/sentry-service/sentry-service-api/pom.xml
+++ b/sentry-service/sentry-service-api/pom.xml
@@ -95,6 +95,11 @@ limitations under the License.
             <groupId>org.apache.sentry</groupId>
             <artifactId>sentry-core-model-indexer</artifactId>
         </dependency>
+      <dependency>
+        <groupId>org.mockito</groupId>
+        <artifactId>mockito-all</artifactId>
+        <scope>test</scope>
+      </dependency>
     </dependencies>
 
 

http://git-wip-us.apache.org/repos/asf/sentry/blob/212eb0cc/sentry-service/sentry-service-api/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyServiceClient.java
----------------------------------------------------------------------
diff --git 
a/sentry-service/sentry-service-api/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyServiceClient.java
 
b/sentry-service/sentry-service-api/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyServiceClient.java
index 4ba7e80..6f38ed2 100644
--- 
a/sentry-service/sentry-service-api/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyServiceClient.java
+++ 
b/sentry-service/sentry-service-api/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyServiceClient.java
@@ -56,6 +56,30 @@ public interface SentryPolicyServiceClient extends 
AutoCloseable {
   Set<TSentryPrivilege> listPrivilegesByRoleName(String requestorUserName, 
String roleName,
       List<? extends Authorizable> authorizable) throws SentryUserException;
 
+  /**
+   * Gets sentry privilege objects for a given userName using the Sentry 
service.
+   *
+   * @param requestorUserName : user on whose behalf the request is issued
+   * @param userName : userName to look up
+   * @return Set of thrift sentry privilege objects
+   * @throws SentryUserException
+   */
+  Set<TSentryPrivilege> listAllPrivilegesByUserName(String requestorUserName, 
String userName)
+      throws SentryUserException;
+
+  /**
+   * Gets sentry privileges for a given userName for a specific authorizable 
object
+   * using the Sentry service.
+   *
+   * @param requestorUserName : user on whose behalf the request is issued
+   * @param userName : userName to look up
+   * @param authorizable : authorizable Hierarchy (server->db->table etc)
+   * @return Set of thrift sentry privilege objects
+   * @throws SentryUserException
+   */
+  Set<TSentryPrivilege> listPrivilegesByUserName(String requestorUserName, 
String userName,
+      List<? extends Authorizable> authorizable) throws SentryUserException;
+
   Set<TSentryRole> listAllRoles(String requestorUserName) throws 
SentryUserException;
 
   Set<TSentryRole> listUserRoles(String requestorUserName) throws 
SentryUserException;
@@ -197,6 +221,28 @@ public interface SentryPolicyServiceClient extends 
AutoCloseable {
       Set<String> groups, ActiveRoleSet roleSet) throws SentryUserException;
 
   /**
+   * Returns a list of privileges assigned to a set of users and/or groups 
available in a
+   * set of authorizable objects.
+   *
+   * @param requestorUserName The user who is requesting the list of 
privileges.
+   * @param authorizables A list of authorizable objects to look for 
privileges.
+   *                      If null, then privileges of any authorizable object 
should be returned.
+   * @param groups A list of groups to look for privileges assigned.
+   *               If null, then privileges of any group on the specified 
authorizable object
+   *               should be returned.
+   * @param users A list of users to look for privileges assigned.
+   *              If null, then privileges of any user on the specified 
authorizable object
+   *              should be returned.
+   * @param roleSet The active role the group and/or user has. If null, then 
privileges of
+   *                any role on the specified group or user should be returned.
+   * @return A list of privileges on the specified authorizable object.
+   * @throws SentryUserException In case an error occurs while getting the 
list of privileges.
+   */
+  Map<TSentryAuthorizable, TSentryPrivilegeMap> listPrivilegsbyAuthorizable(
+    String requestorUserName, Set<List<? extends Authorizable>> authorizables,
+    Set<String> groups, Set<String> users, ActiveRoleSet roleSet) throws 
SentryUserException;
+
+  /**
    * Returns the configuration value in the sentry server associated with 
propertyName, or if
    * propertyName does not exist, the defaultValue. There is no 
"requestorUserName" because this is
    * regarded as an internal interface.

http://git-wip-us.apache.org/repos/asf/sentry/blob/212eb0cc/sentry-service/sentry-service-api/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyServiceClientDefaultImpl.java
----------------------------------------------------------------------
diff --git 
a/sentry-service/sentry-service-api/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyServiceClientDefaultImpl.java
 
b/sentry-service/sentry-service-api/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyServiceClientDefaultImpl.java
index c964318..4e605ae 100644
--- 
a/sentry-service/sentry-service-api/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyServiceClientDefaultImpl.java
+++ 
b/sentry-service/sentry-service-api/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyServiceClientDefaultImpl.java
@@ -78,6 +78,15 @@ public class SentryPolicyServiceClientDefaultImpl implements 
SentryPolicyService
   }
 
   /**
+   * Sets the Client object which is usually a mock object of the Client class 
used for testing.
+   * @param client
+   */
+  @VisibleForTesting
+  void setClient(Client client) {
+    this.client = client;
+  }
+
+  /**
    * Connect to the sentry server
    *
    * @throws IOException
@@ -223,7 +232,8 @@ public class SentryPolicyServiceClientDefaultImpl 
implements SentryPolicyService
     TListSentryPrivilegesRequest request = new TListSentryPrivilegesRequest();
     
request.setProtocol_version(ThriftConstants.TSENTRY_SERVICE_VERSION_CURRENT);
     request.setRequestorUserName(requestorUserName);
-    request.setRoleName(roleName);
+    request.setRoleName(""); // 'roleName' is required but it is deprecated by 
'entityName'
+    request.setEntityName(roleName);
     if (authorizable != null && !authorizable.isEmpty()) {
       TSentryAuthorizable tSentryAuthorizable = 
setupSentryAuthorizable(authorizable);
       request.setAuthorizableHierarchy(tSentryAuthorizable);
@@ -239,6 +249,40 @@ public class SentryPolicyServiceClientDefaultImpl 
implements SentryPolicyService
   }
 
   @Override
+  public Set<TSentryPrivilege> listAllPrivilegesByUserName(String 
requestorUserName,
+                                                                    String 
userName)
+    throws SentryUserException {
+    return listPrivilegesByUserName(requestorUserName, userName, null);
+  }
+
+  @Override
+  public Set<TSentryPrivilege> listPrivilegesByUserName(String 
requestorUserName, String userName,
+                                                                  List<? 
extends Authorizable> authorizable)
+    throws SentryUserException {
+    TListSentryPrivilegesRequest request = new TListSentryPrivilegesRequest();
+    
request.setProtocol_version(ThriftConstants.TSENTRY_SERVICE_VERSION_CURRENT);
+    request.setRequestorUserName(requestorUserName);
+    request.setEntityName(userName);
+    if (authorizable != null && !authorizable.isEmpty()) {
+      TSentryAuthorizable tSentryAuthorizable = 
setupSentryAuthorizable(authorizable);
+      request.setAuthorizableHierarchy(tSentryAuthorizable);
+    }
+    TListSentryPrivilegesResponse response;
+    try {
+      response = client.list_sentry_privileges_by_user(request);
+      if (response == null) {
+        throw new SentryUserException("The Sentry server has returned a NULL 
response. "
+          + "See the Sentry server logs for more information about the 
error.");
+      }
+
+      Status.throwIfNotOk(response.getStatus());
+      return response.getPrivileges();
+    } catch (TException e) {
+      throw new SentryUserException(THRIFT_EXCEPTION_MESSAGE, e);
+    }
+  }
+
+  @Override
   public Set<TSentryRole> listAllRoles(String requestorUserName)
     throws SentryUserException {
     return listRolesByGroupName(requestorUserName, null);
@@ -876,11 +920,18 @@ public class SentryPolicyServiceClientDefaultImpl 
implements SentryPolicyService
   }
 
   @Override
+  public Map<TSentryAuthorizable, TSentryPrivilegeMap> 
listPrivilegsbyAuthorizable(
+    String requestorUserName, Set<List<? extends Authorizable>> authorizables, 
Set<String> groups,
+    ActiveRoleSet roleSet) throws SentryUserException {
+    return listPrivilegsbyAuthorizable(requestorUserName, authorizables, 
groups, null, roleSet);
+  }
+
+  @Override
   public Map<TSentryAuthorizable, TSentryPrivilegeMap> 
listPrivilegsbyAuthorizable
     (
       String requestorUserName,
       Set<List<? extends Authorizable>> authorizables, Set<String> groups,
-      ActiveRoleSet roleSet) throws SentryUserException {
+      Set<String> users, ActiveRoleSet roleSet) throws SentryUserException {
     Set<TSentryAuthorizable> authSet = Sets.newTreeSet();
 
     for (List<? extends Authorizable> authorizableHierarchy : authorizables) {
@@ -895,6 +946,9 @@ public class SentryPolicyServiceClientDefaultImpl 
implements SentryPolicyService
     if (roleSet != null) {
       request.setRoleSet(new TSentryActiveRoleSet(roleSet.isAll(), 
roleSet.getRoles()));
     }
+    if (users != null) {
+      request.setUsers(users);
+    }
 
     try {
       TListSentryPrivilegesByAuthResponse response = client

http://git-wip-us.apache.org/repos/asf/sentry/blob/212eb0cc/sentry-service/sentry-service-api/src/test/java/org/apache/sentry/api/service/thrift/TestSentryPolicyServiceClientDefaultImpl.java
----------------------------------------------------------------------
diff --git 
a/sentry-service/sentry-service-api/src/test/java/org/apache/sentry/api/service/thrift/TestSentryPolicyServiceClientDefaultImpl.java
 
b/sentry-service/sentry-service-api/src/test/java/org/apache/sentry/api/service/thrift/TestSentryPolicyServiceClientDefaultImpl.java
new file mode 100644
index 0000000..1666e32
--- /dev/null
+++ 
b/sentry-service/sentry-service-api/src/test/java/org/apache/sentry/api/service/thrift/TestSentryPolicyServiceClientDefaultImpl.java
@@ -0,0 +1,162 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.api.service.thrift;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.sentry.api.common.Status;
+import org.apache.sentry.api.service.thrift.SentryPolicyService.Client;
+import org.apache.sentry.core.common.Authorizable;
+import org.apache.sentry.core.common.exception.SentryAccessDeniedException;
+import org.apache.sentry.core.common.exception.SentryUserException;
+import org.apache.thrift.TException;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mockito;
+
+import org.apache.sentry.core.model.db.Table;
+
+public class TestSentryPolicyServiceClientDefaultImpl {
+  private final static Client mockClient = Mockito.mock(Client.class);
+
+  private SentryPolicyServiceClientDefaultImpl sentryClient;
+
+  @Before
+  public void setup() throws IOException {
+    Mockito.reset(mockClient);
+
+    // Initialize the mock for the Sentry client
+    Configuration conf = new Configuration();
+    sentryClient = new SentryPolicyServiceClientDefaultImpl(conf, null);
+    sentryClient.setClient(mockClient);
+  }
+
+  @Test
+  public void testListAllPrivilegesByUserName() throws SentryUserException, 
TException {
+    Set<TSentryPrivilege> allPrivileges;
+
+    // Prepare some privileges for user1
+    Mockito.when(mockClient.list_sentry_privileges_by_user(
+      listSentryPrivilegesRequest("admin", "user1", null)))
+      .thenReturn(listSentryPrivilegesResponse(
+        Sets.newHashSet(
+          newSentryPrivilege("database", "db1", "t1", "select"),
+          newSentryPrivilege("database", "db1", "t2", "insert"))));
+
+    // Prepare some privileges for user2
+    Mockito.when(mockClient.list_sentry_privileges_by_user(
+      listSentryPrivilegesRequest("admin", "user2", null)))
+      .thenReturn(listSentryPrivilegesResponse(
+        Sets.newHashSet(
+          newSentryPrivilege("database", "db1", "t1", "*"),
+          newSentryPrivilege("database", "db1", "t2", "*"))));
+
+    // Request all privileges as user1
+    allPrivileges = sentryClient.listAllPrivilegesByUserName("admin", "user1");
+    assertEquals(2, allPrivileges.size());
+    assertTrue(allPrivileges.contains(newSentryPrivilege("database", "db1", 
"t1", "select")));
+    assertTrue(allPrivileges.contains(newSentryPrivilege("database", "db1", 
"t2", "insert")));
+
+    // Request all privileges as user2
+    allPrivileges = sentryClient.listAllPrivilegesByUserName("admin", "user2");
+    assertEquals(2, allPrivileges.size());
+    assertTrue(allPrivileges.contains(newSentryPrivilege("database", "db1", 
"t1", "*")));
+    assertTrue(allPrivileges.contains(newSentryPrivilege("database", "db1", 
"t2", "*")));
+
+    // Prepare some privileges for user1 as requestor
+    TListSentryPrivilegesResponse accessDeniedResp = new 
TListSentryPrivilegesResponse();
+    accessDeniedResp.setStatus(Status.AccessDenied("", new 
SentryAccessDeniedException("")));
+    Mockito.when(mockClient.list_sentry_privileges_by_user(
+      listSentryPrivilegesRequest("user1", "user2", null)))
+      .thenReturn(accessDeniedResp);
+
+    // Request all privileges as unauthorized user
+    try {
+      sentryClient.listAllPrivilegesByUserName("user1", "user2");
+      assertTrue("Requesting privileges as a unauthorized user should fail", 
false);
+    } catch (SentryAccessDeniedException e) {
+      assertTrue(true);
+    }
+  }
+
+  @Test
+  public void testListPrivilegesByUserName() throws SentryUserException, 
TException {
+    Set<TSentryPrivilege> privileges;
+
+    // Prepare some privileges for user1
+    Mockito.when(mockClient.list_sentry_privileges_by_user(
+      listSentryPrivilegesRequest("admin", "user1", Arrays.asList(new 
Table("t1")))))
+      .thenReturn(listSentryPrivilegesResponse(
+        Sets.newHashSet(
+          newSentryPrivilege("database", "db1", "t1", "select")
+        )));
+
+    // Request all privileges as user1
+    privileges = sentryClient.listPrivilegesByUserName("admin", "user1", 
Arrays.asList(new Table("t1")));
+    assertEquals(1, privileges.size());
+    assertTrue(privileges.contains(newSentryPrivilege("database", "db1", "t1", 
"select")));
+  }
+
+  private static TSentryPrivilege newSentryPrivilege(String scope, String 
dbname, String tablename, String action) {
+    TSentryPrivilege privilege = new TSentryPrivilege();
+    privilege.setPrivilegeScope(scope);
+    privilege.setDbName(dbname);
+    privilege.setTableName(tablename);
+    privilege.setAction(action);
+    return privilege;
+  }
+
+  private static TListSentryPrivilegesRequest 
listSentryPrivilegesRequest(String requestorUser, String entityName, List<? 
extends Authorizable> authorizable) {
+    return Mockito.argThat(new ArgumentMatcher<TListSentryPrivilegesRequest>() 
{
+      @Override
+      public boolean matches(Object o) {
+        if (o == null) {
+          return false;
+        }
+
+        TListSentryPrivilegesRequest request = (TListSentryPrivilegesRequest)o;
+        if (authorizable != null && !authorizable.isEmpty()) {
+          TSentryAuthorizable tSentryAuthorizable =
+            
SentryPolicyServiceClientDefaultImpl.setupSentryAuthorizable(authorizable);
+          if (!request.getAuthorizableHierarchy().equals(tSentryAuthorizable)) 
{
+            return false;
+          }
+        }
+
+        return (request.getRequestorUserName().equalsIgnoreCase(requestorUser) 
&&
+                request.getEntityName().equalsIgnoreCase(entityName));
+      }
+    });
+  }
+
+  private static TListSentryPrivilegesResponse 
listSentryPrivilegesResponse(Set<TSentryPrivilege> privileges) {
+    TListSentryPrivilegesResponse response = new 
TListSentryPrivilegesResponse();
+    response.setStatus(Status.OK());
+    response.setPrivileges(privileges);
+    return response;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/sentry/blob/212eb0cc/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetrics.java
----------------------------------------------------------------------
diff --git 
a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetrics.java
 
b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetrics.java
index 80a6343..5424bff 100644
--- 
a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetrics.java
+++ 
b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetrics.java
@@ -106,6 +106,8 @@ public final class SentryMetrics {
       name(SentryPolicyStoreProcessor.class, "list-roles-by-group"));
   final Timer listPrivilegesByRoleTimer = METRIC_REGISTRY.timer(
       name(SentryPolicyStoreProcessor.class, "list-privileges-by-role"));
+  final Timer listPrivilegesByUserTimer = METRIC_REGISTRY.timer(
+    name(SentryPolicyStoreProcessor.class, "list-privileges-by-user"));
   final Timer listPrivilegesForProviderTimer = METRIC_REGISTRY.timer(
       name(SentryPolicyStoreProcessor.class, "list-privileges-for-provider"));
   final Timer listPrivilegesByAuthorizableTimer = METRIC_REGISTRY.timer(

http://git-wip-us.apache.org/repos/asf/sentry/blob/212eb0cc/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyStoreProcessor.java
----------------------------------------------------------------------
diff --git 
a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyStoreProcessor.java
 
b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyStoreProcessor.java
index b5ef200..7f97ff7 100644
--- 
a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyStoreProcessor.java
+++ 
b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryPolicyStoreProcessor.java
@@ -793,21 +793,28 @@ public class SentryPolicyStoreProcessor implements 
SentryPolicyService.Iface {
     TSentryResponseStatus status;
     Set<TSentryPrivilege> privilegeSet = new HashSet<TSentryPrivilege>();
     String subject = request.getRequestorUserName();
+
+    // The 'roleName' parameter is deprecated in Sentry 2.x. If the new 
'entityName' is not
+    // null, then use it to get the role name otherwise fall back to the old 
'roleName' which
+    // is required to be set.
+    String roleName = (request.getEntityName() != null)
+      ? request.getEntityName() : request.getRoleName();
+
     try {
       validateClientVersion(request.getProtocol_version());
       Set<String> groups = getRequestorGroups(subject);
       Boolean admin = inAdminGroups(groups);
       if(!admin) {
         Set<String> roleNamesForGroups = 
toTrimedLower(sentryStore.getRoleNamesForGroups(groups));
-        
if(!roleNamesForGroups.contains(request.getRoleName().trim().toLowerCase())) {
+        if(!roleNamesForGroups.contains(roleName.trim().toLowerCase())) {
           throw new SentryAccessDeniedException("Access denied to " + subject);
         }
       }
       if (request.isSetAuthorizableHierarchy()) {
         TSentryAuthorizable authorizableHierarchy = 
request.getAuthorizableHierarchy();
-        privilegeSet = sentryStore.getTSentryPrivileges(SentryEntityType.ROLE, 
Sets.newHashSet(request.getRoleName()), authorizableHierarchy);
+        privilegeSet = sentryStore.getTSentryPrivileges(SentryEntityType.ROLE, 
Sets.newHashSet(roleName), authorizableHierarchy);
       } else {
-        privilegeSet = 
sentryStore.getAllTSentryPrivilegesByRoleName(request.getRoleName());
+        privilegeSet = sentryStore.getAllTSentryPrivilegesByRoleName(roleName);
       }
       response.setPrivileges(privilegeSet);
       response.setStatus(Status.OK());
@@ -835,10 +842,85 @@ public class SentryPolicyStoreProcessor implements 
SentryPolicyService.Iface {
     return response;
   }
 
+  /**
+   * This method is used to check that required parameters marked as optional 
in thrift are
+   * not null.
+   *
+   * @param param The object parameter marked as optional to check.
+   * @param message The warning message to log and return to the client.
+   * @return Null if the parameter is not null, otherwise a InvalidInput 
status that can be
+   * used to return to the client.
+   */
+  private TSentryResponseStatus checkRequiredParameter(Object param, String 
message) {
+    if (param == null) {
+      LOGGER.warn(message);
+      return Status.InvalidInput(message, new 
SentryInvalidInputException(message));
+    }
+
+    return null;
+  }
+
   @Override
   public TListSentryPrivilegesResponse list_sentry_privileges_by_user(
     TListSentryPrivilegesRequest request) throws TException {
-    return null;
+    final Timer.Context timerContext = 
sentryMetrics.listPrivilegesByUserTimer.time();
+    TListSentryPrivilegesResponse response = new 
TListSentryPrivilegesResponse();
+    Set<TSentryPrivilege> privilegeSet = new HashSet<TSentryPrivilege>();
+    String subject = request.getRequestorUserName();
+
+    // The 'entityName' parameter is made optional in thrift, so we need to 
check that is not
+    // null before proceed.
+    TSentryResponseStatus status =
+      checkRequiredParameter(request.getEntityName(), "entityName parameter 
must not be null");
+    if (status != null) {
+      response.setStatus(status);
+      return response;
+    }
+
+    String userName = request.getEntityName().trim();
+
+    try {
+      validateClientVersion(request.getProtocol_version());
+
+      // To allow listing the privileges, the requestor user must be part of 
the admins group, or
+      // the requestor user must be the same user requesting privileges for.
+      Set<String> groups = getRequestorGroups(subject);
+      Boolean admin = inAdminGroups(groups);
+      if(!admin && !userName.equalsIgnoreCase(subject)) {
+        throw new SentryAccessDeniedException("Access denied to " + subject);
+      }
+
+      if (request.isSetAuthorizableHierarchy()) {
+        TSentryAuthorizable authorizableHierarchy = 
request.getAuthorizableHierarchy();
+        privilegeSet = sentryStore.getTSentryPrivileges(SentryEntityType.USER, 
Sets.newHashSet(userName), authorizableHierarchy);
+      } else {
+        privilegeSet = sentryStore.getAllTSentryPrivilegesByUserName(userName);
+      }
+
+      response.setPrivileges(privilegeSet);
+      response.setStatus(Status.OK());
+    } catch (SentryNoSuchObjectException e) {
+      response.setPrivileges(privilegeSet);
+      String msg = "Privilege: " + request + " couldn't be retrieved.";
+      LOGGER.error(msg, e);
+      response.setStatus(Status.NoSuchObject(msg, e));
+    } catch (SentryAccessDeniedException e) {
+      LOGGER.error(e.getMessage(), e);
+      response.setStatus(Status.AccessDenied(e.getMessage(), e));
+    } catch (SentryGroupNotFoundException e) {
+      LOGGER.error(e.getMessage(), e);
+      response.setStatus(Status.AccessDenied(e.getMessage(), e));
+    } catch (SentryThriftAPIMismatchException e) {
+      LOGGER.error(e.getMessage(), e);
+      response.setStatus(Status.THRIFT_VERSION_MISMATCH(e.getMessage(), e));
+    } catch (Exception e) {
+      String msg = "Unknown error for request: " + request + ", message: " + 
e.getMessage();
+      LOGGER.error(msg, e);
+      response.setStatus(Status.RuntimeError(msg, e));
+    } finally {
+      timerContext.stop();
+    }
+    return response;
   }
 
   /**
@@ -1020,8 +1102,10 @@ public class SentryPolicyStoreProcessor implements 
SentryPolicyService.Iface {
     final Timer.Context timerContext = 
sentryMetrics.listPrivilegesByAuthorizableTimer.time();
     TListSentryPrivilegesByAuthResponse response = new 
TListSentryPrivilegesByAuthResponse();
     Map<TSentryAuthorizable, TSentryPrivilegeMap> authRoleMap = 
Maps.newHashMap();
+    Map<TSentryAuthorizable, TSentryPrivilegeMap> authUserMap = 
Maps.newHashMap();
     String subject = request.getRequestorUserName();
     Set<String> requestedGroups = request.getGroups();
+    Set<String> requestedUsers = request.getUsers();
     TSentryActiveRoleSet requestedRoleSet = request.getRoleSet();
     try {
       validateClientVersion(request.getProtocol_version());
@@ -1051,17 +1135,30 @@ public class SentryPolicyStoreProcessor implements 
SentryPolicyService.Iface {
             }
           }
         }
+
+        // disallow non-admin to lookup users that they are not part of
+        if (requestedUsers != null && !requestedUsers.isEmpty()) {
+          for (String requestedUser : requestedUsers) {
+            if (!requestedUser.equalsIgnoreCase(subject)) {
+              // if user doesn't is not requesting its own user privileges 
then raise error
+              throw new SentryAccessDeniedException("Access denied to " + 
subject);
+            }
+          }
+        }
       }
 
-      // If user is not part of any group.. return empty response
+      // Return user and role privileges found per authorizable object
       for (TSentryAuthorizable authorizable : request.getAuthorizableSet()) {
         authRoleMap.put(authorizable, sentryStore
             .listSentryPrivilegesByAuthorizable(requestedGroups,
                 request.getRoleSet(), authorizable, 
inAdminGroups(memberGroups)));
 
-        // TODO: add privileges associated with user by calling 
listSentryPrivilegesByAuthorizableForUser
+        authUserMap.put(authorizable, sentryStore
+          .listSentryPrivilegesByAuthorizableForUser(requestedUsers, 
authorizable,
+            inAdminGroups(memberGroups)));
       }
       response.setPrivilegesMapByAuth(authRoleMap);
+      response.setPrivilegesMapByAuthForUsers(authUserMap);
       response.setStatus(Status.OK());
       // TODO : Sentry - HDFS : Have to handle this
     } catch (SentryAccessDeniedException e) {

http://git-wip-us.apache.org/repos/asf/sentry/blob/212eb0cc/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/MockGroupMappingService.java
----------------------------------------------------------------------
diff --git 
a/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/MockGroupMappingService.java
 
b/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/MockGroupMappingService.java
new file mode 100644
index 0000000..adf1de7
--- /dev/null
+++ 
b/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/MockGroupMappingService.java
@@ -0,0 +1,48 @@
+/*
+ * 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.api.service.thrift;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.sentry.core.common.exception.SentryGroupNotFoundException;
+import org.apache.sentry.provider.common.GroupMappingService;
+
+public class MockGroupMappingService implements GroupMappingService {
+  private static Map<String, Set<String>> userGroups = new HashMap<>();
+
+  @SuppressWarnings("unused")
+  public MockGroupMappingService(Configuration conf, String resource) {
+  }
+
+  public static void addUserGroupMapping(String user, Set<String> groups) {
+    userGroups.put(user, groups);
+  }
+
+  @Override
+  public Set<String> getGroups(String user) throws 
SentryGroupNotFoundException {
+    if (userGroups.containsKey(user)) {
+      return userGroups.get(user);
+    }
+
+    return Collections.emptySet();
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/212eb0cc/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/TestSentryPolicyStoreProcessor.java
----------------------------------------------------------------------
diff --git 
a/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/TestSentryPolicyStoreProcessor.java
 
b/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/TestSentryPolicyStoreProcessor.java
index b028303..6bfe872 100644
--- 
a/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/TestSentryPolicyStoreProcessor.java
+++ 
b/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/TestSentryPolicyStoreProcessor.java
@@ -17,13 +17,22 @@
  */
 package org.apache.sentry.api.service.thrift;
 
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
 import com.codahale.metrics.Gauge;
+import com.google.common.collect.Sets;
+import java.util.Set;
 import org.apache.sentry.api.common.ApiConstants;
+import org.apache.sentry.api.common.Status;
 import org.apache.sentry.api.common.ThriftConstants;
+import org.apache.sentry.core.common.exception.SentryInvalidInputException;
 import org.apache.sentry.core.model.db.AccessConstants;
 import org.apache.sentry.service.common.ServiceConstants;
 import 
org.apache.sentry.core.common.exception.SentrySiteConfigurationException;
 import org.apache.sentry.provider.db.service.persistent.SentryStore;
+import org.apache.sentry.service.common.ServiceConstants.SentryEntityType;
+import org.apache.sentry.service.common.ServiceConstants.ServerConfig;
 import org.junit.Assert;
 
 import org.apache.hadoop.conf.Configuration;
@@ -147,7 +156,7 @@ public class TestSentryPolicyStoreProcessor {
     authorizable.setTable("tb1");
 
     //Check the behaviour when owner privileges feature is not configured.
-    
Assert.assertNull(sentryServiceHandler.constructOwnerPrivilege(authorizable));
+    assertNull(sentryServiceHandler.constructOwnerPrivilege(authorizable));
 
 
     //Check behaviour when DB name is not set
@@ -157,7 +166,7 @@ public class TestSentryPolicyStoreProcessor {
                     conf, sentryStore);
     authorizable = new TSentryAuthorizable("");
     authorizable.setTable("tb1");
-    
Assert.assertNull(sentryServiceHandler.constructOwnerPrivilege(authorizable));
+    assertNull(sentryServiceHandler.constructOwnerPrivilege(authorizable));
 
     //Check the behavior when DB name is set and table name is not set.
     authorizable = new TSentryAuthorizable("");
@@ -188,4 +197,88 @@ public class TestSentryPolicyStoreProcessor {
     
Assert.assertNotNull(sentryServiceHandler.constructOwnerPrivilege(authorizable));
     Assert.assertEquals(privilege, 
sentryServiceHandler.constructOwnerPrivilege(authorizable));
   }
+
+  @Test
+  public void testListPrivilegesByUserName() throws Exception {
+    MockGroupMappingService.addUserGroupMapping("admin", 
Sets.newHashSet("admin"));
+
+    Configuration conf = new Configuration();
+    conf.set(ServerConfig.SENTRY_STORE_GROUP_MAPPING, 
"org.apache.sentry.api.service.thrift.MockGroupMappingService");
+    conf.set(ServerConfig.ADMIN_GROUPS, "admin");
+
+    SentryPolicyStoreProcessor policyStoreProcessor =
+      new 
SentryPolicyStoreProcessor(ApiConstants.SentryPolicyServiceConstants.SENTRY_POLICY_SERVICE_NAME,
+        conf, sentryStore);
+    TListSentryPrivilegesResponse returnedResp;
+    TListSentryPrivilegesResponse expectedResp;
+
+    // Request privileges when user is null must throw an exception that 
entityName must not be null
+    returnedResp = 
policyStoreProcessor.list_sentry_privileges_by_user(newPrivilegesRequest("admin",
 null, null));
+    expectedResp = new TListSentryPrivilegesResponse();
+    expectedResp.setStatus(Status.InvalidInput("entityName parameter must not 
be null",
+      new SentryInvalidInputException("entityName parameter must not be 
null")));
+    Assert.assertEquals(expectedResp.getStatus().getValue(), 
returnedResp.getStatus().getValue());
+
+    // Prepare privileges for user1
+    Set<TSentryPrivilege> user1Privileges = Sets.newHashSet(
+      newSentryPrivilege("database", "db1", "t1", "*"),
+      newSentryPrivilege("database", "db1", "t2", "*"));
+    
Mockito.when(sentryStore.getAllTSentryPrivilegesByUserName("user1")).thenReturn(user1Privileges);
+
+    // Request privileges of a user as admin
+    returnedResp = 
policyStoreProcessor.list_sentry_privileges_by_user(newPrivilegesRequest("admin",
 "user1", null));
+    Assert.assertEquals(2, returnedResp.getPrivileges().size());
+    Assert.assertEquals(Status.OK(),  returnedResp.getStatus());
+    assertTrue("User should have ALL privileges in db1.t1",
+      returnedResp.getPrivileges().contains(newSentryPrivilege("database", 
"db1", "t1", "*")));
+    assertTrue("User should have ALL privileges in db1.t2",
+      returnedResp.getPrivileges().contains(newSentryPrivilege("database", 
"db1", "t2", "*")));
+
+    // Request privileges of a user as the same user
+    returnedResp = 
policyStoreProcessor.list_sentry_privileges_by_user(newPrivilegesRequest("user1",
 "user1", null));
+    Assert.assertEquals(2, returnedResp.getPrivileges().size());
+    Assert.assertEquals(Status.OK(),  returnedResp.getStatus());
+    assertTrue("User should have ALL privileges in db1.t1",
+      returnedResp.getPrivileges().contains(newSentryPrivilege("database", 
"db1", "t1", "*")));
+    assertTrue("User should have ALL privileges in db1.t2",
+      returnedResp.getPrivileges().contains(newSentryPrivilege("database", 
"db1", "t2", "*")));
+
+    // Request privileges of a user as an unauthorized user
+    returnedResp = 
policyStoreProcessor.list_sentry_privileges_by_user(newPrivilegesRequest("user2",
 "user1", null));
+    Assert.assertEquals(Status.ACCESS_DENIED.getCode(), 
returnedResp.getStatus().getValue());
+    assertNull(returnedResp.getPrivileges());
+
+    // Request privileges of a user on a specified authorizable as admin
+    TSentryAuthorizable authorizable = new TSentryAuthorizable();
+    authorizable.setServer("server1");
+    authorizable.setDb("db1");
+    authorizable.setTable("t1");
+
+    user1Privileges = Sets.newHashSet(
+      newSentryPrivilege("database", "db1", "t1", "*"));
+    
Mockito.when(sentryStore.getTSentryPrivileges(SentryEntityType.USER,Sets.newHashSet("user1"),
 authorizable)).thenReturn(user1Privileges);
+
+    returnedResp = 
policyStoreProcessor.list_sentry_privileges_by_user(newPrivilegesRequest("user1",
 "user1", authorizable));
+    Assert.assertEquals(1, returnedResp.getPrivileges().size());
+    Assert.assertEquals(Status.OK(),  returnedResp.getStatus());
+    assertTrue("User should have ALL privileges in db1.t1",
+      returnedResp.getPrivileges().contains(newSentryPrivilege("database", 
"db1", "t1", "*")));
+  }
+
+  private TListSentryPrivilegesRequest newPrivilegesRequest(String 
requestorUser, String entityName, TSentryAuthorizable authorizable) {
+    TListSentryPrivilegesRequest request = new TListSentryPrivilegesRequest();
+    request.setRequestorUserName(requestorUser);
+    request.setEntityName(entityName);
+    request.setAuthorizableHierarchy(authorizable);
+    return request;
+  }
+
+  private static TSentryPrivilege newSentryPrivilege(String scope, String 
dbname, String tablename, String action) {
+    TSentryPrivilege privilege = new TSentryPrivilege();
+    privilege.setPrivilegeScope(scope);
+    privilege.setDbName(dbname);
+    privilege.setTableName(tablename);
+    privilege.setAction(action);
+    return privilege;
+  }
 }

Reply via email to