Repository: sentry Updated Branches: refs/heads/master f330be532 -> 4d9bc9cea
SENTRY-2274: Grant and revoke owner privileges based on HMS updates(server-side) (Kalyan Kumar Kalvagadda reviewed by Sergio Pena) Project: http://git-wip-us.apache.org/repos/asf/sentry/repo Commit: http://git-wip-us.apache.org/repos/asf/sentry/commit/4d9bc9ce Tree: http://git-wip-us.apache.org/repos/asf/sentry/tree/4d9bc9ce Diff: http://git-wip-us.apache.org/repos/asf/sentry/diff/4d9bc9ce Branch: refs/heads/master Commit: 4d9bc9cea94f13b7994817d33c016c776fa7dcda Parents: f330be5 Author: Kalyan Kumar Kalvagadda <kkal...@cloudera.com> Authored: Thu Jun 21 15:59:05 2018 -0500 Committer: Kalyan Kumar Kalvagadda <kkal...@cloudera.com> Committed: Thu Jun 21 15:59:05 2018 -0500 ---------------------------------------------------------------------- .../java/org/apache/sentry/SentryOwnerInfo.java | 84 +++++++ .../org/apache/sentry/hdfs/SentryPlugin.java | 40 ++-- .../api/service/thrift/SentryMetrics.java | 2 + .../thrift/SentryPolicyStoreProcessor.java | 227 ++++++++++++++++++- .../provider/db/SentryPolicyStorePlugin.java | 22 +- .../db/service/persistent/CounterWait.java | 4 +- .../db/service/persistent/SentryStore.java | 208 ++++++++++++++++- .../thrift/TestSentryPolicyStoreProcessor.java | 125 +++++++++- .../db/service/persistent/TestSentryStore.java | 204 +++++++++++++++++ 9 files changed, 869 insertions(+), 47 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/sentry/blob/4d9bc9ce/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/SentryOwnerInfo.java ---------------------------------------------------------------------- diff --git a/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/SentryOwnerInfo.java b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/SentryOwnerInfo.java new file mode 100644 index 0000000..ee4fce5 --- /dev/null +++ b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/SentryOwnerInfo.java @@ -0,0 +1,84 @@ +/** + * 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; + +import org.apache.commons.lang.StringUtils; +import org.apache.sentry.service.common.ServiceConstants.SentryEntityType; + +/** + * This class holds the owner name and the Type + */ +public class SentryOwnerInfo { + private String ownerName; + private SentryEntityType ownerType; + + public SentryOwnerInfo (SentryEntityType type, String name) { + ownerType = type; + ownerName = name; + } + + public void setOwnerName(String name) { + ownerName = name; + } + + public String getOwnerName() { + return ownerName; + } + + public SentryEntityType getOwnerType() { + return ownerType; + } + + public void setOwnerType(SentryEntityType type) { + ownerType = type; + } + + @Override + public String toString() { + return "Owner Type: " + ownerType.toString() + ", Owner Name: " + ownerName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((ownerName == null) ? 0 : ownerName.hashCode()); + result = prime * result + ownerType.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + SentryOwnerInfo other = (SentryOwnerInfo) obj; + + if(ownerType != other.getOwnerType()) { + return false; + } + return StringUtils.equals(ownerName, other.ownerName); + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/4d9bc9ce/sentry-hdfs/sentry-hdfs-service/src/main/java/org/apache/sentry/hdfs/SentryPlugin.java ---------------------------------------------------------------------- diff --git a/sentry-hdfs/sentry-hdfs-service/src/main/java/org/apache/sentry/hdfs/SentryPlugin.java b/sentry-hdfs/sentry-hdfs-service/src/main/java/org/apache/sentry/hdfs/SentryPlugin.java index b5e01e4..6ff3c82 100644 --- a/sentry-hdfs/sentry-hdfs-service/src/main/java/org/apache/sentry/hdfs/SentryPlugin.java +++ b/sentry-hdfs/sentry-hdfs-service/src/main/java/org/apache/sentry/hdfs/SentryPlugin.java @@ -38,8 +38,6 @@ import org.apache.sentry.provider.db.service.persistent.SentryStore; import org.apache.sentry.api.common.SentryServiceUtil; import org.apache.sentry.api.service.thrift.TAlterSentryRoleAddGroupsRequest; import org.apache.sentry.api.service.thrift.TAlterSentryRoleDeleteGroupsRequest; -import org.apache.sentry.api.service.thrift.TAlterSentryRoleGrantPrivilegeRequest; -import org.apache.sentry.api.service.thrift.TAlterSentryRoleRevokePrivilegeRequest; import org.apache.sentry.api.service.thrift.TDropPrivilegesRequest; import org.apache.sentry.api.service.thrift.TDropSentryRoleRequest; import org.apache.sentry.api.service.thrift.TRenamePrivilegesRequest; @@ -251,17 +249,18 @@ public class SentryPlugin implements SentryPolicyStorePlugin, SigUtils.SigListen } @Override - public void onAlterSentryRoleGrantPrivilege(TAlterSentryRoleGrantPrivilegeRequest request, - Map<TSentryPrivilege, Update> privilegesUpdateMap) throws SentryPluginException { - Preconditions.checkNotNull(request, "request"); + public void onAlterSentryRoleGrantPrivilege(String roleName, Set<TSentryPrivilege> privileges, + Map<TSentryPrivilege, Update> privilegesUpdateMap) throws SentryPluginException { + Preconditions.checkNotNull(roleName, "Role name is NULL"); + Preconditions.checkNotNull(privilegesUpdateMap, "Privilege MAP NULL"); + Preconditions.checkNotNull(privileges, "Privilege Set provided is NULL"); + if (LOGGER.isTraceEnabled()) { - LOGGER.trace("onAlterSentryRoleGrantPrivilege: {}", request); // request.toString() provides all details + LOGGER.trace("onAlterSentryRoleGrantPrivilege: {}", roleName, privileges); } - if (request.isSetPrivileges()) { - String roleName = request.getRoleName(); - - for (TSentryPrivilege privilege : request.getPrivileges()) { + if (privileges.size() > 0) { + for (TSentryPrivilege privilege : privileges) { if(!(PrivilegeScope.COLUMN.name().equalsIgnoreCase(privilege.getPrivilegeScope()))) { PermissionsUpdate update = onAlterSentryGrantPrivilegeCore(new TPrivilegeEntity(TPrivilegeEntityType.ROLE, roleName), privilege); @@ -285,7 +284,7 @@ public class SentryPlugin implements SentryPolicyStorePlugin, SigUtils.SigListen Preconditions.checkNotNull(privileges, "Privilege Set provided is NULL"); if (LOGGER.isTraceEnabled()) { - LOGGER.trace("onAlterSentryUserGrantPrivilege: {}", userName); + LOGGER.trace("onAlterSentryUserGrantPrivilege: {}", userName, privileges); } if (privileges.size() > 0) { @@ -349,18 +348,19 @@ public class SentryPlugin implements SentryPolicyStorePlugin, SigUtils.SigListen } @Override - public void onAlterSentryRoleRevokePrivilege(TAlterSentryRoleRevokePrivilegeRequest request, - Map<TSentryPrivilege, Update> privilegesUpdateMap) + public void onAlterSentryRoleRevokePrivilege(String roleName, Set<TSentryPrivilege> privileges, + Map<TSentryPrivilege, Update> privilegesUpdateMap) throws SentryPluginException { - Preconditions.checkNotNull(request, "request"); + Preconditions.checkNotNull(roleName, "Role name is NULL"); + Preconditions.checkNotNull(privilegesUpdateMap, "Privilege MAP NULL"); + Preconditions.checkNotNull(privileges, "Privilege Set provided is NULL"); + if (LOGGER.isTraceEnabled()) { - LOGGER.trace("onAlterSentryRoleRevokePrivilege: {}", request); // request.toString() provides all details + LOGGER.trace("onAlterSentryRoleRevokePrivilege: {}", roleName, privileges); } - if (request.isSetPrivileges()) { - String roleName = request.getRoleName(); - - for (TSentryPrivilege privilege : request.getPrivileges()) { + if (privileges.size() > 0) { + for (TSentryPrivilege privilege : privileges) { if(!("COLUMN".equalsIgnoreCase(privilege.getPrivilegeScope()))) { PermissionsUpdate update = onAlterSentryRevokePrivilegeCore(new TPrivilegeEntity(TPrivilegeEntityType.ROLE, roleName), privilege); @@ -385,7 +385,7 @@ public class SentryPlugin implements SentryPolicyStorePlugin, SigUtils.SigListen Preconditions.checkNotNull(privileges, "Privilege Set provided is NULL"); if (LOGGER.isTraceEnabled()) { - LOGGER.trace("onAlterSentryUserRevokePrivilege: {}", userName); // request.toString() provides all details + LOGGER.trace("onAlterSentryUserRevokePrivilege: {}", userName, privileges); } if (privileges.size() > 0) { http://git-wip-us.apache.org/repos/asf/sentry/blob/4d9bc9ce/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 5424bff..232a979 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 @@ -112,6 +112,8 @@ public final class SentryMetrics { name(SentryPolicyStoreProcessor.class, "list-privileges-for-provider")); final Timer listPrivilegesByAuthorizableTimer = METRIC_REGISTRY.timer( name(SentryPolicyStoreProcessor.class, "list-privileges-by-authorizable")); + final Timer notificationProcessTimer = METRIC_REGISTRY.timer( + name(SentryPolicyStoreProcessor.class, "process-hsm-notification")); /** * Return a Timer with name. http://git-wip-us.apache.org/repos/asf/sentry/blob/4d9bc9ce/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 7f97ff7..5aef620 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 @@ -22,6 +22,8 @@ package org.apache.sentry.api.service.thrift; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -32,6 +34,8 @@ import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hive.metastore.messaging.EventMessage.EventType; +import org.apache.sentry.SentryOwnerInfo; import org.apache.sentry.api.common.ThriftConstants; import org.apache.sentry.core.common.exception.SentryUserException; import org.apache.sentry.core.common.exception.SentrySiteConfigurationException; @@ -68,6 +72,7 @@ import static com.codahale.metrics.MetricRegistry.name; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -81,6 +86,10 @@ import static org.apache.sentry.hdfs.Updateable.Update; public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface { private static final Logger LOGGER = Logger.getLogger(SentryPolicyStoreProcessor.class); private static final Logger AUDIT_LOGGER = Logger.getLogger(Constants.AUDIT_LOGGER_NAME); + private static final Map<TSentryObjectOwnerType, SentryEntityType> mapOwnerType = ImmutableMap.of( + TSentryObjectOwnerType.ROLE, SentryEntityType.ROLE, + TSentryObjectOwnerType.USER, SentryEntityType.USER + ); private final String name; private final Configuration conf; @@ -257,7 +266,7 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface { Preconditions.checkState(sentryPlugins.size() <= 1); Map<TSentryPrivilege, Update> privilegesUpdateMap = new HashMap<>(); for (SentryPolicyStorePlugin plugin : sentryPlugins) { - plugin.onAlterSentryRoleGrantPrivilege(request, privilegesUpdateMap); + plugin.onAlterSentryRoleGrantPrivilege(request.getRoleName(), request.getPrivileges(), privilegesUpdateMap); } if (!privilegesUpdateMap.isEmpty()) { @@ -335,7 +344,7 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface { Preconditions.checkState(sentryPlugins.size() <= 1); Map<TSentryPrivilege, Update> privilegesUpdateMap = new HashMap<>(); for (SentryPolicyStorePlugin plugin : sentryPlugins) { - plugin.onAlterSentryRoleRevokePrivilege(request, privilegesUpdateMap); + plugin.onAlterSentryRoleRevokePrivilege(request.getRoleName(), request.getPrivileges(), privilegesUpdateMap); } if (!privilegesUpdateMap.isEmpty()) { @@ -1342,6 +1351,179 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface { return response; } + @Override + public TSentryHmsEventNotificationResponse sentry_notify_hms_event + (TSentryHmsEventNotification request) throws TException{ + TSentryHmsEventNotificationResponse response = new TSentryHmsEventNotificationResponse(); + final Timer.Context timerContext = sentryMetrics.notificationProcessTimer.time(); + EventType eventType = EventType.valueOf(request.getEventType()); + try { + switch (eventType) { + case CREATE_DATABASE: + case CREATE_TABLE: + // Wait till Sentry server processes HMS Notification Event. + if(request.getId() > 0) { + response.setId(syncEventId(request.getId())); + } + //Grant privilege to the owner. + grantOwnerPrivilege(request); + break; + case DROP_DATABASE: + case DROP_TABLE: + // Wait till Sentry server processes HMS Notification Event. + if(request.getId() > 0) { + response.setId(syncEventId(request.getId())); + } + // Owner privileges for the database and tables that are dropped are cleaned-up when + // sentry fetches and process the DROP_DATABASE and DROP_TABLE notifications. + break; + case ALTER_TABLE: + /* Alter table event is notified to sentry when either of below is observed. + together. + 1. Owner Update + 2. Table Rename + */ + // Wait till Sentry server processes HMS Notification Event. + if(request.getId() > 0) { + response.setId(syncEventId(request.getId())); + } + // Owner is updated. There is no need to wait till Sentry processes HMS Notification Event. + // Revoke owner privilege from old owners and grant one to the new owner. + updateOwnerPrivilege(request); + break; + default: + LOGGER.info("Processing HMS Event of Type: " + eventType.toString() + " skipped"); + } + response.setStatus(Status.OK()); + } catch (SentryNoSuchObjectException e) { + String msg = request.getOwnerType().toString() + ": " + request.getOwnerName() + " doesn't exist"; + LOGGER.error(msg, e); + response.setStatus(Status.NoSuchObject(msg, e)); + } catch (SentryInvalidInputException e) { + LOGGER.error(e.getMessage(), e); + response.setStatus(Status.InvalidInput(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; + } + + /** + * Grants owner privilege to an authorizable. + * + * Privilege is granted based on the information in TSentryHmsEventNotification + * @param request TSentryHmsEventNotification + * @throws Exception when there an exception while sending/processing the request. + */ + private void grantOwnerPrivilege(TSentryHmsEventNotification request) throws Exception { + if (Strings.isNullOrEmpty(request.getOwnerName()) || (request.getOwnerType().getValue() == 0)) { + LOGGER.debug(String.format("Owner Information not provided for Operation: [%s], Not adding owner privilege for" + + " object: [%s].[%s]", request.getEventType(), request.getAuthorizable().getDb(), + request.getAuthorizable().getTable())); + return; + } + + TSentryPrivilege ownerPrivilege = constructOwnerPrivilege(request.getAuthorizable()); + if (ownerPrivilege == null) { + LOGGER.debug("Owner privilege is not added"); + return; + } + + SentryEntityType entityType = getSentryEntityType(request.getOwnerType()); + if (entityType == null) { + String error = "Invalid owner type : " + request.getEventType(); + LOGGER.error(error); + throw new SentryInvalidInputException(error); + } + + Preconditions.checkState(sentryPlugins.size() <= 1); + Set<TSentryPrivilege> privSet = Collections.singleton(ownerPrivilege); + Map<TSentryPrivilege, Update> privilegesUpdateMap = new HashMap<>(); + switch (request.getOwnerType()) { + case ROLE: + for (SentryPolicyStorePlugin plugin : sentryPlugins) { + plugin.onAlterSentryRoleGrantPrivilege(request.getOwnerName(), privSet, privilegesUpdateMap); + } + break; + case USER: + for (SentryPolicyStorePlugin plugin : sentryPlugins) { + plugin.onAlterSentryUserGrantPrivilege(request.getOwnerName(), privSet, privilegesUpdateMap); + } + break; + default: + LOGGER.error("Invalid owner Type"); + } + // Grants owner privilege to the entity + sentryStore.alterSentryGrantOwnerPrivilege(request.getOwnerName(), entityType, + ownerPrivilege, privilegesUpdateMap.get(ownerPrivilege)); + //TODO Implement notificationHandlerInvoker API for granting user priv and invoke it. + //TODO Implement Audit Log API's and invoke them here. + } + + /** + * Alters owner privilege of an authorizable. + * + * Revoke all the owner privileges on the authorizable and grants new owner privilege. + * @param request Sentry HMS Event Notification + * @throws Exception when there an exception while sending/processing the request. + */ + private void updateOwnerPrivilege(TSentryHmsEventNotification request) throws Exception { + if (Strings.isNullOrEmpty(request.getOwnerName()) || (request.getOwnerType().getValue() == 0)) { + LOGGER.debug(String.format("Owner Information not provided for Operation: [%s], Not revoking owner privilege for" + + " object: [%s].[%s]", request.getEventType(), request.getAuthorizable().getDb(), + request.getAuthorizable().getTable())); + return; + } + + TSentryPrivilege ownerPrivilege = constructOwnerPrivilege(request.getAuthorizable()); + if (ownerPrivilege == null) { + LOGGER.debug("Owner privilege is not added"); + return; + } + + SentryEntityType entityType = getSentryEntityType(request.getOwnerType()); + if(entityType == null ) { + String error = "Invalid owner type : " + request.getEventType(); + LOGGER.error(error); + throw new SentryInvalidInputException(error); + } + + Set<TSentryPrivilege> privSet = Collections.singleton(ownerPrivilege); + Preconditions.checkState(sentryPlugins.size() <= 1); + Map<TSentryPrivilege, Update> privilegesUpdateMap = new HashMap<>(); + List<Update> updateList = new ArrayList<>(); + List<SentryOwnerInfo> ownerInfoList = sentryStore.listOwnersByAuthorizable(request.getAuthorizable()); + // Creating updates for deleting all the old owner privileges + // There should only one owner privilege for an authorizable but the current schema + // doesn't have constraints to limit it. It is possible to have multiple owners for an authorizable (which is unlikely) + // This logic makes sure of revoking all the owner privilege. + for (SentryOwnerInfo ownerInfo : ownerInfoList) { + if (ownerInfo.getOwnerType() == SentryEntityType.USER) { + for (SentryPolicyStorePlugin plugin : sentryPlugins) { + plugin.onAlterSentryUserRevokePrivilege(ownerInfo.getOwnerName(), privSet, privilegesUpdateMap); + updateList.add(privilegesUpdateMap.get(ownerPrivilege)); + } + } else if (ownerInfo.getOwnerType() == SentryEntityType.ROLE) { + for (SentryPolicyStorePlugin plugin : sentryPlugins) { + plugin.onAlterSentryRoleRevokePrivilege(request.getOwnerName(), privSet, privilegesUpdateMap); + updateList.add(privilegesUpdateMap.get(ownerPrivilege)); + } + } + } + // Revokes old owner privileges and grants owner privilege for new owner. + sentryStore.updateOwnerPrivilege(request.getAuthorizable(), request.getOwnerName(), + entityType, updateList); + //TODO Implement notificationHandlerInvoker API for granting user priv and invoke it. + //TODO Implement Audit Log API's and invoke them here. + } + /** * This API constructs (@Link TSentryPrivilege} for authorizable provided * based on the configurations. @@ -1352,7 +1534,7 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface { TSentryPrivilege constructOwnerPrivilege(TSentryAuthorizable authorizable) { Boolean isOwnerPrivEnabled = conf.getBoolean(ServerConfig.SENTRY_ENABLE_OWNER_PRIVILEGES, ServerConfig.SENTRY_ENABLE_OWNER_PRIVILEGES_DEFAULT); - if(isOwnerPrivEnabled == false) { + if(!isOwnerPrivEnabled) { return null; } if(Strings.isNullOrEmpty(authorizable.getDb())) { @@ -1366,6 +1548,9 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface { ownerPrivilege.setDbName(authorizable.getDb()); if(!Strings.isNullOrEmpty(authorizable.getTable())) { ownerPrivilege.setTableName(authorizable.getTable()); + ownerPrivilege.setPrivilegeScope("TABLE"); + } else { + ownerPrivilege.setPrivilegeScope("DATABASE"); } if(privilegeWithGrantOption) { ownerPrivilege.setGrantOption(TSentryGrantOption.TRUE); @@ -1374,12 +1559,34 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface { return ownerPrivilege; } - @Override - public TSentryHmsEventNotificationResponse sentry_notify_hms_event - (TSentryHmsEventNotification request) throws TException{ - TSentryHmsEventNotificationResponse response = new TSentryHmsEventNotificationResponse(); - //TODO This API has to be implemented. - response.setStatus(Status.OK()); - return response; + /** + * + * @param ownerType + * @return SentryEntityType if input was valid, otherwise returns null + * @throws Exception + */ + private SentryEntityType getSentryEntityType(TSentryObjectOwnerType ownerType) throws Exception { + return mapOwnerType.get(ownerType); + } + + /** + * Syncronizes with the eventId processed by sentry + * @param eventId + * @return current counter value that should be no smaller then the requested + * value, returns 0 if there were an exception. + */ + long syncEventId(long eventId) { + try { + return sentryStore.getCounterWait().waitFor(eventId); + } catch (InterruptedException e) { + String msg = String.format("wait request for id %d is interrupted", + eventId); + LOGGER.error(msg, e); + Thread.currentThread().interrupt(); + } catch (TimeoutException e) { + String msg = String.format("timed out wait request for id %d", eventId); + LOGGER.warn(msg, e); + } + return 0; } } http://git-wip-us.apache.org/repos/asf/sentry/blob/4d9bc9ce/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/SentryPolicyStorePlugin.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/SentryPolicyStorePlugin.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/SentryPolicyStorePlugin.java index 52f25dc..e27e1db 100644 --- a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/SentryPolicyStorePlugin.java +++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/SentryPolicyStorePlugin.java @@ -24,8 +24,6 @@ import org.apache.sentry.core.common.exception.SentryUserException; import org.apache.sentry.provider.db.service.persistent.SentryStore; import org.apache.sentry.api.service.thrift.TAlterSentryRoleAddGroupsRequest; import org.apache.sentry.api.service.thrift.TAlterSentryRoleDeleteGroupsRequest; -import org.apache.sentry.api.service.thrift.TAlterSentryRoleGrantPrivilegeRequest; -import org.apache.sentry.api.service.thrift.TAlterSentryRoleRevokePrivilegeRequest; import org.apache.sentry.api.service.thrift.TDropPrivilegesRequest; import org.apache.sentry.api.service.thrift.TDropSentryRoleRequest; import org.apache.sentry.api.service.thrift.TRenamePrivilegesRequest; @@ -62,10 +60,24 @@ public interface SentryPolicyStorePlugin { Update onAlterSentryRoleDeleteGroups(TAlterSentryRoleDeleteGroupsRequest tRequest) throws SentryPluginException; - void onAlterSentryRoleGrantPrivilege(TAlterSentryRoleGrantPrivilegeRequest tRequest, - Map<TSentryPrivilege, Update> privilegesUpdateMap) throws SentryPluginException; + /** + * Used to create an update when privileges are granted to owner who is a Role + * @param roleName + * @param privileges + * @param privilegesUpdateMap + * @throws SentryPluginException + */ + void onAlterSentryRoleGrantPrivilege(String roleName, Set<TSentryPrivilege> privileges, + Map<TSentryPrivilege, Update> privilegesUpdateMap) throws SentryPluginException; - void onAlterSentryRoleRevokePrivilege(TAlterSentryRoleRevokePrivilegeRequest tRequest, + /** + * Used to create an update when privileges are revoked from owner who is a role + * @param roleName + * @param privileges + * @param privilegesUpdateMap + * @throws SentryPluginException + */ + void onAlterSentryRoleRevokePrivilege(String roleName, Set<TSentryPrivilege> privileges, Map<TSentryPrivilege, Update> privilegesUpdateMap) throws SentryPluginException; /** http://git-wip-us.apache.org/repos/asf/sentry/blob/4d9bc9ce/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/persistent/CounterWait.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/persistent/CounterWait.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/persistent/CounterWait.java index d8c8297..ea2f77d 100644 --- a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/persistent/CounterWait.java +++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/persistent/CounterWait.java @@ -54,7 +54,7 @@ import java.util.concurrent.atomic.AtomicLong; * updater threads. */ @ThreadSafe -public final class CounterWait { +public class CounterWait { // Implementation notes. // // The implementation is based on: @@ -102,7 +102,7 @@ public final class CounterWait { /** * Create an instance of CounterWait object that will timeout during wait - * @param waitTimeout maximum time in seconds to wait for counter + * @param waitTimeoutSec maximum time in seconds to wait for counter */ public CounterWait(long waitTimeoutSec) { this(waitTimeoutSec, TimeUnit.SECONDS); http://git-wip-us.apache.org/repos/asf/sentry/blob/4d9bc9ce/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java index d673239..27b8876 100644 --- a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java +++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java @@ -42,6 +42,7 @@ import javax.jdo.Query; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; +import org.apache.sentry.SentryOwnerInfo; import org.apache.sentry.core.common.exception.SentryAccessDeniedException; import org.apache.sentry.core.common.exception.SentryAlreadyExistsException; import org.apache.sentry.core.common.exception.SentryGrantDeniedException; @@ -199,6 +200,7 @@ public class SentryStore { */ private final CounterWait counterWait; + private final boolean ownerPrivilegeWithGrant; public static Properties getDataNucleusProperties(Configuration conf) throws SentrySiteConfigurationException, IOException { Properties prop = new Properties(); @@ -283,6 +285,8 @@ public class SentryStore { long notificationTimeout = conf.getInt(ServerConfig.SENTRY_NOTIFICATION_SYNC_TIMEOUT_MS, ServerConfig.SENTRY_NOTIFICATION_SYNC_TIMEOUT_DEFAULT); counterWait = new CounterWait(notificationTimeout, TimeUnit.MILLISECONDS); + ownerPrivilegeWithGrant = conf.getBoolean(ServerConfig.SENTRY_OWNER_PRIVILEGE_WITH_GRANT, + ServerConfig.SENTRY_OWNER_PRIVILEGE_WITH_GRANT_DEFAULT); } public void setPersistUpdateDeltas(boolean persistUpdateDeltas) { @@ -793,7 +797,8 @@ public class SentryStore { if(type == SentryEntityType.ROLE) { throw noSuchRole(entityName); } else if(type == SentryEntityType.USER) { - throw noSuchUser(entityName); + // User might not exist. Creating one. + mEntity = new MSentryUser(entityName, System.currentTimeMillis(), Sets.newHashSet()); } } @@ -802,8 +807,9 @@ public class SentryStore { throw new SentryInvalidInputException("cannot grant URI privileges to Null or EMPTY location"); } - if (!isNULL(privilege.getColumnName()) || !isNULL(privilege.getTableName()) - || !isNULL(privilege.getDbName())) { + if ((!isNULL(privilege.getColumnName()) || !isNULL(privilege.getTableName()) + || !isNULL(privilege.getDbName())) + && !AccessConstants.OWNER.equalsIgnoreCase(privilege.getAction())) { // If Grant is for ALL and Either INSERT/SELECT already exists.. // need to remove it and GRANT ALL.. if (AccessConstants.ALL.equalsIgnoreCase(privilege.getAction()) @@ -878,6 +884,36 @@ public class SentryStore { } /** + * Alter a give sentry user/role to set owner privilege, as well as persist the corresponding + * permission change to MSentryPermChange table in a single transaction. + * Creates User, if it is not already there. + * Internally calls alterSentryGrantPrivilege. + * @param entityName Entity name to which permissions should be granted. + * @param entityType Entity Type + * @param privilege Privilege to be granted + * @param update DeltaTransactionBlock + * @throws Exception + */ + public void alterSentryGrantOwnerPrivilege(final String entityName, SentryEntityType entityType, + final TSentryPrivilege privilege, + final Update update) throws Exception { + execute(update, pm -> { + pm.setDetachAllOnCommit(false); // No need to detach objects + String trimmedEntityName = trimAndLower(entityName); + + // Alter sentry Role and grant Privilege. + MSentryPrivilege mPrivilege = alterSentryGrantPrivilegeCore(pm, entityType, + trimmedEntityName, privilege); + + if (mPrivilege != null) { + // update the privilege to be the one actually updated. + convertToTSentryPrivilege(mPrivilege, privilege); + } + return null; + }); + } + + /** * Alter a given sentry user to grant a set of privileges, as well as persist the * corresponding permission change to MSentryPermChange table in a single transaction. * Internally calls alterSentryGrantPrivilege. @@ -1092,7 +1128,7 @@ public class SentryStore { privilegeGraph.add(mFalse); } // Get the privilege graph - populateChildren(pm, SentryEntityType.ROLE, Sets.newHashSet(entityName), mPrivilege, privilegeGraph); + populateChildren(pm, type, Sets.newHashSet(entityName), mPrivilege, privilegeGraph); for (MSentryPrivilege childPriv : privilegeGraph) { revokePrivilege(pm, tPrivilege, mEntity, childPriv); } @@ -1933,6 +1969,46 @@ public class SentryStore { return result; }); } + /** + * List the Owner privileges for an authorizable + * @param pm persistance manager + * @param authHierarchy Authorizable + * @return privilege list + * @throws Exception + */ + private List<MSentryPrivilege> getMSentryOwnerPrivilegesByAuth(PersistenceManager pm, + final TSentryAuthorizable + authHierarchy) throws Exception { + Query query = pm.newQuery(MSentryPrivilege.class); + QueryParamBuilder paramBuilder = QueryParamBuilder.newQueryParamBuilder(); + if (authHierarchy.getServer() != null) { + paramBuilder.add(SERVER_NAME, authHierarchy.getServer()); + if (authHierarchy.getDb() != null) { + paramBuilder.add(DB_NAME, authHierarchy.getDb()).addNull(URI); + if (authHierarchy.getTable() != null) { + paramBuilder.add(TABLE_NAME, authHierarchy.getTable()); + } else { + paramBuilder.addNull(TABLE_NAME); + } + } else if (authHierarchy.getUri() != null) { + paramBuilder.addNotNull(URI) + .addNull(DB_NAME) + .addCustomParam("(:authURI.startsWith(URI))", "authURI", authHierarchy.getUri()); + } else { + paramBuilder.addNull(DB_NAME) + .addNull(URI); + } + paramBuilder.add(ACTION, AccessConstants.OWNER); + } else { + // if no server, then return empty result + return Collections.emptyList(); + } + query.setFilter(paramBuilder.toString()); + @SuppressWarnings("unchecked") + List<MSentryPrivilege> result = (List<MSentryPrivilege>) query. + executeWithMap(paramBuilder.getArguments()); + return result; + } private Set<MSentryPrivilege> getMSentryPrivilegesByUserName(String userName) throws Exception { @@ -1997,6 +2073,31 @@ public class SentryStore { } /** + * List the Owners for an authorizable + * @param authorizable Authorizable + * @return List of owner for an authorizable + * @throws Exception + */ + public List<SentryOwnerInfo> listOwnersByAuthorizable(TSentryAuthorizable authorizable) + throws Exception { + List<SentryOwnerInfo> ownerInfolist = new ArrayList<>(); + return tm.executeTransaction( + pm -> { + List<MSentryPrivilege> mSentryPrivileges = + getMSentryOwnerPrivilegesByAuth(pm, authorizable); + for (MSentryPrivilege priv : mSentryPrivileges) { + for (PrivilegeEntity user : priv.getUsers()) { + ownerInfolist.add(new SentryOwnerInfo(user.getType(), user.getEntityName())); + } + for (PrivilegeEntity role : priv.getRoles()) { + ownerInfolist.add(new SentryOwnerInfo(role.getType(), role.getEntityName())); + } + } + return ownerInfolist; + }); + } + + /** * Get all privileges associated with the authorizable and input users * @param userNames the users to get their privileges * @param authHierarchy the authorizables @@ -2577,6 +2678,78 @@ public class SentryStore { } /** + * Updates the owner privileges by revoking owner privileges to an authorizable and adding new + * privilege based on the arguments provided. + * @param tAuthorizable Authorizable to which owner privilege should be granted. + * @param ownerName + * @param entityType + * @param updates Delta Updates. + * @throws Exception + */ + public synchronized void updateOwnerPrivilege(final TSentryAuthorizable tAuthorizable, + String ownerName, SentryEntityType entityType, + final List<Update> updates) throws Exception { + execute(updates, pm -> { + if(entityType == null) { + LOGGER.info("Invalid Entity Type"); + } + pm.setDetachAllOnCommit(false); // No need to detach objects + TSentryPrivilege tOwnerPrivilege = toSentryPrivilege(tAuthorizable); + tOwnerPrivilege.setAction(AccessConstants.OWNER); + + revokeOwnerPrivilegesCore(pm, tAuthorizable); + + try { + if(ownerPrivilegeWithGrant) { + tOwnerPrivilege.setGrantOption(TSentryGrantOption.TRUE); + } + //Granting the privilege. + alterSentryGrantPrivilegeCore(pm, entityType, ownerName, tOwnerPrivilege); + return null; + } catch (JDODataStoreException e) { + throw new SentryInvalidInputException("Failed to grant owner privilege on Authorizable : " + + tAuthorizable.toString() + " to " + entityType.toString() + ": " + ownerName + " " + + e.getMessage()); + } + }); + } + + /** + * Revokes all the owner privileges granted to an authorizable + * @param tAuthorizable authorizable for which owner privilege should be revoked. + * @param updates + * @throws Exception + */ + @VisibleForTesting + void revokeOwnerPrivileges(final TSentryAuthorizable tAuthorizable, final List<Update> updates) + throws Exception{ + execute(updates, pm -> { + pm.setDetachAllOnCommit(false); + revokeOwnerPrivilegesCore(pm, tAuthorizable); + return null; + }); + } + + public void revokeOwnerPrivilegesCore(PersistenceManager pm, final TSentryAuthorizable tAuthorizable) + throws Exception{ + TSentryPrivilege tOwnerPrivilege = toSentryPrivilege(tAuthorizable); + tOwnerPrivilege.setAction(AccessConstants.OWNER); + + // Finding owner privileges and removing them. + List<MSentryPrivilege> mOwnerPrivileges = getMSentryPrivileges(tOwnerPrivilege, pm); + for(MSentryPrivilege mOwnerPriv : mOwnerPrivileges) { + Set<MSentryUser> users; + users = mOwnerPriv.getUsers(); + // Making sure of removing stale users. + for (MSentryUser user : users) { + user.removePrivilege(mOwnerPriv); + persistEntity(pm, SentryEntityType.USER, user); + } + } + pm.deletePersistentAll(mOwnerPrivileges); + } + + /** * Rename the privilege for all roles. Drop the old privilege name and create the new one. * * @param oldTAuthorizable the old authorizable name needs to be renamed. @@ -4469,12 +4642,31 @@ public class SentryStore { */ private void execute(Update update, TransactionBlock<Object> transactionBlock) throws Exception { - List<TransactionBlock<Object>> tbs = new ArrayList<>(2); + execute(update != null ? Collections.singletonList(update) : Collections.emptyList(), transactionBlock); + } - if (persistUpdateDeltas && update != null) { - tbs.add(new DeltaTransactionBlock(update)); - } + /** + * Execute multiple delta updates in a single transaction. + * Note that this method only applies to TransactionBlock that + * does not have any return value. + * <p> + * Failure in any TransactionBlock would cause the whole transaction + * to fail. + * + * @param updates list of delta updates + * @throws Exception + */ + private void execute(List<Update> updates, TransactionBlock<Object> transactionBlock) throws Exception { + // Currently this API is used to update the owner privilege. This needs two DeltaTransactionBlock's to record + // revoking/granting owner privilege and one TransactionBlock to perform actual permission change. + // Default size of tbs is picked accordingly. + List<TransactionBlock<Object>> tbs = new ArrayList<>(3); + if (persistUpdateDeltas && updates != null && updates.size() > 0) { + for (Update update : updates) { + tbs.add(new DeltaTransactionBlock(update)); + } + } tbs.add(transactionBlock); tm.executeTransactionBlocksWithRetry(tbs); } http://git-wip-us.apache.org/repos/asf/sentry/blob/4d9bc9ce/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 6bfe872..de4e001 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 @@ -23,11 +23,14 @@ import static org.junit.Assert.assertTrue; import com.codahale.metrics.Gauge; import com.google.common.collect.Sets; import java.util.Set; +import org.apache.hadoop.hive.metastore.messaging.EventMessage; +import org.apache.hadoop.hive.metastore.messaging.EventMessage.EventType; 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.provider.db.service.persistent.CounterWait; import org.apache.sentry.service.common.ServiceConstants; import org.apache.sentry.core.common.exception.SentrySiteConfigurationException; import org.apache.sentry.provider.db.service.persistent.SentryStore; @@ -44,11 +47,17 @@ import org.mockito.Mockito; public class TestSentryPolicyStoreProcessor { + private static final String DBNAME = "db1"; + private static final String TABLENAME = "table1"; + private static final String OWNER = "owner1"; private Configuration conf; private static final SentryStore sentryStore = Mockito.mock(SentryStore.class); + private static final CounterWait counterWait = Mockito.mock(CounterWait.class); @Before - public void setup() { + public void setup() throws Exception{ conf = new Configuration(false); + //Check behaviour when DB name is not set + conf.setBoolean(ServiceConstants.ServerConfig.SENTRY_ENABLE_OWNER_PRIVILEGES, true); Mockito.when(sentryStore.getRoleCountGauge()).thenReturn(new Gauge< Long >() { @Override @@ -98,8 +107,14 @@ public class TestSentryPolicyStoreProcessor { } }); + Mockito.doAnswer((invocation) -> { + long id = (long) invocation.getArguments()[0]; + return id; + }).when(counterWait).waitFor(Mockito.anyLong()); - + Mockito.doAnswer((invocation) -> { + return counterWait; + }).when(sentryStore).getCounterWait(); } @Test(expected=SentrySiteConfigurationException.class) public void testConfigNotNotificationHandler() throws Exception { @@ -147,6 +162,7 @@ public class TestSentryPolicyStoreProcessor { @Test public void testConstructOwnerPrivilege() throws Exception { + conf.setBoolean(ServiceConstants.ServerConfig.SENTRY_ENABLE_OWNER_PRIVILEGES, false); SentryPolicyStoreProcessor sentryServiceHandler = new SentryPolicyStoreProcessor(ApiConstants.SentryPolicyServiceConstants.SENTRY_POLICY_SERVICE_NAME, conf, sentryStore); @@ -173,6 +189,7 @@ public class TestSentryPolicyStoreProcessor { authorizable.setDb("db1"); privilege.setDbName("db1"); privilege.setAction(AccessConstants.OWNER); + privilege.setPrivilegeScope("DATABASE"); Assert.assertNotNull(sentryServiceHandler.constructOwnerPrivilege(authorizable)); Assert.assertEquals(privilege, sentryServiceHandler.constructOwnerPrivilege(authorizable)); @@ -181,6 +198,7 @@ public class TestSentryPolicyStoreProcessor { authorizable.setDb("db1"); authorizable.setTable("tb1"); privilege.setTableName("tb1"); + privilege.setPrivilegeScope("TABLE"); Assert.assertNotNull(sentryServiceHandler.constructOwnerPrivilege(authorizable)); Assert.assertEquals(privilege, sentryServiceHandler.constructOwnerPrivilege(authorizable)); @@ -193,6 +211,7 @@ public class TestSentryPolicyStoreProcessor { authorizable = new TSentryAuthorizable(""); authorizable.setDb("db1"); authorizable.setTable("tb1"); + privilege.setPrivilegeScope("TABLE"); privilege.setGrantOption(TSentryGrantOption.TRUE); Assert.assertNotNull(sentryServiceHandler.constructOwnerPrivilege(authorizable)); Assert.assertEquals(privilege, sentryServiceHandler.constructOwnerPrivilege(authorizable)); @@ -281,4 +300,106 @@ public class TestSentryPolicyStoreProcessor { privilege.setAction(action); return privilege; } + +@Test + public void testCreateTableEventProcessing() throws Exception { + SentryPolicyStoreProcessor sentryServiceHandler = + new SentryPolicyStoreProcessor(ApiConstants.SentryPolicyServiceConstants.SENTRY_POLICY_SERVICE_NAME, + conf, sentryStore); + TSentryAuthorizable authorizable = new TSentryAuthorizable(); + authorizable.setDb(DBNAME); + authorizable.setTable(TABLENAME); + + TSentryHmsEventNotification notification = new TSentryHmsEventNotification(); + notification.setId(1L); + notification.setOwnerType(TSentryObjectOwnerType.ROLE); + notification.setOwnerName(OWNER); + notification.setAuthorizable(authorizable); + notification.setEventType(EventMessage.EventType.CREATE_TABLE.toString()); + + sentryServiceHandler.sentry_notify_hms_event(notification); + + TSentryPrivilege ownerPrivilege = sentryServiceHandler.constructOwnerPrivilege(authorizable); + Mockito.verify( + sentryStore, Mockito.times(1) + ).alterSentryGrantOwnerPrivilege(OWNER, SentryEntityType.ROLE, ownerPrivilege, null); + + notification.setOwnerType(TSentryObjectOwnerType.USER); + sentryServiceHandler.sentry_notify_hms_event(notification); + + //Verify Sentry Store is invoked to grant privilege. + Mockito.verify( + sentryStore, Mockito.times(1) + ).alterSentryGrantOwnerPrivilege(OWNER, SentryEntityType.USER, ownerPrivilege, null); + } + + + @Test + public void testCreateDatabaseEventProcessing() throws Exception { + + SentryPolicyStoreProcessor sentryServiceHandler = + new SentryPolicyStoreProcessor(ApiConstants.SentryPolicyServiceConstants.SENTRY_POLICY_SERVICE_NAME, + conf, sentryStore); + TSentryAuthorizable authorizable = new TSentryAuthorizable(); + authorizable.setDb(DBNAME); + + TSentryHmsEventNotification notification = new TSentryHmsEventNotification(); + notification.setId(1L); + notification.setOwnerType(TSentryObjectOwnerType.ROLE); + notification.setOwnerName(OWNER); + notification.setAuthorizable(authorizable); + notification.setEventType(EventType.CREATE_DATABASE.toString()); + + sentryServiceHandler.sentry_notify_hms_event(notification); + + //Verify Sentry Store is invoked to grant privilege. + TSentryPrivilege ownerPrivilege = sentryServiceHandler.constructOwnerPrivilege(authorizable); + Mockito.verify( + sentryStore, Mockito.times(1) + ).alterSentryGrantOwnerPrivilege(OWNER, SentryEntityType.ROLE, ownerPrivilege, null); + + notification.setOwnerType(TSentryObjectOwnerType.USER); + sentryServiceHandler.sentry_notify_hms_event(notification); + + //Verify Sentry Store is invoked to grant privilege. + Mockito.verify( + sentryStore, Mockito.times(1) + ).alterSentryGrantOwnerPrivilege(OWNER, SentryEntityType.USER, ownerPrivilege, null); + } + + @Test + public void testAlterTableEventProcessing() throws Exception { + + SentryPolicyStoreProcessor sentryServiceHandler = + new SentryPolicyStoreProcessor(ApiConstants.SentryPolicyServiceConstants.SENTRY_POLICY_SERVICE_NAME, + conf, sentryStore); + TSentryAuthorizable authorizable = new TSentryAuthorizable(); + authorizable.setDb(DBNAME); + authorizable.setTable(TABLENAME); + + TSentryHmsEventNotification notification = new TSentryHmsEventNotification(); + notification.setId(1L); + notification.setOwnerType(TSentryObjectOwnerType.ROLE); + notification.setOwnerName(OWNER); + notification.setAuthorizable(authorizable); + notification.setEventType(EventType.ALTER_TABLE.toString()); + + sentryServiceHandler.sentry_notify_hms_event(notification); + + //Verify Sentry Store is invoked to grant privilege. + Mockito.verify( + sentryStore, Mockito.times(1) + ).updateOwnerPrivilege(Mockito.eq(authorizable), Mockito.eq(OWNER), Mockito.eq(SentryEntityType.ROLE), + Mockito.anyList()); + + + notification.setOwnerType(TSentryObjectOwnerType.USER); + sentryServiceHandler.sentry_notify_hms_event(notification); + + //Verify Sentry Store is invoked to grant privilege. + Mockito.verify( + sentryStore, Mockito.times(1) + ).updateOwnerPrivilege(Mockito.eq(authorizable), Mockito.eq(OWNER), Mockito.eq(SentryEntityType.ROLE), + Mockito.anyList()); + } } http://git-wip-us.apache.org/repos/asf/sentry/blob/4d9bc9ce/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java b/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java index c056446..51048bc 100644 --- a/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java +++ b/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java @@ -43,6 +43,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.alias.CredentialProvider; import org.apache.hadoop.security.alias.CredentialProviderFactory; import org.apache.hadoop.security.alias.UserProvider; +import org.apache.sentry.SentryOwnerInfo; import org.apache.sentry.core.common.exception.SentryAccessDeniedException; import org.apache.sentry.core.common.exception.SentryInvalidInputException; import org.apache.sentry.core.model.db.AccessConstants; @@ -1976,6 +1977,209 @@ public class TestSentryStore extends org.junit.Assert { } /** + * Grants owner privileges to role/user and updates the owner + * and makes sure that the owner privilege is updated. + * @throws Exception + */ + @Test + public void testUpdateOwnerPrivilege() throws Exception { + String roleName1 = "list-privs-r1", roleName2 = "list-privs-r2", roleName3 = "list-privs-r3"; + String userName1 = "user1", userName2 = "user2"; + String grantor = "g1"; + List<SentryOwnerInfo> ownerInfoList = null; + sentryStore.createSentryRole(roleName1); + sentryStore.createSentryRole(roleName2); + sentryStore.createSentryRole(roleName3); + sentryStore.createSentryUser(userName1); + sentryStore.createSentryUser(userName2); + + + TSentryPrivilege privilege_tbl1 = new TSentryPrivilege(); + privilege_tbl1.setPrivilegeScope("TABLE"); + privilege_tbl1.setServerName("server1"); + privilege_tbl1.setDbName("db1"); + privilege_tbl1.setTableName("tbl1"); + privilege_tbl1.setCreateTime(System.currentTimeMillis()); + privilege_tbl1.setAction("OWNER"); + + TSentryAuthorizable tSentryAuthorizable = new TSentryAuthorizable(); + tSentryAuthorizable.setServer("server1"); + tSentryAuthorizable.setDb("db1"); + tSentryAuthorizable.setTable("tbl1"); + + sentryStore.alterSentryGrantPrivilege(grantor, SentryEntityType.ROLE, roleName1, privilege_tbl1, null); + + assertEquals(1, sentryStore.getAllTSentryPrivilegesByRoleName(roleName1) + .size()); + assertEquals(0, sentryStore.getAllTSentryPrivilegesByRoleName(roleName2) + .size()); + + ownerInfoList = sentryStore.listOwnersByAuthorizable(tSentryAuthorizable); + assertEquals(1, ownerInfoList.size()); + assertEquals(SentryEntityType.ROLE, ownerInfoList.get(0).getOwnerType()); + assertEquals(roleName1, ownerInfoList.get(0).getOwnerName()); + + + // Change owner from a one role to another role + sentryStore.updateOwnerPrivilege(tSentryAuthorizable, roleName2, SentryEntityType.ROLE, null); + ownerInfoList = sentryStore.listOwnersByAuthorizable(tSentryAuthorizable); + assertEquals(1, ownerInfoList.size()); + assertEquals(SentryEntityType.ROLE, ownerInfoList.get(0).getOwnerType()); + assertEquals(roleName2, ownerInfoList.get(0).getOwnerName()); + + assertEquals(0, sentryStore.getAllTSentryPrivilegesByRoleName(roleName1) + .size()); + assertEquals(1, sentryStore.getAllTSentryPrivilegesByRoleName(roleName2) + .size()); + + tSentryAuthorizable.setTable("tbl2"); + TSentryPrivilege privilege_tbl2 = new TSentryPrivilege(privilege_tbl1); + privilege_tbl2.setTableName("tbl2"); + + // Change owner from a one user to another user + sentryStore.alterSentryGrantPrivilege(grantor, SentryEntityType.USER,userName1, privilege_tbl2, null); + assertEquals(1, sentryStore.getAllTSentryPrivilegesByUserName(userName1) + .size()); + + + + sentryStore.updateOwnerPrivilege(tSentryAuthorizable, userName2, SentryEntityType.USER, null); + assertEquals(1, sentryStore.getAllTSentryPrivilegesByUserName(userName2) + .size()); + ownerInfoList = sentryStore.listOwnersByAuthorizable(tSentryAuthorizable); + assertEquals(1, ownerInfoList.size()); + assertEquals(SentryEntityType.USER, ownerInfoList.get(0).getOwnerType()); + assertEquals(userName2, ownerInfoList.get(0).getOwnerName()); + + // Change owner from a user to role + sentryStore.updateOwnerPrivilege(tSentryAuthorizable, roleName1, SentryEntityType.ROLE, null); + assertEquals(1, sentryStore.getAllTSentryPrivilegesByRoleName(roleName1) + .size()); + + // At this point roleName1 has owner privilege on db1.tb2 + //Add all privilege to roleName1 and make sure that owner privilege is not effected. + TSentryPrivilege privilege_tbl2_all = new TSentryPrivilege(privilege_tbl2); + privilege_tbl2_all.setAction(AccessConstants.ALL); + sentryStore.alterSentryGrantPrivilege(grantor, SentryEntityType.ROLE, roleName1, privilege_tbl2_all, null); + // Verify that there are two privileges. + assertEquals(2, sentryStore.getAllTSentryPrivilegesByRoleName(roleName1) + .size()); + + + tSentryAuthorizable.setTable("tbl3"); + TSentryPrivilege privilege_tbl3_all = new TSentryPrivilege(privilege_tbl2); + privilege_tbl3_all.setAction(AccessConstants.ALL); + privilege_tbl3_all.setTableName("tbl3"); + sentryStore.alterSentryGrantPrivilege(grantor, SentryEntityType.ROLE, roleName3, privilege_tbl3_all, null); + assertEquals(1, sentryStore.getAllTSentryPrivilegesByRoleName(roleName3) + .size()); + TSentryPrivilege privilege_tbl3_owner = new TSentryPrivilege(privilege_tbl3_all); + privilege_tbl3_owner.setAction(AccessConstants.OWNER); + sentryStore.alterSentryGrantPrivilege(grantor, SentryEntityType.ROLE, roleName3, privilege_tbl3_owner, null); + + assertEquals(2, sentryStore.getAllTSentryPrivilegesByRoleName(roleName3) + .size()); + } + + @Test + public void testListSentryOwnerPrivilegesByAuthorizable() throws Exception { + String roleName1 = "list-privs-r1"; + String userName1 = "user1"; + String grantor = "g1"; + sentryStore.createSentryRole(roleName1); + sentryStore.createSentryUser(userName1); + + TSentryPrivilege privilege_tbl1 = new TSentryPrivilege(); + privilege_tbl1.setPrivilegeScope("TABLE"); + privilege_tbl1.setServerName("server1"); + privilege_tbl1.setDbName("db1"); + privilege_tbl1.setTableName("tbl1"); + privilege_tbl1.setCreateTime(System.currentTimeMillis()); + privilege_tbl1.setAction("OWNER"); + privilege_tbl1.setGrantOption(TSentryGrantOption.TRUE); + + TSentryAuthorizable tSentryAuthorizable = new TSentryAuthorizable(); + tSentryAuthorizable.setServer("server1"); + tSentryAuthorizable.setDb("db1"); + tSentryAuthorizable.setTable("tbl1"); + + sentryStore.alterSentryGrantPrivilege(grantor, SentryEntityType.ROLE, roleName1, privilege_tbl1, null); + + sentryStore.updateOwnerPrivilege(tSentryAuthorizable, userName1, SentryEntityType.USER, null); + assertEquals(1, sentryStore.getAllTSentryPrivilegesByUserName(userName1) + .size()); + } + + @Test + public void testRevokeOwnerPrivilege() throws Exception { + + String roleName1 = "list-privs-r1"; + String userName1 = "user1"; + String grantor = "g1"; + sentryStore.createSentryRole(roleName1); + sentryStore.createSentryUser(userName1); + + TSentryPrivilege privilege_tbl1 = new TSentryPrivilege(); + privilege_tbl1.setPrivilegeScope("TABLE"); + privilege_tbl1.setServerName("server1"); + privilege_tbl1.setDbName("db1"); + privilege_tbl1.setTableName("tbl1"); + privilege_tbl1.setCreateTime(System.currentTimeMillis()); + privilege_tbl1.setAction("OWNER"); + privilege_tbl1.setGrantOption(TSentryGrantOption.TRUE); + + TSentryAuthorizable tSentryAuthorizable = new TSentryAuthorizable(); + tSentryAuthorizable.setServer("server1"); + tSentryAuthorizable.setDb("db1"); + tSentryAuthorizable.setTable("tbl1"); + + sentryStore.alterSentryGrantPrivilege(grantor, SentryEntityType.ROLE, roleName1, privilege_tbl1, null); + + sentryStore.revokeOwnerPrivileges(tSentryAuthorizable, null); + assertEquals(0, sentryStore.getAllTSentryPrivilegesByUserName(userName1) + .size()); + } + + @Test + public void testDropUserOnUpdateOwnerPrivilege() throws Exception { + String userName1 = "user1", userName2 = "user2"; + String grantor = "g1"; + sentryStore.createSentryUser(userName1); + sentryStore.createSentryUser(userName2); + + + TSentryPrivilege privilege_tbl1 = new TSentryPrivilege(); + privilege_tbl1.setPrivilegeScope("TABLE"); + privilege_tbl1.setServerName("server1"); + privilege_tbl1.setDbName("db1"); + privilege_tbl1.setTableName("tbl1"); + privilege_tbl1.setCreateTime(System.currentTimeMillis()); + privilege_tbl1.setAction("OWNER"); + + TSentryAuthorizable tSentryAuthorizable = new TSentryAuthorizable(); + tSentryAuthorizable.setServer("server1"); + tSentryAuthorizable.setDb("db1"); + tSentryAuthorizable.setTable("tbl1"); + + + // Change owner from a one user to another user + sentryStore.alterSentryGrantPrivilege(grantor, SentryEntityType.USER, userName1, privilege_tbl1, null); + assertEquals(1, sentryStore.getAllTSentryPrivilegesByUserName(userName1) + .size()); + + + sentryStore.updateOwnerPrivilege(tSentryAuthorizable, userName2, SentryEntityType.USER, null); + assertEquals(1, sentryStore.getAllTSentryPrivilegesByUserName(userName2) + .size()); + + try { + sentryStore.createSentryUser(userName1); + } catch (Exception e) { + fail("Exception should not be seen asthe user: " + userName1 + " should have been deleted."); + } + + } + /** * Regression test for SENTRY-547 and SENTRY-548 * Use case: * GRANT INSERT on TABLE tbl1 to ROLE role1