http://git-wip-us.apache.org/repos/asf/sentry/blob/e72e6eac/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java new file mode 100644 index 0000000..5dff12a --- /dev/null +++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java @@ -0,0 +1,1111 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sentry.provider.db.service.thrift; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.commons.lang.StringUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.sentry.core.common.exception.SentryUserException; +import org.apache.sentry.core.common.exception.SentrySiteConfigurationException; +import org.apache.sentry.core.model.db.AccessConstants; +import org.apache.sentry.core.common.service.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.log.entity.JsonLogEntity; +import org.apache.sentry.provider.db.log.entity.JsonLogEntityFactory; +import org.apache.sentry.provider.db.log.util.Constants; +import org.apache.sentry.provider.db.service.persistent.CommitContext; +import org.apache.sentry.provider.db.service.persistent.HAContext; +import org.apache.sentry.provider.db.service.persistent.SentryStore; +import org.apache.sentry.provider.db.service.persistent.ServiceRegister; +import org.apache.sentry.provider.db.service.thrift.PolicyStoreConstants.PolicyStoreServerConfig; +import org.apache.sentry.service.thrift.SentryServiceUtil; +import org.apache.sentry.service.thrift.ServiceConstants; +import org.apache.sentry.service.thrift.ServiceConstants.ConfUtilties; +import org.apache.sentry.service.thrift.ServiceConstants.ServerConfig; +import org.apache.sentry.service.thrift.ServiceConstants.ThriftConstants; +import org.apache.sentry.service.thrift.Status; +import org.apache.sentry.service.thrift.TSentryResponseStatus; +import org.apache.thrift.TException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.codahale.metrics.Timer; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +@SuppressWarnings("unused") +public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface { + private static final Logger LOGGER = LoggerFactory.getLogger(SentryPolicyStoreProcessor.class); + private static final Logger AUDIT_LOGGER = LoggerFactory.getLogger(Constants.AUDIT_LOGGER_NAME); + + public static volatile SentryPolicyStoreProcessor instance; + + private final String name; + private final Configuration conf; + private final SentryStore sentryStore; + private final NotificationHandlerInvoker notificationHandlerInvoker; + private final ImmutableSet<String> adminGroups; + private boolean isReady; + SentryMetrics sentryMetrics; + private HAContext haContext; + + private List<SentryPolicyStorePlugin> sentryPlugins = new LinkedList<SentryPolicyStorePlugin>(); + + public SentryPolicyStoreProcessor(String name, Configuration conf) throws Exception { + super(); + this.name = name; + this.conf = conf; + this.notificationHandlerInvoker = new NotificationHandlerInvoker(conf, + createHandlers(conf)); + isReady = false; + if (conf.getBoolean(ServerConfig.SENTRY_HA_ENABLED, + ServerConfig.SENTRY_HA_ENABLED_DEFAULT)) { + haContext = HAContext.getHAServerContext(conf); + sentryStore = new SentryStore(conf); + ServiceRegister reg = new ServiceRegister(haContext); + reg.regService(conf.get(ServerConfig.RPC_ADDRESS), + conf.getInt(ServerConfig.RPC_PORT,ServerConfig.RPC_PORT_DEFAULT)); + } else { + sentryStore = new SentryStore(conf); + } + isReady = true; + 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); + } + if (instance == null) { + instance = this; + } + initMetrics(); + } + + private void initMetrics() { + sentryMetrics = SentryMetrics.getInstance(); + sentryMetrics.addSentryStoreGauges(sentryStore); + + String sentryReporting = conf.get(ServerConfig.SENTRY_REPORTER); + if (sentryReporting != null) { + SentryMetrics.Reporting reporting; + try { + reporting = SentryMetrics.Reporting.valueOf(sentryReporting.toUpperCase()); + sentryMetrics.initReporting(reporting); + + } catch (IllegalArgumentException e) { + LOGGER.warn("Metrics reporting not configured correctly, please set " + ServerConfig.SENTRY_REPORTER + + " to: " + SentryMetrics.Reporting.CONSOLE.name() + "/" + SentryMetrics.Reporting.JMX.name()); + } + } + } + + public void stop() { + if (isReady) { + sentryStore.stop(); + } + if (haContext != null) { + try { + haContext.getCuratorFramework().close(); + } catch (Exception e) { + LOGGER.warn("Error in stopping processor", e); + } + } + } + + 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())); + CommitContext commitContext = sentryStore.createSentryRole(request.getRoleName()); + response.setStatus(Status.OK()); + notificationHandlerInvoker.create_sentry_role(commitContext, + request, response); + } catch (SentryAlreadyExistsException e) { + String msg = "Role: " + request + " already exists."; + LOGGER.error(msg, e); + response.setStatus(Status.AlreadyExists(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(); + } + + try { + AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance() + .createJsonLogEntity(request, response, conf).toJsonFormatLog()); + } catch (Exception e) { + // if any exception, log the exception. + String msg = "Error creating audit log for create role: " + e.getMessage(); + LOGGER.error(msg, e); + } + return response; + } + + @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())); + } + CommitContext commitContext = sentryStore.alterSentryRoleGrantPrivileges(request.getRequestorUserName(), + request.getRoleName(), request.getPrivileges()); + 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(commitContext, + request, response); + for (SentryPolicyStorePlugin plugin : sentryPlugins) { + plugin.onAlterSentryRoleGrantPrivilege(request); + } + } catch (SentryNoSuchObjectException e) { + String msg = "Role: " + request.getRoleName() + " doesn't exist"; + LOGGER.error(msg, e); + response.setStatus(Status.NoSuchObject(msg, e)); + } catch (SentryInvalidInputException e) { + String msg = "Invalid input privilege object"; + LOGGER.error(msg, e); + response.setStatus(Status.InvalidInput(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(); + } + + try { + Set<JsonLogEntity> jsonLogEntitys = JsonLogEntityFactory.getInstance().createJsonLogEntitys( + request, response, conf); + for (JsonLogEntity jsonLogEntity : jsonLogEntitys) { + AUDIT_LOGGER.info(jsonLogEntity.toJsonFormatLog()); + } + } catch (Exception e) { + // if any exception, log the exception. + String msg = "Error creating audit log for grant privilege to role: " + e.getMessage(); + LOGGER.error(msg, e); + } + 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())); + } + CommitContext commitContext = sentryStore.alterSentryRoleRevokePrivileges(request.getRequestorUserName(), + request.getRoleName(), request.getPrivileges()); + response.setStatus(Status.OK()); + notificationHandlerInvoker.alter_sentry_role_revoke_privilege(commitContext, + request, response); + for (SentryPolicyStorePlugin plugin : sentryPlugins) { + plugin.onAlterSentryRoleRevokePrivilege(request); + } + } 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) { + String msg = "Invalid input privilege object"; + LOGGER.error(msg, e); + response.setStatus(Status.InvalidInput(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(); + } + + try { + Set<JsonLogEntity> jsonLogEntitys = JsonLogEntityFactory.getInstance().createJsonLogEntitys( + request, response, conf); + for (JsonLogEntity jsonLogEntity : jsonLogEntitys) { + AUDIT_LOGGER.info(jsonLogEntity.toJsonFormatLog()); + } + } catch (Exception e) { + // if any exception, log the exception. + String msg = "Error creating audit log for revoke privilege from role: " + e.getMessage(); + LOGGER.error(msg, e); + } + 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())); + CommitContext commitContext = sentryStore.dropSentryRole(request.getRoleName()); + response.setStatus(Status.OK()); + notificationHandlerInvoker.drop_sentry_role(commitContext, + request, response); + for (SentryPolicyStorePlugin plugin : sentryPlugins) { + plugin.onDropSentryRole(request); + } + } 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 (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(); + } + + try { + AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance() + .createJsonLogEntity(request, response, conf).toJsonFormatLog()); + } catch (Exception e) { + // if any exception, log the exception. + String msg = "Error creating audit log for drop role: " + e.getMessage(); + LOGGER.error(msg, e); + } + 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())); + CommitContext commitContext = sentryStore.alterSentryRoleAddGroups( + request.getRequestorUserName(), request.getRoleName(), + request.getGroups()); + response.setStatus(Status.OK()); + notificationHandlerInvoker.alter_sentry_role_add_groups(commitContext, + request, response); + for (SentryPolicyStorePlugin plugin : sentryPlugins) { + plugin.onAlterSentryRoleAddGroups(request); + } + } 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 (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(); + } + + try { + AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance() + .createJsonLogEntity(request, response, conf).toJsonFormatLog()); + } catch (Exception e) { + // if any exception, log the exception. + String msg = "Error creating audit log for add role to group: " + e.getMessage(); + LOGGER.error(msg, e); + } + 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())); + CommitContext commitContext = sentryStore.alterSentryRoleAddUsers(request.getRoleName(), + request.getUsers()); + response.setStatus(Status.OK()); + notificationHandlerInvoker.alter_sentry_role_add_users(commitContext, 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 (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(); + } + + try { + AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance() + .createJsonLogEntity(request, response, conf).toJsonFormatLog()); + } catch (Exception e) { + // if any exception, log the exception. + String msg = "Error creating audit log for add role to user: " + e.getMessage(); + LOGGER.error(msg, e); + } + 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())); + CommitContext commitContext = sentryStore.alterSentryRoleDeleteUsers(request.getRoleName(), + request.getUsers()); + response.setStatus(Status.OK()); + notificationHandlerInvoker.alter_sentry_role_delete_users(commitContext, 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 (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(); + } + + try { + AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance() + .createJsonLogEntity(request, response, conf).toJsonFormatLog()); + } catch (Exception e) { + // if any exception, log the exception. + String msg = "Error creating audit log for delete role from user: " + e.getMessage(); + LOGGER.error(msg, e); + } + 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())); + CommitContext commitContext = sentryStore.alterSentryRoleDeleteGroups(request.getRoleName(), + request.getGroups()); + response.setStatus(Status.OK()); + notificationHandlerInvoker.alter_sentry_role_delete_groups(commitContext, + request, response); + for (SentryPolicyStorePlugin plugin : sentryPlugins) { + plugin.onAlterSentryRoleDeleteGroups(request); + } + } 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 (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(); + } + + try { + AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance() + .createJsonLogEntity(request, response, conf).toJsonFormatLog()); + } catch (Exception e) { + // if any exception, log the exception. + String msg = "Error creating audit log for delete role from group: " + e.getMessage(); + LOGGER.error(msg, e); + } + 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 (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 = getRequestorGroups(requestor); + Set<String> userGroups = getRequestorGroups(userName); + 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 (SentryGroupNotFoundException e) { + LOGGER.error(e.getMessage(), e); + String msg = "Group couldn't be retrieved for " + requestor + " or " + userName + "."; + response.setStatus(Status.AccessDenied(msg, e)); + } 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(); + try { + validateClientVersion(request.getProtocol_version()); + Set<String> groups = getRequestorGroups(subject); + Boolean admin = inAdminGroups(groups); + if(!admin) { + Set<String> roleNamesForGroups = toTrimedLower(sentryStore.getRoleNamesForGroups(groups)); + if(!roleNamesForGroups.contains(request.getRoleName().trim().toLowerCase())) { + throw new SentryAccessDeniedException("Access denied to " + subject); + } + } + if (request.isSetAuthorizableHierarchy()) { + TSentryAuthorizable authorizableHierarchy = request.getAuthorizableHierarchy(); + privilegeSet = sentryStore.getTSentryPrivileges(Sets.newHashSet(request.getRoleName()), authorizableHierarchy); + } else { + privilegeSet = sentryStore.getAllTSentryPrivilegesByRoleName(request.getRoleName()); + } + 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 (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 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); + sentryStore.dropPrivilege(request.getAuthorizable()); + for (SentryPolicyStorePlugin plugin : sentryPlugins) { + plugin.onDropSentryPrivilege(request); + } + response.setStatus(Status.OK()); + } 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 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); + sentryStore.renamePrivilege(request.getOldAuthorizable(), + request.getNewAuthorizable()); + for (SentryPolicyStorePlugin plugin : sentryPlugins) { + plugin.onRenameSentryPrivilege(request); + } + response.setStatus(Status.OK()); + } 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.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(); + String subject = request.getRequestorUserName(); + Set<String> requestedGroups = request.getGroups(); + 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); + } + } + } + } + + // If user is not part of any group.. return empty response + for (TSentryAuthorizable authorizable : request.getAuthorizableSet()) { + authRoleMap.put(authorizable, sentryStore + .listSentryPrivilegesByAuthorizable(requestedGroups, + request.getRoleSet(), authorizable, inAdminGroups(memberGroups))); + } + response.setPrivilegesMapByAuth(authRoleMap); + 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 (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 (ServiceConstants.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( + SentryStore.INDEX_GROUP_ROLES_MAP)); + tSentryMappingData.setUserRolesMap(mapList.get(SentryStore.INDEX_USER_ROLES_MAP)); + + response.setMappingData(tSentryMappingData); + response.setStatus(Status.OK()); + } 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 (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; + } +}
http://git-wip-us.apache.org/repos/asf/sentry/blob/e72e6eac/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessorFactory.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessorFactory.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessorFactory.java new file mode 100644 index 0000000..45966e5 --- /dev/null +++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessorFactory.java @@ -0,0 +1,40 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sentry.provider.db.service.thrift; + +import org.apache.hadoop.conf.Configuration; +import org.apache.sentry.service.thrift.ProcessorFactory; +import org.apache.sentry.service.thrift.ServiceConstants; +import org.apache.thrift.TMultiplexedProcessor; +import org.apache.thrift.TProcessor; + +public class SentryPolicyStoreProcessorFactory extends ProcessorFactory { + public SentryPolicyStoreProcessorFactory(Configuration conf) { + super(conf); + } + + public boolean register(TMultiplexedProcessor multiplexedProcessor) throws Exception { + SentryPolicyStoreProcessor sentryServiceHandler = + new SentryPolicyStoreProcessor(ServiceConstants.SENTRY_POLICY_SERVICE_NAME, + conf); + TProcessor processor = + new SentryProcessorWrapper<SentryPolicyService.Iface>(sentryServiceHandler); + multiplexedProcessor.registerProcessor(ServiceConstants.SENTRY_POLICY_SERVICE_NAME, processor); + return true; + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/e72e6eac/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryProcessorWrapper.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryProcessorWrapper.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryProcessorWrapper.java new file mode 100644 index 0000000..a5f11a9 --- /dev/null +++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryProcessorWrapper.java @@ -0,0 +1,37 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sentry.provider.db.service.thrift; + +import org.apache.thrift.TException; +import org.apache.thrift.protocol.TProtocol; + +public class SentryProcessorWrapper<I extends SentryPolicyService.Iface> extends + SentryPolicyService.Processor<SentryPolicyService.Iface> { + + public SentryProcessorWrapper(I iface) { + super(iface); + } + + @Override + public boolean process(TProtocol in, TProtocol out) throws TException { + ThriftUtil.setIpAddress(in); + ThriftUtil.setImpersonator(in); + return super.process(in, out); + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/e72e6eac/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryWebServer.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryWebServer.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryWebServer.java new file mode 100644 index 0000000..a42f395 --- /dev/null +++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryWebServer.java @@ -0,0 +1,184 @@ +package org.apache.sentry.provider.db.service.thrift; + +/** + * 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. + */ + +import com.codahale.metrics.servlets.AdminServlet; +import com.google.common.base.Preconditions; + +import java.io.IOException; +import java.util.EnumSet; +import java.net.URL; +import java.util.EventListener; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.Sets; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.SecurityUtil; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.server.AuthenticationFilter; +import org.apache.sentry.service.thrift.ServiceConstants.ServerConfig; +import org.eclipse.jetty.server.DispatcherType; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.handler.ResourceHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SentryWebServer { + + private static final Logger LOGGER = LoggerFactory.getLogger(SentryWebServer.class); + private static final String RESOURCE_DIR = "/webapp"; + private static final String WELCOME_PAGE = "SentryService.html"; + + private Server server; + + public SentryWebServer(List<EventListener> listeners, int port, Configuration conf) { + server = new Server(); + + // Create a channel connector for "http/https" requests + SelectChannelConnector connector = new SelectChannelConnector(); + if (conf.getBoolean(ServerConfig.SENTRY_WEB_USE_SSL, false)) { + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setKeyStorePath(conf.get(ServerConfig.SENTRY_WEB_SSL_KEYSTORE_PATH, "")); + sslContextFactory.setKeyStorePassword( + conf.get(ServerConfig.SENTRY_WEB_SSL_KEYSTORE_PASSWORD, "")); + // Exclude SSL blacklist protocols + sslContextFactory.setExcludeProtocols(ServerConfig.SENTRY_SSL_PROTOCOL_BLACKLIST_DEFAULT); + Set<String> moreExcludedSSLProtocols = + Sets.newHashSet(Splitter.on(",").trimResults().omitEmptyStrings() + .split(Strings.nullToEmpty(conf.get(ServerConfig.SENTRY_SSL_PROTOCOL_BLACKLIST)))); + sslContextFactory.addExcludeProtocols(moreExcludedSSLProtocols.toArray( + new String[moreExcludedSSLProtocols.size()])); + connector = new SslSelectChannelConnector(sslContextFactory); + LOGGER.info("Now using SSL mode."); + } + + connector.setPort(port); + server.addConnector(connector); + + ServletContextHandler servletContextHandler = new ServletContextHandler(); + ServletHolder servletHolder = new ServletHolder(AdminServlet.class); + servletContextHandler.addServlet(servletHolder, "/*"); + + for(EventListener listener:listeners) { + servletContextHandler.addEventListener(listener); + } + + ServletHolder confServletHolder = new ServletHolder(ConfServlet.class); + servletContextHandler.addServlet(confServletHolder, "/conf"); + servletContextHandler.getServletContext() + .setAttribute(ConfServlet.CONF_CONTEXT_ATTRIBUTE, conf); + + ResourceHandler resourceHandler = new ResourceHandler(); + resourceHandler.setDirectoriesListed(true); + URL url = this.getClass().getResource(RESOURCE_DIR); + try { + resourceHandler.setBaseResource(Resource.newResource(url.toString())); + } catch (IOException e) { + LOGGER.error("Got exception while setBaseResource for Sentry Service web UI", e); + } + resourceHandler.setWelcomeFiles(new String[]{WELCOME_PAGE}); + ContextHandler contextHandler= new ContextHandler(); + contextHandler.setHandler(resourceHandler); + + ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); + contextHandlerCollection.setHandlers(new Handler[]{contextHandler, servletContextHandler}); + + String authMethod = conf.get(ServerConfig.SENTRY_WEB_SECURITY_TYPE); + if (!ServerConfig.SENTRY_WEB_SECURITY_TYPE_NONE.equals(authMethod)) { + /** + * SentryAuthFilter is a subclass of AuthenticationFilter and + * AuthenticationFilter tagged as private and unstable interface: + * While there are not guarantees that this interface will not change, + * it is fairly stable and used by other projects (ie - Oozie) + */ + FilterHolder filterHolder = servletContextHandler.addFilter(SentryAuthFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + filterHolder.setInitParameters(loadWebAuthenticationConf(conf)); + } + + server.setHandler(contextHandlerCollection); + } + + public void start() throws Exception{ + server.start(); + } + public void stop() throws Exception{ + server.stop(); + } + public boolean isAlive() { + return server != null && server.isStarted(); + } + private static Map<String, String> loadWebAuthenticationConf(Configuration conf) { + Map<String,String> prop = new HashMap<String, String>(); + prop.put(AuthenticationFilter.CONFIG_PREFIX, ServerConfig.SENTRY_WEB_SECURITY_PREFIX); + String allowUsers = conf.get(ServerConfig.SENTRY_WEB_SECURITY_ALLOW_CONNECT_USERS); + if (allowUsers == null || allowUsers.equals("")) { + allowUsers = conf.get(ServerConfig.ALLOW_CONNECT); + conf.set(ServerConfig.SENTRY_WEB_SECURITY_ALLOW_CONNECT_USERS, allowUsers); + } + validateConf(conf); + for (Map.Entry<String, String> entry : conf) { + String name = entry.getKey(); + if (name.startsWith(ServerConfig.SENTRY_WEB_SECURITY_PREFIX)) { + String value = conf.get(name); + prop.put(name, value); + } + } + return prop; + } + + private static void validateConf(Configuration conf) { + String authHandlerName = conf.get(ServerConfig.SENTRY_WEB_SECURITY_TYPE); + Preconditions.checkNotNull(authHandlerName, "Web authHandler should not be null."); + String allowUsers = conf.get(ServerConfig.SENTRY_WEB_SECURITY_ALLOW_CONNECT_USERS); + Preconditions.checkNotNull(allowUsers, "Allow connect user(s) should not be null."); + if (ServerConfig.SENTRY_WEB_SECURITY_TYPE_KERBEROS.equalsIgnoreCase(authHandlerName)) { + String principal = conf.get(ServerConfig.SENTRY_WEB_SECURITY_PRINCIPAL); + Preconditions.checkNotNull(principal, "Kerberos principal should not be null."); + Preconditions.checkArgument(principal.length() != 0, "Kerberos principal is not right."); + String keytabFile = conf.get(ServerConfig.SENTRY_WEB_SECURITY_KEYTAB); + Preconditions.checkNotNull(keytabFile, "Keytab File should not be null."); + Preconditions.checkArgument(keytabFile.length() != 0, "Keytab File is not right."); + try { + UserGroupInformation.setConfiguration(conf); + String hostPrincipal = SecurityUtil.getServerPrincipal(principal, ServerConfig.RPC_ADDRESS_DEFAULT); + UserGroupInformation.loginUserFromKeytab(hostPrincipal, keytabFile); + } catch (IOException ex) { + throw new IllegalArgumentException("Can't use Kerberos authentication, principal [" + + principal + "] keytab [" + keytabFile + "]", ex); + } + LOGGER.info("Using Kerberos authentication, principal [" + + principal + "] keytab [" + keytabFile + "]"); + } + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/e72e6eac/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/ThriftUtil.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/ThriftUtil.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/ThriftUtil.java new file mode 100644 index 0000000..3a96d0b --- /dev/null +++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/service/thrift/ThriftUtil.java @@ -0,0 +1,112 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sentry.provider.db.service.thrift; + +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.transport.TSaslClientTransport; +import org.apache.thrift.transport.TSaslServerTransport; +import org.apache.thrift.transport.TSocket; +import org.apache.thrift.transport.TTransport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +public final class ThriftUtil { + + private static final Logger LOGGER = LoggerFactory.getLogger(ThriftUtil.class); + + public static void setImpersonator(final TProtocol in) { + try { + TTransport transport = in.getTransport(); + if (transport instanceof TSaslServerTransport) { + String impersonator = ((TSaslServerTransport) transport).getSaslServer() + .getAuthorizationID(); + setImpersonator(impersonator); + } + } catch (Exception e) { + // If there has exception when get impersonator info, log the error information. + LOGGER.warn("There is an error when get the impersonator:" + e.getMessage()); + } + } + + public static void setIpAddress(final TProtocol in) { + try { + TTransport transport = in.getTransport(); + TSocket tSocket = getUnderlyingSocketFromTransport(transport); + if (tSocket != null) { + setIpAddress(tSocket.getSocket().getInetAddress().toString()); + } else { + LOGGER.warn("Unknown Transport, cannot determine ipAddress"); + } + } catch (Exception e) { + // If there has exception when get impersonator info, log the error information. + LOGGER.warn("There is an error when get the client's ip address:" + e.getMessage()); + } + } + + /** + * Returns the underlying TSocket from the transport, or null of the transport type is unknown. + */ + private static TSocket getUnderlyingSocketFromTransport(TTransport transport) { + Preconditions.checkNotNull(transport); + if (transport instanceof TSaslServerTransport) { + return (TSocket) ((TSaslServerTransport) transport).getUnderlyingTransport(); + } else if (transport instanceof TSaslClientTransport) { + return (TSocket) ((TSaslClientTransport) transport).getUnderlyingTransport(); + } else if (transport instanceof TSocket) { + return (TSocket) transport; + } + return null; + } + + private static ThreadLocal<String> threadLocalIpAddress = new ThreadLocal<String>() { + @Override + protected synchronized String initialValue() { + return ""; + } + }; + + public static void setIpAddress(String ipAddress) { + threadLocalIpAddress.set(ipAddress); + } + + public static String getIpAddress() { + return threadLocalIpAddress.get(); + } + + private static ThreadLocal<String> threadLocalImpersonator = new ThreadLocal<String>() { + @Override + protected synchronized String initialValue() { + return ""; + } + }; + + public static void setImpersonator(String impersonator) { + threadLocalImpersonator.set(impersonator); + } + + public static String getImpersonator() { + return threadLocalImpersonator.get(); + } + + private ThriftUtil() { + // Make constructor private to avoid instantiation + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/sentry/blob/e72e6eac/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/tools/SentrySchemaHelper.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/tools/SentrySchemaHelper.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/tools/SentrySchemaHelper.java new file mode 100644 index 0000000..cf1c725 --- /dev/null +++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/tools/SentrySchemaHelper.java @@ -0,0 +1,315 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sentry.provider.db.tools; + +import java.util.IllegalFormatException; + +public final class SentrySchemaHelper { + public static final String DB_DERBY = "derby"; + public static final String DB_MYSQL = "mysql"; + public static final String DB_POSTGRACE = "postgres"; + public static final String DB_ORACLE = "oracle"; + public static final String DB_DB2 = "db2"; + + public interface NestedScriptParser { + + public enum CommandType { + PARTIAL_STATEMENT, + TERMINATED_STATEMENT, + COMMENT + } + + String DEFAUTL_DELIMITER = ";"; + /*** + * Find the type of given command + * @param dbCommand + * @return + */ + boolean isPartialCommand(String dbCommand) throws IllegalArgumentException; + + /** Parse the DB specific nesting format and extract the inner script name if any + * @param dbCommand command from parent script + * @return + * @throws IllegalFormatException + */ + String getScriptName(String dbCommand) throws IllegalArgumentException; + + /*** + * Find if the given command is a nested script execution + * @param dbCommand + * @return + */ + boolean isNestedScript(String dbCommand); + + /*** + * Find if the given command is should be passed to DB + * @param dbCommand + * @return + */ + boolean isNonExecCommand(String dbCommand); + + /*** + * Get the SQL statement delimiter + * @return + */ + String getDelimiter(); + + /*** + * Clear any client specific tags + * @return + */ + String cleanseCommand(String dbCommand); + + /*** + * Does the DB required table/column names quoted + * @return + */ + boolean needsQuotedIdentifier(); + + /*** + * Set DB specific options if any + * @param dbOps + */ + void setDbOpts(String dbOps); + } + + + /*** + * Base implemenation of NestedScriptParser + * abstractCommandParser. + * + */ + private static abstract class AbstractCommandParser implements NestedScriptParser { + private String dbOpts = null; + + @Override + public boolean isPartialCommand(String dbCommand) throws IllegalArgumentException{ + if (dbCommand == null || dbCommand.isEmpty()) { + throw new IllegalArgumentException("invalid command line " + dbCommand); + } + String trimmedDbCommand = dbCommand.trim(); + return !(trimmedDbCommand.endsWith(getDelimiter()) || isNonExecCommand(trimmedDbCommand)); + } + + @Override + public boolean isNonExecCommand(String dbCommand) { + return dbCommand.startsWith("--") || dbCommand.startsWith("#"); + } + + @Override + public String getDelimiter() { + return DEFAUTL_DELIMITER; + } + + @Override + public String cleanseCommand(String dbCommand) { + // strip off the delimiter + if (dbCommand.endsWith(getDelimiter())) { + dbCommand = dbCommand.substring(0, + dbCommand.length() - getDelimiter().length()); + } + return dbCommand; + } + + @Override + public boolean needsQuotedIdentifier() { + return false; + } + + @Override + public void setDbOpts(String dbOpts) { + this.dbOpts = dbOpts; + } + + protected String getDbOpts() { + return dbOpts; + } + } + + + // Derby commandline parser + public static class DerbyCommandParser extends AbstractCommandParser { + private static final String DERBY_NESTING_TOKEN = "RUN"; + + @Override + public String getScriptName(String dbCommand) throws IllegalArgumentException { + + if (!isNestedScript(dbCommand)) { + throw new IllegalArgumentException("Not a script format " + dbCommand); + } + String[] tokens = dbCommand.split(" "); + if (tokens.length != 2) { + throw new IllegalArgumentException("Couldn't parse line " + dbCommand); + } + return tokens[1].replace(";", "").replaceAll("'", ""); + } + + @Override + public boolean isNestedScript(String dbCommand) { + // Derby script format is RUN '<file>' + return dbCommand.startsWith(DERBY_NESTING_TOKEN); + } + } + + + // MySQL parser + public static class MySqlCommandParser extends AbstractCommandParser { + private static final String MYSQL_NESTING_TOKEN = "SOURCE"; + private static final String DELIMITER_TOKEN = "DELIMITER"; + private String delimiter = DEFAUTL_DELIMITER; + + @Override + public boolean isPartialCommand(String dbCommand) throws IllegalArgumentException{ + boolean isPartial = super.isPartialCommand(dbCommand); + // if this is a delimiter directive, reset our delimiter + if (dbCommand.startsWith(DELIMITER_TOKEN)) { + String[] tokens = dbCommand.split(" "); + if (tokens.length != 2) { + throw new IllegalArgumentException("Couldn't parse line " + dbCommand); + } + delimiter = tokens[1]; + } + return isPartial; + } + + @Override + public String getScriptName(String dbCommand) throws IllegalArgumentException { + String[] tokens = dbCommand.split(" "); + if (tokens.length != 2) { + throw new IllegalArgumentException("Couldn't parse line " + dbCommand); + } + // remove ending ';' + return tokens[1].replace(";", ""); + } + + @Override + public boolean isNestedScript(String dbCommand) { + return dbCommand.startsWith(MYSQL_NESTING_TOKEN); + } + + @Override + public String getDelimiter() { + return delimiter; + } + + @Override + public boolean isNonExecCommand(String dbCommand) { + return super.isNonExecCommand(dbCommand) || + dbCommand.startsWith("/*") && dbCommand.endsWith("*/") || + dbCommand.startsWith(DELIMITER_TOKEN); + } + + @Override + public String cleanseCommand(String dbCommand) { + return super.cleanseCommand(dbCommand).replaceAll("/\\*.*?\\*/[^;]", ""); + } + + } + + // Postgres specific parser + public static class PostgresCommandParser extends AbstractCommandParser { + public static final String POSTGRES_STRING_COMMAND_FILTER = "SET standard_conforming_strings"; + public static final String POSTGRES_STRING_CLIENT_ENCODING = "SET client_encoding"; + public static final String POSTGRES_SKIP_STANDARD_STRING = "postgres.filter.81"; + private static final String POSTGRES_NESTING_TOKEN = "\\i"; + + @Override + public String getScriptName(String dbCommand) throws IllegalArgumentException { + String[] tokens = dbCommand.split(" "); + if (tokens.length != 2) { + throw new IllegalArgumentException("Couldn't parse line " + dbCommand); + } + // remove ending ';' + return tokens[1].replace(";", ""); + } + + @Override + public boolean isNestedScript(String dbCommand) { + return dbCommand.startsWith(POSTGRES_NESTING_TOKEN); + } + + @Override + public boolean needsQuotedIdentifier() { + return true; + } + + @Override + public boolean isNonExecCommand(String dbCommand) { + // Skip "standard_conforming_strings" command which is not supported in older postgres + if (POSTGRES_SKIP_STANDARD_STRING.equalsIgnoreCase(getDbOpts()) + && (dbCommand.startsWith(POSTGRES_STRING_COMMAND_FILTER) || dbCommand.startsWith(POSTGRES_STRING_CLIENT_ENCODING))) { + return true; + } + return super.isNonExecCommand(dbCommand); + } + } + + //Oracle specific parser + public static class OracleCommandParser extends AbstractCommandParser { + private static final String ORACLE_NESTING_TOKEN = "@"; + @Override + public String getScriptName(String dbCommand) throws IllegalArgumentException { + if (!isNestedScript(dbCommand)) { + throw new IllegalArgumentException("Not a nested script format " + dbCommand); + } + // remove ending ';' and starting '@' + return dbCommand.replace(";", "").replace(ORACLE_NESTING_TOKEN, ""); + } + + @Override + public boolean isNestedScript(String dbCommand) { + return dbCommand.startsWith(ORACLE_NESTING_TOKEN); + } + } + + // DB2 commandline parser + public static class DB2CommandParser extends AbstractCommandParser { + + @Override + public String getScriptName(String dbCommand) throws IllegalArgumentException { + //DB2 does not support nesting script + throw new IllegalArgumentException("DB2 does not support nesting script " + dbCommand); + } + + @Override + public boolean isNestedScript(String dbCommand) { + //DB2 does not support nesting script + return false; + } + } + + public static NestedScriptParser getDbCommandParser(String dbName) { + if (dbName.equalsIgnoreCase(DB_DERBY)) { + return new DerbyCommandParser(); + } else if (dbName.equalsIgnoreCase(DB_MYSQL)) { + return new MySqlCommandParser(); + } else if (dbName.equalsIgnoreCase(DB_POSTGRACE)) { + return new PostgresCommandParser(); + } else if (dbName.equalsIgnoreCase(DB_ORACLE)) { + return new OracleCommandParser(); + } else if (dbName.equalsIgnoreCase(DB_DB2)) { + return new DB2CommandParser(); + } else { + throw new IllegalArgumentException("Unknown dbType " + dbName); + } + } + + private SentrySchemaHelper() { + // Make constructor private to avoid instantiation + } +}
