Java tutorial
/* * * 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.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; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeoutException; 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.SentryGrantDeniedException; import org.apache.sentry.core.common.exception.SentryUserException; import org.apache.sentry.core.common.exception.SentrySiteConfigurationException; import org.apache.sentry.core.common.utils.SentryConstants; import org.apache.sentry.core.model.db.AccessConstants; import org.apache.sentry.provider.common.GroupMappingService; import org.apache.sentry.core.common.utils.PolicyFileConstants; import org.apache.sentry.core.common.exception.SentryGroupNotFoundException; import org.apache.sentry.core.common.exception.SentryAccessDeniedException; import org.apache.sentry.core.common.exception.SentryAlreadyExistsException; import org.apache.sentry.core.common.exception.SentryInvalidInputException; import org.apache.sentry.core.common.exception.SentryNoSuchObjectException; import org.apache.sentry.provider.db.SentryPolicyStorePlugin; import org.apache.sentry.provider.db.SentryPolicyStorePlugin.SentryPluginException; import org.apache.sentry.core.common.exception.SentryThriftAPIMismatchException; import org.apache.sentry.provider.db.audit.SentryAuditLogger; import org.apache.sentry.provider.db.log.util.Constants; import org.apache.sentry.provider.db.service.persistent.SentryStoreInterface; import org.apache.sentry.core.common.utils.PolicyStoreConstants.PolicyStoreServerConfig; import org.apache.sentry.api.service.thrift.validator.GrantPrivilegeRequestValidator; import org.apache.sentry.api.service.thrift.validator.RevokePrivilegeRequestValidator; import org.apache.sentry.api.common.SentryServiceUtil; import org.apache.sentry.service.common.SentryOwnerPrivilegeType; import org.apache.sentry.service.common.ServiceConstants.ConfUtilties; import org.apache.sentry.service.common.ServiceConstants.SentryPrincipalType; import org.apache.sentry.service.common.ServiceConstants.ServerConfig; import org.apache.sentry.api.common.Status; import org.apache.sentry.service.thrift.FullUpdateInitializerState; import org.apache.sentry.service.thrift.SentryStateBank; import org.apache.sentry.service.thrift.TSentryResponseStatus; import org.apache.thrift.TException; import org.apache.log4j.Logger; import com.codahale.metrics.Timer; 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; import com.google.common.collect.Sets; import com.google.common.base.Strings; import static org.apache.sentry.hdfs.Updateable.Update; @SuppressWarnings("unused") 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<TSentryPrincipalType, SentryPrincipalType> mapOwnerType = ImmutableMap.of( TSentryPrincipalType.ROLE, SentryPrincipalType.ROLE, TSentryPrincipalType.USER, SentryPrincipalType.USER); private final String name; private final Configuration conf; private final SentryStoreInterface sentryStore; private final NotificationHandlerInvoker notificationHandlerInvoker; private final ImmutableSet<String> adminGroups; private SentryMetrics sentryMetrics; private final Timer hmsWaitTimer = SentryMetrics.getInstance() .getTimer(name(SentryPolicyStoreProcessor.class, "hms", "wait")); private final SentryAuditLogger audit; private List<SentryPolicyStorePlugin> sentryPlugins = new LinkedList<SentryPolicyStorePlugin>(); SentryPolicyStoreProcessor(String name, Configuration conf, SentryStoreInterface store) throws Exception { super(); this.name = name; this.conf = conf; this.sentryStore = store; this.notificationHandlerInvoker = new NotificationHandlerInvoker(conf, createHandlers(conf)); this.audit = new SentryAuditLogger(conf); adminGroups = ImmutableSet.copyOf( toTrimedLower(Sets.newHashSet(conf.getStrings(ServerConfig.ADMIN_GROUPS, new String[] {})))); Iterable<String> pluginClasses = ConfUtilties.CLASS_SPLITTER.split( conf.get(ServerConfig.SENTRY_POLICY_STORE_PLUGINS, ServerConfig.SENTRY_POLICY_STORE_PLUGINS_DEFAULT) .trim()); for (String pluginClassStr : pluginClasses) { Class<?> clazz = conf.getClassByName(pluginClassStr); if (!SentryPolicyStorePlugin.class.isAssignableFrom(clazz)) { throw new IllegalArgumentException("Sentry Plugin [" + pluginClassStr + "] is not a " + SentryPolicyStorePlugin.class.getName()); } SentryPolicyStorePlugin plugin = (SentryPolicyStorePlugin) clazz.newInstance(); plugin.initialize(conf, sentryStore); sentryPlugins.add(plugin); } initMetrics(); } private void initMetrics() { sentryMetrics = SentryMetrics.getInstance(); sentryMetrics.addSentryStoreGauges(sentryStore); sentryMetrics.initReporting(conf); } public void stop() { sentryStore.stop(); } public void registerPlugin(SentryPolicyStorePlugin plugin) throws SentryPluginException { plugin.initialize(conf, sentryStore); sentryPlugins.add(plugin); } @VisibleForTesting static List<NotificationHandler> createHandlers(Configuration conf) throws SentrySiteConfigurationException { List<NotificationHandler> handlers = Lists.newArrayList(); Iterable<String> notificationHandlers = Splitter.onPattern("[\\s,]").trimResults().omitEmptyStrings() .split(conf.get(PolicyStoreServerConfig.NOTIFICATION_HANDLERS, "")); for (String notificationHandler : notificationHandlers) { Class<?> clazz = null; try { clazz = Class.forName(notificationHandler); if (!NotificationHandler.class.isAssignableFrom(clazz)) { throw new SentrySiteConfigurationException( "Class " + notificationHandler + " is not a " + NotificationHandler.class.getName()); } } catch (ClassNotFoundException e) { throw new SentrySiteConfigurationException("Value " + notificationHandler + " is not a class", e); } Preconditions.checkNotNull(clazz, "Error class cannot be null"); try { Constructor<?> constructor = clazz.getConstructor(Configuration.class); handlers.add((NotificationHandler) constructor.newInstance(conf)); } catch (Exception e) { throw new SentrySiteConfigurationException("Error attempting to create " + notificationHandler, e); } } return handlers; } @VisibleForTesting public Configuration getSentryStoreConf() { return conf; } private static Set<String> toTrimedLower(Set<String> s) { Set<String> result = Sets.newHashSet(); for (String v : s) { result.add(v.trim().toLowerCase()); } return result; } private boolean inAdminGroups(Set<String> requestorGroups) { Set<String> trimmedRequestorGroups = toTrimedLower(requestorGroups); return !Sets.intersection(adminGroups, trimmedRequestorGroups).isEmpty(); } private void authorize(String requestorUser, Set<String> requestorGroups) throws SentryAccessDeniedException { if (!inAdminGroups(requestorGroups)) { String msg = "User: " + requestorUser + " is part of " + requestorGroups + " which does not, intersect admin groups " + adminGroups; LOGGER.warn(msg); throw new SentryAccessDeniedException("Access denied to " + requestorUser); } } @Override public TCreateSentryRoleResponse create_sentry_role(TCreateSentryRoleRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.createRoleTimer.time(); TCreateSentryRoleResponse response = new TCreateSentryRoleResponse(); try { validateClientVersion(request.getProtocol_version()); authorize(request.getRequestorUserName(), getRequestorGroups(request.getRequestorUserName())); sentryStore.createSentryRole(request.getRoleName()); response.setStatus(Status.OK()); notificationHandlerInvoker.create_sentry_role(request, response); } catch (SentryAlreadyExistsException e) { String msg = "Role: " + request + " already exists."; LOGGER.error(msg, e); response.setStatus(Status.AlreadyExists(e.getMessage(), 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(); } audit.onCreateRole(request, response); return response; } /** * Throws an exception if one of the set of privileges passed as a parameter cannot be granted by * the grantor user. * * <p/> The check is done by looking at the grant option flag each user or user/group role have * stored on the DB, and compare it with set of privileges that the user is attempting to grant. * If one of the privileges has the grant option disabled, then this method throws an exception * to let the caller know it cannot continue with the grant of the privilege. * * @param grantorUser The user who is attempting to grant the set or privileges. * @param checkPrivileges The set of privileges to check. * @throws Exception If the user does not have grant privileges. */ private void checkGrantOptionPrivileges(String grantorUser, Set<TSentryPrivilege> checkPrivileges) throws Exception { Preconditions.checkNotNull(checkPrivileges, "Privileges to check for grant option must not be null."); Set<String> groups = getGroupsFromUserName(conf, grantorUser); if (groups != null && inAdminGroups(groups)) { // grantorUser is part of one of the admin groups, so we permit the grant action return; } // Get all the privileges a user has (either directly granted to the user or through a role // which the user belongs too) Set<TSentryPrivilege> userPrivileges = sentryStore.listSentryPrivilegesByUsersAndGroups(groups, Collections.singleton(grantorUser), new TSentryActiveRoleSet(true, null), null); if (userPrivileges == null || userPrivileges.isEmpty()) { throw new SentryGrantDeniedException( String.format("User %s does not have privileges to grant.", grantorUser)); } // Check if each privilege grant will be permitted. Throws an exception in the first privilege // that is not permitted. for (TSentryPrivilege checkPrivilege : checkPrivileges) { boolean hasGrant = false; for (TSentryPrivilege p : userPrivileges) { if (p.getGrantOption() == TSentryGrantOption.TRUE && SentryPolicyStoreUtils.privilegeImplies(p, checkPrivilege)) { hasGrant = true; break; } } if (!hasGrant) { throw new SentryGrantDeniedException(String.format("User %s does not have privileges to grant %s.", grantorUser, checkPrivilege.getAction().toUpperCase())); } } } @Override public TAlterSentryRoleGrantPrivilegeResponse alter_sentry_role_grant_privilege( TAlterSentryRoleGrantPrivilegeRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.grantTimer.time(); TAlterSentryRoleGrantPrivilegeResponse response = new TAlterSentryRoleGrantPrivilegeResponse(); try { validateClientVersion(request.getProtocol_version()); // There should only one field be set if (!(request.isSetPrivileges() ^ request.isSetPrivilege())) { throw new SentryUserException("SENTRY API version is not right!"); } // Maintain compatibility for old API: Set privilege field to privileges field if (request.isSetPrivilege()) { request.setPrivileges(Sets.newHashSet(request.getPrivilege())); } // Throw an exception if one of the grants is not permitted. SentryServiceUtil.checkDbExplicitGrantsPermitted(conf, request.getPrivileges()); // Throw an exception if the user has not rights to grant one of the grants requested checkGrantOptionPrivileges(request.getRequestorUserName(), request.getPrivileges()); // TODO: now only has SentryPlugin. Once add more SentryPolicyStorePlugins, // TODO: need to differentiate the updates for different Plugins. Preconditions.checkState(sentryPlugins.size() <= 1); Map<TSentryPrivilege, Update> privilegesUpdateMap = new HashMap<>(); for (SentryPolicyStorePlugin plugin : sentryPlugins) { plugin.onAlterSentryRoleGrantPrivilege(request.getRoleName(), request.getPrivileges(), privilegesUpdateMap); } if (!privilegesUpdateMap.isEmpty()) { sentryStore.alterSentryRoleGrantPrivileges(request.getRoleName(), request.getPrivileges(), privilegesUpdateMap); } else { sentryStore.alterSentryRoleGrantPrivileges(request.getRoleName(), request.getPrivileges()); } GrantPrivilegeRequestValidator.validate(request); response.setStatus(Status.OK()); response.setPrivileges(request.getPrivileges()); // Maintain compatibility for old API: Set privilege field to response if (response.isSetPrivileges() && response.getPrivileges().size() == 1) { response.setPrivilege(response.getPrivileges().iterator().next()); } notificationHandlerInvoker.alter_sentry_role_grant_privilege(request, response); } catch (SentryNoSuchObjectException e) { String msg = "Role: " + request.getRoleName() + " 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 (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(); } audit.onGrantRolePrivilege(request, response); return response; } @Override public TAlterSentryRoleRevokePrivilegeResponse alter_sentry_role_revoke_privilege( TAlterSentryRoleRevokePrivilegeRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.revokeTimer.time(); TAlterSentryRoleRevokePrivilegeResponse response = new TAlterSentryRoleRevokePrivilegeResponse(); try { validateClientVersion(request.getProtocol_version()); // There should only one field be set if (!(request.isSetPrivileges() ^ request.isSetPrivilege())) { throw new SentryUserException("SENTRY API version is not right!"); } // Maintain compatibility for old API: Set privilege field to privileges field if (request.isSetPrivilege()) { request.setPrivileges(Sets.newHashSet(request.getPrivilege())); } // Throw an exception if the user has not rights to revoke one of the revokes requested checkGrantOptionPrivileges(request.getRequestorUserName(), request.getPrivileges()); // TODO: now only has SentryPlugin. Once add more SentryPolicyStorePlugins, // TODO: need to differentiate the updates for different Plugins. Preconditions.checkState(sentryPlugins.size() <= 1); Map<TSentryPrivilege, Update> privilegesUpdateMap = new HashMap<>(); for (SentryPolicyStorePlugin plugin : sentryPlugins) { plugin.onAlterSentryRoleRevokePrivilege(request.getRoleName(), request.getPrivileges(), privilegesUpdateMap); } if (!privilegesUpdateMap.isEmpty()) { sentryStore.alterSentryRoleRevokePrivileges(request.getRoleName(), request.getPrivileges(), privilegesUpdateMap); } else { sentryStore.alterSentryRoleRevokePrivileges(request.getRoleName(), request.getPrivileges()); } RevokePrivilegeRequestValidator.validate(request); response.setStatus(Status.OK()); notificationHandlerInvoker.alter_sentry_role_revoke_privilege(request, response); } catch (SentryNoSuchObjectException e) { StringBuilder msg = new StringBuilder(); if (request.getPrivileges().size() > 0) { for (TSentryPrivilege privilege : request.getPrivileges()) { msg.append("Privilege: [server="); msg.append(privilege.getServerName()); msg.append(",db="); msg.append(privilege.getDbName()); msg.append(",table="); msg.append(privilege.getTableName()); msg.append(",URI="); msg.append(privilege.getURI()); msg.append(",action="); msg.append(privilege.getAction()); msg.append("] "); } msg.append("doesn't exist."); } LOGGER.error(msg.toString(), e); response.setStatus(Status.NoSuchObject(msg.toString(), e)); } catch (SentryInvalidInputException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.InvalidInput(e.getMessage(), 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(); } audit.onRevokeRolePrivilege(request, response); return response; } @Override public TDropSentryRoleResponse drop_sentry_role(TDropSentryRoleRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.dropRoleTimer.time(); TDropSentryRoleResponse response = new TDropSentryRoleResponse(); TSentryResponseStatus status; try { validateClientVersion(request.getProtocol_version()); authorize(request.getRequestorUserName(), getRequestorGroups(request.getRequestorUserName())); // TODO: now only has SentryPlugin. Once add more SentryPolicyStorePlugins, // TODO: need to differentiate the updates for different Plugins. Preconditions.checkState(sentryPlugins.size() <= 1); Update update = null; for (SentryPolicyStorePlugin plugin : sentryPlugins) { update = plugin.onDropSentryRole(request); } if (update != null) { sentryStore.dropSentryRole(request.getRoleName(), update); } else { sentryStore.dropSentryRole(request.getRoleName()); } response.setStatus(Status.OK()); notificationHandlerInvoker.drop_sentry_role(request, response); } catch (SentryNoSuchObjectException e) { String msg = "Role :" + request + " doesn't exist"; 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(); } audit.onDropRole(request, response); return response; } @Override public TAlterSentryRoleAddGroupsResponse alter_sentry_role_add_groups(TAlterSentryRoleAddGroupsRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.grantRoleTimer.time(); TAlterSentryRoleAddGroupsResponse response = new TAlterSentryRoleAddGroupsResponse(); try { validateClientVersion(request.getProtocol_version()); authorize(request.getRequestorUserName(), getRequestorGroups(request.getRequestorUserName())); // TODO: now only has SentryPlugin. Once add more SentryPolicyStorePlugins, // TODO: need to differentiate the updates for different Plugins. Preconditions.checkState(sentryPlugins.size() <= 1); Update update = null; for (SentryPolicyStorePlugin plugin : sentryPlugins) { update = plugin.onAlterSentryRoleAddGroups(request); } if (update != null) { sentryStore.alterSentryRoleAddGroups(request.getRequestorUserName(), request.getRoleName(), request.getGroups(), update); } else { sentryStore.alterSentryRoleAddGroups(request.getRequestorUserName(), request.getRoleName(), request.getGroups()); } response.setStatus(Status.OK()); notificationHandlerInvoker.alter_sentry_role_add_groups(request, response); } catch (SentryNoSuchObjectException e) { String msg = "Role: " + request + " doesn't exist"; 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(); } audit.onGrantRoleToGroup(request, response); return response; } @Override public TAlterSentryRoleAddUsersResponse alter_sentry_role_add_users(TAlterSentryRoleAddUsersRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.grantRoleTimer.time(); TAlterSentryRoleAddUsersResponse response = new TAlterSentryRoleAddUsersResponse(); try { validateClientVersion(request.getProtocol_version()); authorize(request.getRequestorUserName(), getRequestorGroups(request.getRequestorUserName())); sentryStore.alterSentryRoleAddUsers(request.getRoleName(), request.getUsers()); response.setStatus(Status.OK()); notificationHandlerInvoker.alter_sentry_role_add_users(request, response); } catch (SentryNoSuchObjectException e) { String msg = "Role: " + request + " does not exist."; 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(); } audit.onGrantRoleToUser(request, response); return response; } @Override public TAlterSentryRoleDeleteUsersResponse alter_sentry_role_delete_users( TAlterSentryRoleDeleteUsersRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.grantRoleTimer.time(); TAlterSentryRoleDeleteUsersResponse response = new TAlterSentryRoleDeleteUsersResponse(); try { validateClientVersion(request.getProtocol_version()); authorize(request.getRequestorUserName(), getRequestorGroups(request.getRequestorUserName())); sentryStore.alterSentryRoleDeleteUsers(request.getRoleName(), request.getUsers()); response.setStatus(Status.OK()); notificationHandlerInvoker.alter_sentry_role_delete_users(request, response); } catch (SentryNoSuchObjectException e) { String msg = "Role: " + request + " does not exist."; 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(); } audit.onRevokeRoleFromUser(request, response); return response; } @Override public TAlterSentryRoleDeleteGroupsResponse alter_sentry_role_delete_groups( TAlterSentryRoleDeleteGroupsRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.revokeRoleTimer.time(); TAlterSentryRoleDeleteGroupsResponse response = new TAlterSentryRoleDeleteGroupsResponse(); try { validateClientVersion(request.getProtocol_version()); authorize(request.getRequestorUserName(), getRequestorGroups(request.getRequestorUserName())); // TODO: now only has SentryPlugin. Once add more SentryPolicyStorePlugins, // TODO: need to differentiate the updates for different Plugins. Preconditions.checkState(sentryPlugins.size() <= 1); Update update = null; for (SentryPolicyStorePlugin plugin : sentryPlugins) { update = plugin.onAlterSentryRoleDeleteGroups(request); } if (update != null) { sentryStore.alterSentryRoleDeleteGroups(request.getRoleName(), request.getGroups(), update); } else { sentryStore.alterSentryRoleDeleteGroups(request.getRoleName(), request.getGroups()); } response.setStatus(Status.OK()); notificationHandlerInvoker.alter_sentry_role_delete_groups(request, response); } catch (SentryNoSuchObjectException e) { String msg = "Role: " + request + " does not exist."; 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 adding groups to role: " + request; LOGGER.error(msg, e); response.setStatus(Status.RuntimeError(msg, e)); } finally { timerContext.stop(); } audit.onRevokeRoleFromGroup(request, response); return response; } @Override public TListSentryRolesResponse list_sentry_roles_by_group(TListSentryRolesRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.listRolesByGroupTimer.time(); TListSentryRolesResponse response = new TListSentryRolesResponse(); TSentryResponseStatus status; Set<TSentryRole> roleSet = new HashSet<TSentryRole>(); String subject = request.getRequestorUserName(); boolean checkAllGroups = false; try { validateClientVersion(request.getProtocol_version()); Set<String> groups = getRequestorGroups(subject); // Don't check admin permissions for listing requestor's own roles if (AccessConstants.ALL.equalsIgnoreCase(request.getGroupName())) { checkAllGroups = true; } else { boolean admin = inAdminGroups(groups); //Only admin users can list all roles in the system ( groupname = null) //Non admin users are only allowed to list only groups which they belong to if (!admin && (request.getGroupName() == null || !groups.contains(request.getGroupName()))) { throw new SentryAccessDeniedException("Access denied to " + subject); } else { groups.clear(); groups.add(request.getGroupName()); } } roleSet = sentryStore.getTSentryRolesByGroupName(groups, checkAllGroups); response.setRoles(roleSet); response.setStatus(Status.OK()); } catch (SentryNoSuchObjectException e) { response.setRoles(roleSet); String msg = "Request: " + request + " couldn't be completed, message: " + e.getMessage(); 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; } public TListSentryRolesResponse list_sentry_roles_by_user(TListSentryRolesForUserRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.listRolesByGroupTimer.time(); TListSentryRolesResponse response = new TListSentryRolesResponse(); TSentryResponseStatus status; Set<TSentryRole> roleSet = new HashSet<TSentryRole>(); String requestor = request.getRequestorUserName(); String userName = request.getUserName(); boolean checkAllGroups = false; try { validateClientVersion(request.getProtocol_version()); // userName can't be empty if (StringUtils.isEmpty(userName)) { throw new SentryAccessDeniedException("The user name can't be empty."); } Set<String> requestorGroups; try { requestorGroups = getRequestorGroups(requestor); } catch (SentryGroupNotFoundException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.AccessDenied(e.getMessage(), e)); return response; } Set<String> userGroups; try { userGroups = getRequestorGroups(userName); } catch (SentryGroupNotFoundException e) { LOGGER.error(e.getMessage(), e); String msg = "Groups for user " + userName + " do not exist: " + e.getMessage(); response.setStatus(Status.AccessDenied(msg, e)); return response; } boolean isAdmin = inAdminGroups(requestorGroups); // Only admin users can list other user's roles in the system // Non admin users are only allowed to list only their own roles related user and group if (!isAdmin && !userName.equals(requestor)) { throw new SentryAccessDeniedException("Access denied to list the roles for " + userName); } roleSet = sentryStore.getTSentryRolesByUserNames(Sets.newHashSet(userName)); response.setRoles(roleSet); response.setStatus(Status.OK()); } catch (SentryNoSuchObjectException e) { response.setRoles(roleSet); String msg = "Role: " + 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 (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; } @Override public TListSentryPrivilegesResponse list_sentry_privileges_by_role(TListSentryPrivilegesRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.listPrivilegesByRoleTimer.time(); TListSentryPrivilegesResponse response = new TListSentryPrivilegesResponse(); 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.getPrincipalName() != null) ? request.getPrincipalName() : 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(roleName.trim().toLowerCase())) { throw new SentryAccessDeniedException("Access denied to " + subject); } } if (request.isSetAuthorizableHierarchy()) { TSentryAuthorizable authorizableHierarchy = request.getAuthorizableHierarchy(); privilegeSet = sentryStore.getTSentryPrivileges(SentryPrincipalType.ROLE, Sets.newHashSet(roleName), authorizableHierarchy); } else { privilegeSet = sentryStore.getAllTSentryPrivilegesByRoleName(roleName); } 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; } /** * 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 { final Timer.Context timerContext = sentryMetrics.listPrivilegesByUserTimer.time(); TListSentryPrivilegesResponse response = new TListSentryPrivilegesResponse(); Set<TSentryPrivilege> privilegeSet = new HashSet<TSentryPrivilege>(); String subject = request.getRequestorUserName(); // The 'principalName' parameter is made optional in thrift, so we need to check that is not // null before proceed. TSentryResponseStatus status = checkRequiredParameter(request.getPrincipalName(), "principalName parameter must not be null"); if (status != null) { response.setStatus(status); return response; } String userName = request.getPrincipalName().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(SentryPrincipalType.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; } @Override public TListSentryPrivilegesResponse list_sentry_privileges_by_user_and_itsgroups( TListSentryPrivilegesRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.listPrivilegesForUserTimer.time(); TListSentryPrivilegesResponse response = new TListSentryPrivilegesResponse(); // The 'principalName' parameter is made optional in thrift, so we need to // check that is not null before proceed. TSentryResponseStatus status = checkRequiredParameter(request.getPrincipalName(), "principalName parameter must not be null"); if (status != null) { response.setStatus(status); return response; } String requestor = request.getRequestorUserName(); String principalName = request.getPrincipalName().trim(); Set<TSentryPrivilege> privilegeSet = new HashSet<>(); 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> requestorGroups = getRequestorGroups(requestor); Boolean admin = inAdminGroups(requestorGroups); if (!admin && !principalName.equalsIgnoreCase(requestor)) { throw new SentryAccessDeniedException("Access denied to " + requestor); } // Get the groups the user is associated with. Set<String> principalGroups; if (principalName.equals(requestor)) { principalGroups = requestorGroups; } else { principalGroups = getRequestorGroups(principalName); } Set<String> principalUsers = new HashSet<>(); principalUsers.add(principalName); privilegeSet.addAll(sentryStore.listSentryPrivilegesByUsersAndGroups(principalGroups, principalUsers, new TSentryActiveRoleSet(true, null), request.getAuthorizableHierarchy())); response.setPrivileges(privilegeSet); response.setStatus(Status.OK()); } catch (SentryThriftAPIMismatchException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.THRIFT_VERSION_MISMATCH(e.getMessage(), e)); } catch (SentryAccessDeniedException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.AccessDenied(e.getMessage(), e)); } catch (SentryInvalidInputException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.InvalidInput(e.getMessage(), e)); } catch (SentryUserException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.AccessDenied(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; } /** * This method was created specifically for ProviderBackend.getPrivileges() and is not meant * to be used for general privilege retrieval. More details in the .thrift file. */ @Override public TListSentryPrivilegesForProviderResponse list_sentry_privileges_for_provider( TListSentryPrivilegesForProviderRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.listPrivilegesForProviderTimer.time(); TListSentryPrivilegesForProviderResponse response = new TListSentryPrivilegesForProviderResponse(); response.setPrivileges(new HashSet<String>()); try { validateClientVersion(request.getProtocol_version()); Set<String> privilegesForProvider = sentryStore.listSentryPrivilegesForProvider(request.getGroups(), request.getUsers(), request.getRoleSet(), request.getAuthorizableHierarchy()); response.setPrivileges(privilegesForProvider); if (privilegesForProvider == null || privilegesForProvider.size() == 0 && request.getAuthorizableHierarchy() != null && sentryStore.hasAnyServerPrivileges(request.getGroups(), request.getUsers(), request.getRoleSet(), request.getAuthorizableHierarchy().getServer())) { // REQUIRED for ensuring 'default' Db is accessible by any user // with privileges to atleast 1 object with the specific server as root // Need some way to specify that even though user has no privilege // For the specific AuthorizableHierarchy.. he has privilege on // atleast 1 object in the server hierarchy HashSet<String> serverPriv = Sets.newHashSet("server=+"); response.setPrivileges(serverPriv); } response.setStatus(Status.OK()); } 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; } // retrieve the group mapping for the given user name private Set<String> getRequestorGroups(String userName) throws SentryUserException { return getGroupsFromUserName(this.conf, userName); } public static Set<String> getGroupsFromUserName(Configuration conf, String userName) throws SentryUserException { String groupMapping = conf.get(ServerConfig.SENTRY_STORE_GROUP_MAPPING, ServerConfig.SENTRY_STORE_GROUP_MAPPING_DEFAULT); String authResoruce = conf.get(ServerConfig.SENTRY_STORE_GROUP_MAPPING_RESOURCE); // load the group mapping provider class GroupMappingService groupMappingService; try { Constructor<?> constrctor = Class.forName(groupMapping).getDeclaredConstructor(Configuration.class, String.class); constrctor.setAccessible(true); groupMappingService = (GroupMappingService) constrctor.newInstance(new Object[] { conf, authResoruce }); } catch (NoSuchMethodException e) { throw new SentryUserException("Unable to instantiate group mapping", e); } catch (SecurityException e) { throw new SentryUserException("Unable to instantiate group mapping", e); } catch (ClassNotFoundException e) { throw new SentryUserException("Unable to instantiate group mapping", e); } catch (InstantiationException e) { throw new SentryUserException("Unable to instantiate group mapping", e); } catch (IllegalAccessException e) { throw new SentryUserException("Unable to instantiate group mapping", e); } catch (IllegalArgumentException e) { throw new SentryUserException("Unable to instantiate group mapping", e); } catch (InvocationTargetException e) { throw new SentryUserException("Unable to instantiate group mapping", e); } return groupMappingService.getGroups(userName); } @Override public TDropPrivilegesResponse drop_sentry_privilege(TDropPrivilegesRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.dropPrivilegeTimer.time(); TDropPrivilegesResponse response = new TDropPrivilegesResponse(); try { validateClientVersion(request.getProtocol_version()); authorize(request.getRequestorUserName(), adminGroups); // TODO: now only has SentryPlugin. Once add more SentryPolicyStorePlugins, // TODO: need to differentiate the updates for different Plugins. Preconditions.checkState(sentryPlugins.size() <= 1); Update update = null; for (SentryPolicyStorePlugin plugin : sentryPlugins) { update = plugin.onDropSentryPrivilege(request); } if (update != null) { sentryStore.dropPrivilege(request.getAuthorizable(), update); } else { sentryStore.dropPrivilege(request.getAuthorizable()); } response.setStatus(Status.OK()); } 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; } @Override public TRenamePrivilegesResponse rename_sentry_privilege(TRenamePrivilegesRequest request) throws TException { final Timer.Context timerContext = sentryMetrics.renamePrivilegeTimer.time(); TRenamePrivilegesResponse response = new TRenamePrivilegesResponse(); try { validateClientVersion(request.getProtocol_version()); authorize(request.getRequestorUserName(), adminGroups); // TODO: now only has SentryPlugin. Once add more SentryPolicyStorePlugins, // TODO: need to differentiate the updates for different Plugins. Preconditions.checkState(sentryPlugins.size() <= 1); Update update = null; for (SentryPolicyStorePlugin plugin : sentryPlugins) { update = plugin.onRenameSentryPrivilege(request); } if (update != null) { sentryStore.renamePrivilege(request.getOldAuthorizable(), request.getNewAuthorizable(), update); } else { sentryStore.renamePrivilege(request.getOldAuthorizable(), request.getNewAuthorizable()); } response.setStatus(Status.OK()); } 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 (SentryInvalidInputException e) { response.setStatus(Status.InvalidInput(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.close(); } return response; } @Override public TListSentryPrivilegesByAuthResponse list_sentry_privileges_by_authorizable( TListSentryPrivilegesByAuthRequest request) throws TException { 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()); Set<String> memberGroups = getRequestorGroups(subject); if (!inAdminGroups(memberGroups)) { // disallow non-admin to lookup groups that they are not part of if (requestedGroups != null && !requestedGroups.isEmpty()) { for (String requestedGroup : requestedGroups) { if (!memberGroups.contains(requestedGroup)) { // if user doesn't belong to one of the requested group then raise error throw new SentryAccessDeniedException("Access denied to " + subject); } } } else { // non-admin's search is limited to it's own groups requestedGroups = memberGroups; } // disallow non-admin to lookup roles that they are not part of if (requestedRoleSet != null && !requestedRoleSet.isAll()) { Set<String> roles = toTrimedLower(sentryStore.getRoleNamesForGroups(memberGroups)); for (String role : toTrimedLower(requestedRoleSet.getRoles())) { if (!roles.contains(role)) { throw new SentryAccessDeniedException("Access denied to " + subject); } } } // 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); } } } } // 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))); 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) { 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; } /** * Respond to a request for a config value in the sentry server. The client * can request any config value that starts with "sentry." and doesn't contain * "keytab". * @param request Contains config parameter sought and default if not found * @return The response, containing the value and status * @throws TException */ @Override public TSentryConfigValueResponse get_sentry_config_value(TSentryConfigValueRequest request) throws TException { final String requirePattern = "^sentry\\..*"; final String excludePattern = ".*keytab.*|.*\\.jdbc\\..*|.*password.*"; TSentryConfigValueResponse response = new TSentryConfigValueResponse(); String attr = request.getPropertyName(); try { validateClientVersion(request.getProtocol_version()); } catch (SentryThriftAPIMismatchException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.THRIFT_VERSION_MISMATCH(e.getMessage(), e)); } // Only allow config parameters like... if (!Pattern.matches(requirePattern, attr) || Pattern.matches(excludePattern, attr)) { String msg = "Attempted access of the configuration property " + attr + " was denied"; LOGGER.error(msg); response.setStatus(Status.AccessDenied(msg, new SentryAccessDeniedException(msg))); return response; } response.setValue(conf.get(attr, request.getDefaultValue())); response.setStatus(Status.OK()); return response; } @VisibleForTesting static void validateClientVersion(int protocolVersion) throws SentryThriftAPIMismatchException { if (ThriftConstants.TSENTRY_SERVICE_VERSION_CURRENT != protocolVersion) { String msg = "Sentry thrift API protocol version mismatch: Client thrift version " + "is: " + protocolVersion + " , server thrift verion " + "is " + ThriftConstants.TSENTRY_SERVICE_VERSION_CURRENT; throw new SentryThriftAPIMismatchException(msg); } } // get the sentry mapping data and return the data with map structure @Override public TSentryExportMappingDataResponse export_sentry_mapping_data(TSentryExportMappingDataRequest request) throws TException { TSentryExportMappingDataResponse response = new TSentryExportMappingDataResponse(); try { String requestor = request.getRequestorUserName(); Set<String> memberGroups = getRequestorGroups(requestor); String objectPath = request.getObjectPath(); String databaseName = null; String tableName = null; Map<String, String> objectMap = SentryServiceUtil.parseObjectPath(objectPath); databaseName = objectMap.get(PolicyFileConstants.PRIVILEGE_DATABASE_NAME); tableName = objectMap.get(PolicyFileConstants.PRIVILEGE_TABLE_NAME); if (!inAdminGroups(memberGroups)) { // disallow non-admin to import the metadata of sentry throw new SentryAccessDeniedException( "Access denied to " + requestor + " for export the metadata of sentry."); } TSentryMappingData tSentryMappingData = new TSentryMappingData(); Map<String, Set<TSentryPrivilege>> rolePrivileges = sentryStore.getRoleNameTPrivilegesMap(databaseName, tableName); tSentryMappingData.setRolePrivilegesMap(rolePrivileges); Set<String> roleNames = rolePrivileges.keySet(); // roleNames should be null if databaseName == null and tableName == null if (databaseName == null && tableName == null) { roleNames = null; } List<Map<String, Set<String>>> mapList = sentryStore.getGroupUserRoleMapList(roleNames); tSentryMappingData.setGroupRolesMap(mapList.get(SentryConstants.INDEX_GROUP_ROLES_MAP)); tSentryMappingData.setUserRolesMap(mapList.get(SentryConstants.INDEX_USER_ROLES_MAP)); response.setMappingData(tSentryMappingData); response.setStatus(Status.OK()); } 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 (Exception e) { String msg = "Unknown error for request: " + request + ", message: " + e.getMessage(); LOGGER.error(msg, e); response.setMappingData(new TSentryMappingData()); response.setStatus(Status.RuntimeError(msg, e)); } return response; } // import the sentry mapping data @Override public TSentryImportMappingDataResponse import_sentry_mapping_data(TSentryImportMappingDataRequest request) throws TException { TSentryImportMappingDataResponse response = new TSentryImportMappingDataResponse(); try { String requestor = request.getRequestorUserName(); Set<String> memberGroups = getRequestorGroups(requestor); if (!inAdminGroups(memberGroups)) { // disallow non-admin to import the metadata of sentry throw new SentryAccessDeniedException( "Access denied to " + requestor + " for import the metadata of sentry."); } sentryStore.importSentryMetaData(request.getMappingData(), request.isOverwriteRole()); response.setStatus(Status.OK()); } 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 (SentryInvalidInputException e) { String msg = "Invalid input privilege object"; LOGGER.error(msg, e); response.setStatus(Status.InvalidInput(msg, e)); } catch (Exception e) { String msg = "Unknown error for request: " + request + ", message: " + e.getMessage(); LOGGER.error(msg, e); response.setStatus(Status.RuntimeError(msg, e)); } return response; } @Override public TSentrySyncIDResponse sentry_sync_notifications(TSentrySyncIDRequest request) throws TException { TSentrySyncIDResponse response = new TSentrySyncIDResponse(); try (Timer.Context timerContext = hmsWaitTimer.time()) { // Wait until Sentry Server processes specified HMS Notification ID. response.setId(sentryStore.getCounterWait().waitFor(request.getId())); response.setStatus(Status.OK()); } catch (InterruptedException e) { String msg = String.format("wait request for id %d is interrupted", request.getId()); LOGGER.error(msg, e); response.setId(0); response.setStatus(Status.RuntimeError(msg, e)); Thread.currentThread().interrupt(); } catch (TimeoutException e) { String msg = String.format("timed out wait request for id %d", request.getId()); LOGGER.warn(msg, e); response.setId(0); response.setStatus(Status.RuntimeError(msg, e)); } return response; } @Override public TSentryHmsEventNotificationResponse sentry_notify_hms_event(TSentryHmsEventNotification request) throws TException { TSentryHmsEventNotificationResponse response = new TSentryHmsEventNotificationResponse(); EventType eventType = EventType.valueOf(request.getEventType()); try (Timer.Context timerContext = sentryMetrics.notificationProcessTimer.time()) { 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())); } else { response.setId(0L); } //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())); } else { response.setId(0L); } // 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 */ // case ALTER_DATABASE: TODO: Enable once HIVE-18031 is available // Wait till Sentry server processes HMS Notification Event. if (request.getId() > 0) { response.setId(syncEventId(request.getId())); } else { response.setId(0L); } // When owner is updated, 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)); } return response; } @Override public TSentryPrivilegesResponse list_roles_privileges(TSentryPrivilegesRequest request) throws TException { TSentryPrivilegesResponse response = new TSentryPrivilegesResponse(); String requestor = request.getRequestorUserName(); try (Timer.Context timerContext = sentryMetrics.listRolesPrivilegesTimer.time()) { // Throws SentryThriftAPIMismatchException if protocol version mismatch validateClientVersion(request.getProtocol_version()); // Throws SentryUserException with the Status.ACCESS_DENIED status if the requestor // is not an admin. Only admins can request all roles and privileges of the system. authorize(requestor, getRequestorGroups(requestor)); response.setPrivilegesMap(sentryStore.getAllRolesPrivileges()); response.setStatus(Status.OK()); } catch (SentryThriftAPIMismatchException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.THRIFT_VERSION_MISMATCH(e.getMessage(), e)); } catch (SentryAccessDeniedException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.AccessDenied(e.getMessage(), e)); } catch (SentryUserException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.AccessDenied(e.getMessage(), e)); } catch (Exception e) { String msg = "Could not read roles and privileges from the database: " + e.getMessage(); LOGGER.error(msg, e); response.setStatus(Status.RuntimeError(msg, e)); } return response; } @Override public TSentryPrivilegesResponse list_users_privileges(TSentryPrivilegesRequest request) throws TException { TSentryPrivilegesResponse response = new TSentryPrivilegesResponse(); String requestor = request.getRequestorUserName(); try (Timer.Context timerContext = sentryMetrics.listUsersPrivilegesTimer.time()) { // Throws SentryThriftAPIMismatchException if protocol version mismatch validateClientVersion(request.getProtocol_version()); // Throws SentryUserException with the Status.ACCESS_DENIED status if the requestor // is not an admin. Only admins can request all users and privileges of the system. authorize(requestor, getRequestorGroups(requestor)); response.setPrivilegesMap(sentryStore.getAllUsersPrivileges()); response.setStatus(Status.OK()); } catch (SentryThriftAPIMismatchException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.THRIFT_VERSION_MISMATCH(e.getMessage(), e)); } catch (SentryAccessDeniedException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.AccessDenied(e.getMessage(), e)); } catch (SentryUserException e) { LOGGER.error(e.getMessage(), e); response.setStatus(Status.AccessDenied(e.getMessage(), e)); } catch (Exception e) { String msg = "Could not read users and privileges from the database: " + e.getMessage(); LOGGER.error(msg, e); response.setStatus(Status.RuntimeError(msg, e)); } 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; } SentryPrincipalType principalType = getSentryPrincipalType(request.getOwnerType()); if (principalType == 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<>(); getOwnerPrivilegeUpdateForGrant(request.getOwnerName(), request.getOwnerType(), privSet, privilegesUpdateMap); // Grants owner privilege to the principal try { sentryStore.alterSentryGrantOwnerPrivilege(request.getOwnerName(), principalType, ownerPrivilege, privilegesUpdateMap.get(ownerPrivilege)); audit.onGrantOwnerPrivilege(Status.OK(), request.getRequestorUserName(), request.getOwnerType(), request.getOwnerName(), request.getAuthorizable()); } catch (Exception e) { String msg = "Owner privilege for " + request.getAuthorizable() + " could not be granted: " + e.getMessage(); audit.onGrantOwnerPrivilege(Status.RuntimeError(msg, e), request.getRequestorUserName(), request.getOwnerType(), request.getOwnerName(), request.getAuthorizable()); throw e; } //TODO Implement notificationHandlerInvoker API for granting user priv and invoke it. } /** * 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; } SentryPrincipalType principalType = getSentryPrincipalType(request.getOwnerType()); if (principalType == 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 (SentryPolicyStorePlugin plugin : sentryPlugins) { for (SentryOwnerInfo ownerInfo : ownerInfoList) { if (ownerInfo.getOwnerType().equals(SentryPrincipalType.USER)) { plugin.onAlterSentryUserRevokePrivilege(ownerInfo.getOwnerName(), privSet, privilegesUpdateMap); updateList.add(privilegesUpdateMap.get(ownerPrivilege)); privilegesUpdateMap.clear(); } else if (ownerInfo.getOwnerType().equals(SentryPrincipalType.ROLE)) { plugin.onAlterSentryRoleRevokePrivilege(request.getOwnerName(), privSet, privilegesUpdateMap); updateList.add(privilegesUpdateMap.get(ownerPrivilege)); privilegesUpdateMap.clear(); } } } getOwnerPrivilegeUpdateForGrant(request.getOwnerName(), request.getOwnerType(), privSet, privilegesUpdateMap); updateList.add(privilegesUpdateMap.get(ownerPrivilege)); // Revokes old owner privileges and grants owner privilege for new owner. try { sentryStore.updateOwnerPrivilege(request.getAuthorizable(), request.getOwnerName(), principalType, updateList); audit.onTransferOwnerPrivilege(Status.OK(), request.getRequestorUserName(), request.getOwnerType(), request.getOwnerName(), request.getAuthorizable()); } catch (Exception e) { String msg = "Owner privilege for " + request.getAuthorizable() + " could not be granted: " + e.getMessage(); audit.onTransferOwnerPrivilege(Status.RuntimeError(msg, e), request.getRequestorUserName(), request.getOwnerType(), request.getOwnerName(), request.getAuthorizable()); throw e; } //TODO Implement notificationHandlerInvoker API for granting user priv and invoke it. } /** * Adds privilege update for grant into the privilegesUpdateMap provided. * @param ownerName * @param ownerType * @param privSet * @param privilegesUpdateMap * @throws Exception */ private void getOwnerPrivilegeUpdateForGrant(String ownerName, TSentryPrincipalType ownerType, Set<TSentryPrivilege> privSet, Map<TSentryPrivilege, Update> privilegesUpdateMap) throws Exception { for (SentryPolicyStorePlugin plugin : sentryPlugins) { switch (ownerType) { case ROLE: plugin.onAlterSentryRoleGrantPrivilege(ownerName, privSet, privilegesUpdateMap); break; case USER: plugin.onAlterSentryUserGrantPrivilege(ownerName, privSet, privilegesUpdateMap); break; default: String error = "Invalid owner type : " + ownerType; LOGGER.error(error); throw new SentryInvalidInputException(error); } } } /** * This API constructs (@Link TSentryPrivilege} for authorizable provided * based on the configurations. * * @param authorizable for which owner privilege should be constructed. * @return null if owner privilege can not be constructed, else instance of {@Link TSentryPrivilege} */ TSentryPrivilege constructOwnerPrivilege(TSentryAuthorizable authorizable) { SentryOwnerPrivilegeType ownerPrivilegeType = SentryOwnerPrivilegeType.get(conf); if (ownerPrivilegeType == SentryOwnerPrivilegeType.NONE) { return null; } if (Strings.isNullOrEmpty(authorizable.getDb())) { LOGGER.error("Received authorizable with out DB Name"); return null; } TSentryPrivilege ownerPrivilege = new TSentryPrivilege(); ownerPrivilege.setServerName(authorizable.getServer()); ownerPrivilege.setDbName(authorizable.getDb()); if (!Strings.isNullOrEmpty(authorizable.getTable())) { ownerPrivilege.setTableName(authorizable.getTable()); ownerPrivilege.setPrivilegeScope("TABLE"); } else { ownerPrivilege.setPrivilegeScope("DATABASE"); } if (ownerPrivilegeType == SentryOwnerPrivilegeType.ALL_WITH_GRANT) { ownerPrivilege.setGrantOption(TSentryGrantOption.TRUE); } ownerPrivilege.setAction(AccessConstants.OWNER); return ownerPrivilege; } /** * * @param ownerType * @return SentryPrincipalType if input was valid, otherwise returns null * @throws Exception */ private SentryPrincipalType getSentryPrincipalType(TSentryPrincipalType 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 { if (!SentryStateBank.isEnabled(FullUpdateInitializerState.COMPONENT, FullUpdateInitializerState.FULL_SNAPSHOT_INPROGRESS)) { return sentryStore.getCounterWait().waitFor(eventId); } else { LOGGER.info("HMS event synchronization is disabled temporarily as sentry is in the process of " + "fetching full snapshot. No action needed"); return 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; } }