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; + } }
