org.apache.sentry.binding.hive.authz.HiveAuthzBindingHookBase.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sentry.binding.hive.authz.HiveAuthzBindingHookBase.java

Source

/*
 * 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.binding.hive.authz;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.HashSet;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hive.common.JavaUtils;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
import org.apache.hadoop.hive.metastore.api.FieldSchema;
import org.apache.hadoop.hive.ql.exec.FunctionRegistry;
import org.apache.hadoop.hive.ql.exec.Task;
import org.apache.hadoop.hive.ql.exec.Utilities;
import org.apache.hadoop.hive.ql.hooks.Entity;
import org.apache.hadoop.hive.ql.hooks.Entity.Type;
import org.apache.hadoop.hive.ql.hooks.Hook;
import org.apache.hadoop.hive.ql.hooks.ReadEntity;
import org.apache.hadoop.hive.ql.hooks.WriteEntity;
import org.apache.hadoop.hive.ql.metadata.AuthorizationException;
import org.apache.hadoop.hive.ql.parse.ASTNode;
import org.apache.hadoop.hive.ql.parse.AbstractSemanticAnalyzerHook;
import org.apache.hadoop.hive.ql.parse.BaseSemanticAnalyzer;
import org.apache.hadoop.hive.ql.parse.HiveParser;
import org.apache.hadoop.hive.ql.parse.HiveSemanticAnalyzerHookContext;
import org.apache.hadoop.hive.ql.parse.SemanticException;
import org.apache.hadoop.hive.ql.plan.HiveOperation;
import org.apache.hadoop.hive.ql.plan.PlanUtils;
import org.apache.hadoop.hive.ql.session.SessionState;
import org.apache.sentry.binding.hive.SentryOnFailureHook;
import org.apache.sentry.binding.hive.SentryOnFailureHookContext;
import org.apache.sentry.binding.hive.SentryOnFailureHookContextImpl;
import org.apache.sentry.binding.hive.authz.HiveAuthzPrivileges.HiveOperationScope;
import org.apache.sentry.binding.hive.authz.HiveAuthzPrivileges.HiveOperationType;
import org.apache.sentry.binding.hive.conf.HiveAuthzConf;
import org.apache.sentry.core.common.Subject;
import org.apache.sentry.core.common.exception.SentryGroupNotFoundException;
import org.apache.sentry.core.common.utils.PathUtils;
import org.apache.sentry.core.model.db.AccessURI;
import org.apache.sentry.core.model.db.Column;
import org.apache.sentry.core.model.db.DBModelAction;
import org.apache.sentry.core.model.db.DBModelAuthorizable;
import org.apache.sentry.core.model.db.DBModelAuthorizable.AuthorizableType;
import org.apache.sentry.core.model.db.Database;
import org.apache.sentry.core.model.db.Table;
import org.apache.sentry.provider.cache.PrivilegeCache;
import org.apache.sentry.provider.cache.SimplePrivilegeCache;
import org.apache.sentry.provider.common.AuthorizationProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

import static org.apache.hadoop.hive.metastore.MetaStoreUtils.DEFAULT_DATABASE_NAME;

public abstract class HiveAuthzBindingHookBase extends AbstractSemanticAnalyzerHook {
    private static final Logger LOG = LoggerFactory.getLogger(HiveAuthzBindingHookBase.class);
    protected final HiveAuthzBinding hiveAuthzBinding;
    protected final HiveAuthzConf authzConf;
    protected Database currDB = Database.ALL;
    protected Table currTab;
    protected List<AccessURI> udfURIs;
    protected AccessURI serdeURI;
    protected AccessURI partitionURI;
    protected AccessURI indexURI;
    protected Table currOutTab = null;
    protected Database currOutDB = null;
    protected final List<String> serdeWhiteList;
    protected boolean serdeURIPrivilegesEnabled;

    protected final static HiveAuthzPrivileges columnMetaDataPrivilege = new HiveAuthzPrivileges.AuthzPrivilegeBuilder()
            .addInputObjectPriviledge(AuthorizableType.Column,
                    EnumSet.of(DBModelAction.SELECT, DBModelAction.INSERT, DBModelAction.ALTER))
            .setOperationScope(HiveOperationScope.COLUMN).setOperationType(HiveOperationType.INFO).build();

    // True if this is a basic DESCRIBE <table> operation. False for other DESCRIBE variants
    // like DESCRIBE [FORMATTED|EXTENDED]. Required because Hive treats these stmts as the same
    // HiveOperationType, but we want to enforces different privileges on each statement.
    // Basic DESCRIBE <table> is allowed with only column-level privs, while the variants
    // require table-level privileges.
    protected boolean isDescTableBasic = false;

    // Flag that specifies if the operation to validate is an ALTER VIEW AS SELECT.
    // Note: Hive sends CREATEVIEW even if ALTER VIEW AS SELECT is used.
    protected boolean isAlterViewAs = false;

    public HiveAuthzBindingHookBase() throws Exception {
        SessionState session = SessionState.get();
        if (session == null) {
            throw new IllegalStateException("Session has not been started");
        }
        // HACK: set a random classname to force the Auth V2 in Hive
        SessionState.get().setAuthorizer(null);

        HiveConf hiveConf = session.getConf();
        if (hiveConf == null) {
            throw new IllegalStateException("Session HiveConf is null");
        }
        authzConf = loadAuthzConf(hiveConf);
        udfURIs = Lists.newArrayList();
        hiveAuthzBinding = new HiveAuthzBinding(hiveConf, authzConf);
        String serdeWhiteLists = authzConf.get(HiveAuthzConf.HIVE_SENTRY_SERDE_WHITELIST,
                HiveAuthzConf.HIVE_SENTRY_SERDE_WHITELIST_DEFAULT);
        serdeWhiteList = Arrays.asList(serdeWhiteLists.split(","));
        serdeURIPrivilegesEnabled = authzConf.getBoolean(HiveAuthzConf.HIVE_SENTRY_SERDE_URI_PRIVILIEGES_ENABLED,
                HiveAuthzConf.HIVE_SENTRY_SERDE_URI_PRIVILIEGES_ENABLED_DEFAULT);

        FunctionRegistry.setupPermissionsForBuiltinUDFs("", HiveAuthzConf.HIVE_UDF_BLACK_LIST);
    }

    public static HiveAuthzConf loadAuthzConf(HiveConf hiveConf) {
        boolean depreicatedConfigFile = false;
        HiveAuthzConf newAuthzConf = null;
        String hiveAuthzConf = hiveConf.get(HiveAuthzConf.HIVE_SENTRY_CONF_URL);
        if (hiveAuthzConf == null || (hiveAuthzConf = hiveAuthzConf.trim()).isEmpty()) {
            hiveAuthzConf = hiveConf.get(HiveAuthzConf.HIVE_ACCESS_CONF_URL);
            depreicatedConfigFile = true;
        }

        if (hiveAuthzConf == null || (hiveAuthzConf = hiveAuthzConf.trim()).isEmpty()) {
            throw new IllegalArgumentException("Configuration key " + HiveAuthzConf.HIVE_SENTRY_CONF_URL
                    + " value '" + hiveAuthzConf + "' is invalid.");
        }
        try {
            newAuthzConf = new HiveAuthzConf(new URL(hiveAuthzConf));
        } catch (MalformedURLException e) {
            if (depreicatedConfigFile) {
                throw new IllegalArgumentException("Configuration key " + HiveAuthzConf.HIVE_ACCESS_CONF_URL
                        + " specifies a malformed URL '" + hiveAuthzConf + "'", e);
            } else {
                throw new IllegalArgumentException("Configuration key " + HiveAuthzConf.HIVE_SENTRY_CONF_URL
                        + " specifies a malformed URL '" + hiveAuthzConf + "'", e);
            }
        }
        return newAuthzConf;
    }

    @Override
    public abstract ASTNode preAnalyze(HiveSemanticAnalyzerHookContext context, ASTNode ast)
            throws SemanticException;

    /**
     * Post analyze hook that invokes hive auth bindings
     */
    @Override
    public abstract void postAnalyze(HiveSemanticAnalyzerHookContext context,
            List<Task<? extends Serializable>> rootTasks) throws SemanticException;

    protected void executeOnFailureHooks(HiveSemanticAnalyzerHookContext context, HiveOperation hiveOp,
            AuthorizationException e) {
        SentryOnFailureHookContext hookCtx = new SentryOnFailureHookContextImpl(context.getCommand(),
                context.getInputs(), context.getOutputs(), hiveOp, currDB, currTab, udfURIs, null,
                context.getUserName(), context.getIpAddress(), e, context.getConf());
        String csHooks = authzConf.get(HiveAuthzConf.AuthzConfVars.AUTHZ_ONFAILURE_HOOKS.getVar(), "").trim();

        try {
            for (Hook aofh : getHooks(csHooks)) {
                ((SentryOnFailureHook) aofh).run(hookCtx);
            }
        } catch (Exception ex) {
            LOG.error("Error executing hook:", ex);
        }
    }

    /**
     * The command 'create function ... using jar <jar resources>' can create a function
     * with the supplied jar resources in the command, which is translated into ASTNode being
     * [functionName functionClass resourceList] and resourceList being [resourceType resourcePath].
     * This function collects all the jar paths for the supplied jar resources.
     *
     * @param ast the AST node for the command
     * @return    the jar path list if any or an empty list
     */
    protected List<String> getFunctionJars(ASTNode ast) {
        ASTNode resourcesNode = (ASTNode) ast.getFirstChildWithType(HiveParser.TOK_RESOURCE_LIST);

        List<String> resources = new ArrayList<String>();
        if (resourcesNode != null) {
            for (int idx = 0; idx < resourcesNode.getChildCount(); ++idx) {
                ASTNode resNode = (ASTNode) resourcesNode.getChild(idx);
                ASTNode resTypeNode = (ASTNode) resNode.getChild(0);
                ASTNode resUriNode = (ASTNode) resNode.getChild(1);
                if (resTypeNode.getType() == HiveParser.TOK_JAR) {
                    resources.add(PlanUtils.stripQuotes(resUriNode.getText()));
                }
            }
        }

        return resources;
    }

    @VisibleForTesting
    protected static AccessURI extractPartition(ASTNode ast) throws SemanticException {
        for (int i = 0; i < ast.getChildCount(); i++) {
            ASTNode child = (ASTNode) ast.getChild(i);
            if (child.getToken().getType() == HiveParser.TOK_PARTITIONLOCATION && child.getChildCount() == 1) {
                return parseURI(BaseSemanticAnalyzer.unescapeSQLString(child.getChild(0).getText()));
            }
        }
        return null;
    }

    @VisibleForTesting
    static public AccessURI parseURI(String uri) throws SemanticException {
        return parseURI(uri, false);
    }

    @VisibleForTesting
    static public AccessURI parseURI(String uri, boolean isLocal) throws SemanticException {
        try {
            HiveConf conf = SessionState.get().getConf();
            String warehouseDir = conf.getVar(ConfVars.METASTOREWAREHOUSE);
            Path warehousePath = new Path(warehouseDir);

            // If warehousePath is an absolute path and a scheme is null and authority is null as well,
            // qualified it with default file system scheme and authority.
            if (warehousePath.isAbsoluteAndSchemeAuthorityNull()) {
                URI defaultUri = FileSystem.getDefaultUri(conf);
                warehousePath = warehousePath.makeQualified(defaultUri, warehousePath);
                warehouseDir = warehousePath.toUri().toString();
            }
            return new AccessURI(PathUtils.parseURI(warehouseDir, uri, isLocal));
        } catch (Exception e) {
            throw new SemanticException("Error parsing URI " + uri + ": " + e.getMessage(), e);
        }
    }

    // Find the current database for session
    protected Database getCanonicalDb() {
        return new Database(SessionState.get().getCurrentDatabase());
    }

    protected void extractDbTableNameFromTOKTABLE(ASTNode astNode) throws SemanticException {
        String[] fqTableName = BaseSemanticAnalyzer.getQualifiedTableName(astNode);
        Preconditions.checkArgument(fqTableName.length == 2,
                "BaseSemanticAnalyzer.getQualifiedTableName should return " + "an array with dbName and tableName");
        currOutDB = new Database(fqTableName[0]);
        currOutTab = new Table(fqTableName[1]);
    }

    /*TODO: Deprecate */
    protected Database extractDatabase(ASTNode ast) throws SemanticException {
        String tableName = BaseSemanticAnalyzer.getUnescapedName(ast);
        if (tableName.contains(".")) {
            return new Database(tableName.split("\\.")[0]);
        } else {
            return getCanonicalDb();
        }
    }

    /*TODO: Deprecate */
    protected Table extractTable(ASTNode ast) throws SemanticException {
        String tableName = BaseSemanticAnalyzer.getUnescapedName(ast);
        if (tableName.contains(".")) {
            return new Table(tableName.split("\\.")[1]);
        } else {
            return new Table(tableName);
        }
    }

    protected static AccessURI extractTableLocation(ASTNode ast) throws SemanticException {
        ASTNode locationChild = (ASTNode) ast.getFirstChildWithType(HiveParser.TOK_TABLELOCATION);
        if (locationChild == null) {
            LOG.debug("Token HiveParser.TOK_TABLELOCATION not found in ast. "
                    + "This means command does not have a location clause");
            return null;
        }

        if (locationChild.getChildCount() != 1) {
            LOG.error("Found Token HiveParser.TOK_TABLELOCATION, but was expecting the URI as its only "
                    + "child. This means it is possible that permissions on the URI are not checked for this "
                    + "command ");
            return null;
        }

        return parseURI(BaseSemanticAnalyzer.unescapeSQLString(locationChild.getChild(0).getText()));
    }

    public static void runFailureHook(SentryOnFailureHookContext hookContext, String csHooks) {
        try {
            for (Hook aofh : getHooks(csHooks)) {
                ((SentryOnFailureHook) aofh).run(hookContext);
            }
        } catch (Exception ex) {
            LOG.error("Error executing hook:", ex);
        }
    }

    /**
     * Convert the input/output entities into authorizables. generate
     * authorizables for cases like Database and metadata operations where the
     * compiler doesn't capture entities. invoke the hive binding to validate
     * permissions
     *
     * @param context
     * @param stmtAuthObject
     * @param stmtOperation
     * @throws AuthorizationException
     */
    protected void authorizeWithHiveBindings(HiveSemanticAnalyzerHookContext context,
            HiveAuthzPrivileges stmtAuthObject, HiveOperation stmtOperation) throws AuthorizationException {
        Set<ReadEntity> inputs = context.getInputs();
        Set<WriteEntity> outputs = context.getOutputs();
        Set<List<DBModelAuthorizable>> inputHierarchy = new HashSet<List<DBModelAuthorizable>>();
        Set<List<DBModelAuthorizable>> outputHierarchy = new HashSet<List<DBModelAuthorizable>>();

        if (LOG.isDebugEnabled()) {
            LOG.debug("context.getInputs() = " + context.getInputs());
            LOG.debug("context.getOutputs() = " + context.getOutputs());
        }

        // Workaround to allow DESCRIBE <table> to be executed with only column-level privileges, while
        // still authorizing DESCRIBE [EXTENDED|FORMATTED] as table-level.
        // This is done by treating DESCRIBE <table> the same as SHOW COLUMNS, which only requires column
        // level privs.
        if (isDescTableBasic) {
            stmtAuthObject = columnMetaDataPrivilege;
        }

        switch (stmtAuthObject.getOperationScope()) {

        case SERVER:
            // validate server level privileges if applicable. Eg create UDF,register jar etc ..
            List<DBModelAuthorizable> serverHierarchy = new ArrayList<DBModelAuthorizable>();
            serverHierarchy.add(hiveAuthzBinding.getAuthServer());
            inputHierarchy.add(serverHierarchy);
            break;
        case DATABASE:
            // workaround for database scope statements (create/alter/drop db)
            List<DBModelAuthorizable> dbHierarchy = new ArrayList<DBModelAuthorizable>();
            dbHierarchy.add(hiveAuthzBinding.getAuthServer());
            dbHierarchy.add(currDB);
            inputHierarchy.add(dbHierarchy);

            if (currOutDB != null) {
                List<DBModelAuthorizable> outputDbHierarchy = new ArrayList<DBModelAuthorizable>();
                outputDbHierarchy.add(hiveAuthzBinding.getAuthServer());
                outputDbHierarchy.add(currOutDB);
                outputHierarchy.add(outputDbHierarchy);
            } else {
                outputHierarchy.add(dbHierarchy);
            }

            getInputHierarchyFromInputs(inputHierarchy, inputs);

            if (serdeURI != null) {
                List<DBModelAuthorizable> serdeUriHierarchy = new ArrayList<DBModelAuthorizable>();
                serdeUriHierarchy.add(hiveAuthzBinding.getAuthServer());
                serdeUriHierarchy.add(serdeURI);
                outputHierarchy.add(serdeUriHierarchy);
            }
            break;
        case TABLE:
            // workaround for add partitions
            if (partitionURI != null) {
                inputHierarchy.add(ImmutableList.of(hiveAuthzBinding.getAuthServer(), partitionURI));
            }

            if (indexURI != null) {
                outputHierarchy.add(ImmutableList.of(hiveAuthzBinding.getAuthServer(), indexURI));
            }

            getInputHierarchyFromInputs(inputHierarchy, inputs);
            for (WriteEntity writeEntity : outputs) {
                if (filterWriteEntity(writeEntity)) {
                    continue;
                }
                List<DBModelAuthorizable> entityHierarchy = new ArrayList<DBModelAuthorizable>();
                entityHierarchy.add(hiveAuthzBinding.getAuthServer());
                entityHierarchy.addAll(getAuthzHierarchyFromEntity(writeEntity));
                outputHierarchy.add(entityHierarchy);
            }
            // workaround for metadata queries.
            // Capture the table name in pre-analyze and include that in the input entity list.
            // The exception is for ALTERVIEW_AS operations which puts the table to read as input and
            // the view as output. Having the view as input again will case extra privileges to be given.
            if (currTab != null && stmtOperation != HiveOperation.ALTERVIEW_AS) {
                List<DBModelAuthorizable> externalAuthorizableHierarchy = new ArrayList<DBModelAuthorizable>();
                externalAuthorizableHierarchy.add(hiveAuthzBinding.getAuthServer());
                externalAuthorizableHierarchy.add(currDB);
                externalAuthorizableHierarchy.add(currTab);
                inputHierarchy.add(externalAuthorizableHierarchy);
            }

            // workaround for DDL statements
            // Capture the table name in pre-analyze and include that in the output entity list
            if (currOutTab != null) {
                List<DBModelAuthorizable> externalAuthorizableHierarchy = new ArrayList<DBModelAuthorizable>();
                externalAuthorizableHierarchy.add(hiveAuthzBinding.getAuthServer());
                externalAuthorizableHierarchy.add(currOutDB);
                externalAuthorizableHierarchy.add(currOutTab);
                outputHierarchy.add(externalAuthorizableHierarchy);
            }

            if (serdeURI != null) {
                List<DBModelAuthorizable> serdeUriHierarchy = new ArrayList<DBModelAuthorizable>();
                serdeUriHierarchy.add(hiveAuthzBinding.getAuthServer());
                serdeUriHierarchy.add(serdeURI);
                outputHierarchy.add(serdeUriHierarchy);
            }

            break;
        case FUNCTION:
            /* The 'FUNCTION' privilege scope currently used for
             *  - CREATE TEMP FUNCTION
             *  - DROP TEMP FUNCTION.
             */
            if (!udfURIs.isEmpty()) {
                List<DBModelAuthorizable> udfUriHierarchy = new ArrayList<DBModelAuthorizable>();
                udfUriHierarchy.add(hiveAuthzBinding.getAuthServer());
                udfUriHierarchy.addAll(udfURIs);
                inputHierarchy.add(udfUriHierarchy);
                for (WriteEntity writeEntity : outputs) {
                    List<DBModelAuthorizable> entityHierarchy = new ArrayList<DBModelAuthorizable>();
                    entityHierarchy.add(hiveAuthzBinding.getAuthServer());
                    entityHierarchy.addAll(getAuthzHierarchyFromEntity(writeEntity));
                    outputHierarchy.add(entityHierarchy);
                }
            } else if (currDB != Database.ALL) { //This will be true with DROP FUNCTION commands
                List<DBModelAuthorizable> entityHierarchy = new ArrayList<DBModelAuthorizable>();
                entityHierarchy.add(hiveAuthzBinding.getAuthServer());
                entityHierarchy.add(currDB);
                inputHierarchy.add(entityHierarchy);
            }
            break;
        case CONNECT:
            /* The 'CONNECT' is an implicit privilege scope currently used for
             *  - USE <db>
             *  It's allowed when the user has any privilege on the current database. For application
             *  backward compatibility, we allow (optional) implicit connect permission on 'default' db.
             */
            List<DBModelAuthorizable> connectHierarchy = new ArrayList<DBModelAuthorizable>();
            connectHierarchy.add(hiveAuthzBinding.getAuthServer());
            // by default allow connect access to default db
            Table currTbl = Table.ALL;
            Column currCol = Column.ALL;
            if (DEFAULT_DATABASE_NAME.equalsIgnoreCase(currDB.getName()) && "false".equalsIgnoreCase(
                    authzConf.get(HiveAuthzConf.AuthzConfVars.AUTHZ_RESTRICT_DEFAULT_DB.getVar(), "false"))) {
                currDB = Database.ALL;
                currTbl = Table.SOME;
            }

            connectHierarchy.add(currDB);
            connectHierarchy.add(currTbl);
            connectHierarchy.add(currCol);

            inputHierarchy.add(connectHierarchy);
            outputHierarchy.add(connectHierarchy);
            break;
        case COLUMN:
            for (ReadEntity readEntity : inputs) {
                if (readEntity.getAccessedColumns() != null && !readEntity.getAccessedColumns().isEmpty()) {
                    addColumnHierarchy(inputHierarchy, readEntity);
                } else {
                    List<DBModelAuthorizable> entityHierarchy = new ArrayList<DBModelAuthorizable>();
                    entityHierarchy.add(hiveAuthzBinding.getAuthServer());
                    entityHierarchy.addAll(getAuthzHierarchyFromEntity(readEntity));
                    entityHierarchy.add(Column.ALL);
                    inputHierarchy.add(entityHierarchy);
                }
            }
            break;
        default:
            throw new AuthorizationException(
                    "Unknown operation scope type " + stmtAuthObject.getOperationScope().toString());
        }

        HiveAuthzBinding binding = null;
        try {
            binding = getHiveBindingWithPrivilegeCache(hiveAuthzBinding, context.getUserName());
        } catch (SemanticException e) {
            // Will use the original hiveAuthzBinding
            binding = hiveAuthzBinding;
        }
        // validate permission
        binding.authorize(stmtOperation, stmtAuthObject, getCurrentSubject(context), inputHierarchy,
                outputHierarchy);
    }

    // Build the hierarchy of authorizable object for the given entity type.
    private List<DBModelAuthorizable> getAuthzHierarchyFromEntity(Entity entity) {
        List<DBModelAuthorizable> objectHierarchy = new ArrayList<DBModelAuthorizable>();
        switch (entity.getType()) {
        case TABLE:
            objectHierarchy.add(new Database(entity.getTable().getDbName()));
            objectHierarchy.add(new Table(entity.getTable().getTableName()));
            break;
        case PARTITION:
        case DUMMYPARTITION:
            objectHierarchy.add(new Database(entity.getPartition().getTable().getDbName()));
            objectHierarchy.add(new Table(entity.getPartition().getTable().getTableName()));
            break;
        case DFS_DIR:
        case LOCAL_DIR:
            try {
                objectHierarchy.add(parseURI(entity.toString(), entity.getType().equals(Type.LOCAL_DIR)));
            } catch (Exception e) {
                throw new AuthorizationException("Failed to get File URI", e);
            }
            break;
        case DATABASE:
        case FUNCTION:
            // TODO use database entities from compiler instead of capturing from AST
            break;
        default:
            throw new UnsupportedOperationException("Unsupported entity type " + entity.getType().name());
        }
        return objectHierarchy;
    }

    /**
     * Add column level hierarchy to inputHierarchy
     *
     * @param inputHierarchy
     * @param entity
     * @param sentryContext
     */
    protected void addColumnHierarchy(Set<List<DBModelAuthorizable>> inputHierarchy, ReadEntity entity) {
        List<DBModelAuthorizable> entityHierarchy = new ArrayList<DBModelAuthorizable>();
        entityHierarchy.add(hiveAuthzBinding.getAuthServer());
        entityHierarchy.addAll(getAuthzHierarchyFromEntity(entity));

        switch (entity.getType()) {
        case TABLE:
        case PARTITION:
            List<String> cols = entity.getAccessedColumns();
            for (String col : cols) {
                List<DBModelAuthorizable> colHierarchy = new ArrayList<DBModelAuthorizable>(entityHierarchy);
                colHierarchy.add(new Column(col));
                inputHierarchy.add(colHierarchy);
            }
            break;
        default:
            inputHierarchy.add(entityHierarchy);
        }
    }

    /**
     * Get Authorizable from inputs and put into inputHierarchy
     *
     * @param inputHierarchy
     * @param entity
     * @param sentryContext
     */
    public void getInputHierarchyFromInputs(Set<List<DBModelAuthorizable>> inputHierarchy, Set<ReadEntity> inputs) {
        for (ReadEntity readEntity : inputs) {
            // skip the tables/view that are part of expanded view definition
            // skip the Hive generated dummy entities created for queries like 'select <expr>'
            if (isChildTabForView(readEntity) || isDummyEntity(readEntity)) {
                continue;
            }
            if (readEntity.getAccessedColumns() != null && !readEntity.getAccessedColumns().isEmpty()) {
                addColumnHierarchy(inputHierarchy, readEntity);
            } else {
                List<DBModelAuthorizable> entityHierarchy = new ArrayList<DBModelAuthorizable>();
                entityHierarchy.add(hiveAuthzBinding.getAuthServer());
                entityHierarchy.addAll(getAuthzHierarchyFromEntity(readEntity));
                inputHierarchy.add(entityHierarchy);
            }
        }
    }

    // Check if this write entity needs to skipped
    private boolean filterWriteEntity(WriteEntity writeEntity) throws AuthorizationException {
        // skip URI validation for session scratch file URIs
        if (writeEntity.isTempURI()) {
            return true;
        }
        try {
            if (writeEntity.getTyp().equals(Type.DFS_DIR) || writeEntity.getTyp().equals(Type.LOCAL_DIR)) {
                HiveConf conf = SessionState.get().getConf();
                String warehouseDir = conf.getVar(ConfVars.METASTOREWAREHOUSE);
                URI scratchURI = new URI(PathUtils.parseDFSURI(warehouseDir, conf.getVar(ConfVars.SCRATCHDIR)));
                URI requestURI = new URI(PathUtils.parseDFSURI(warehouseDir, writeEntity.getLocation().getPath()));
                LOG.debug("scratchURI = " + scratchURI + ", requestURI = " + requestURI);
                if (PathUtils.impliesURI(scratchURI, requestURI)) {
                    return true;
                }
                URI localScratchURI = new URI(PathUtils.parseLocalURI(conf.getVar(ConfVars.LOCALSCRATCHDIR)));
                URI localRequestURI = new URI(PathUtils.parseLocalURI(writeEntity.getLocation().getPath()));
                LOG.debug("localScratchURI = " + localScratchURI + ", localRequestURI = " + localRequestURI);
                if (PathUtils.impliesURI(localScratchURI, localRequestURI)) {
                    return true;
                }
            }
        } catch (Exception e) {
            throw new AuthorizationException("Failed to extract uri details", e);
        }
        return false;
    }

    public static List<String> filterShowTables(HiveAuthzBinding hiveAuthzBinding, List<String> queryResult,
            HiveOperation operation, String userName, String dbName) throws SemanticException {
        List<String> filteredResult = new ArrayList<String>();
        Subject subject = new Subject(userName);
        HiveAuthzPrivileges tableMetaDataPrivilege = new HiveAuthzPrivileges.AuthzPrivilegeBuilder()
                .addInputObjectPriviledge(AuthorizableType.Column,
                        EnumSet.of(DBModelAction.SELECT, DBModelAction.INSERT))
                .setOperationScope(HiveOperationScope.TABLE).setOperationType(HiveOperationType.INFO).build();

        HiveAuthzBinding hiveBindingWithPrivilegeCache = getHiveBindingWithPrivilegeCache(hiveAuthzBinding,
                userName);

        for (String tableName : queryResult) {
            // if user has privileges on table, add to filtered list, else discard
            Table table = new Table(tableName);
            Database database;
            database = new Database(dbName);

            Set<List<DBModelAuthorizable>> inputHierarchy = new HashSet<List<DBModelAuthorizable>>();
            Set<List<DBModelAuthorizable>> outputHierarchy = new HashSet<List<DBModelAuthorizable>>();
            List<DBModelAuthorizable> externalAuthorizableHierarchy = new ArrayList<DBModelAuthorizable>();
            externalAuthorizableHierarchy.add(hiveAuthzBinding.getAuthServer());
            externalAuthorizableHierarchy.add(database);
            externalAuthorizableHierarchy.add(table);
            externalAuthorizableHierarchy.add(Column.ALL);
            inputHierarchy.add(externalAuthorizableHierarchy);

            try {
                // do the authorization by new HiveAuthzBinding with PrivilegeCache
                hiveBindingWithPrivilegeCache.authorize(operation, tableMetaDataPrivilege, subject, inputHierarchy,
                        outputHierarchy);
                filteredResult.add(table.getName());
            } catch (AuthorizationException e) {
                // squash the exception, user doesn't have privileges, so the table is
                // not added to
                // filtered list.
            }
        }
        return filteredResult;
    }

    public static List<FieldSchema> filterShowColumns(HiveAuthzBinding hiveAuthzBinding, List<FieldSchema> cols,
            HiveOperation operation, String userName, String tableName, String dbName) throws SemanticException {
        List<FieldSchema> filteredResult = new ArrayList<FieldSchema>();
        Subject subject = new Subject(userName);
        HiveAuthzBinding hiveBindingWithPrivilegeCache = getHiveBindingWithPrivilegeCache(hiveAuthzBinding,
                userName);

        Database database = new Database(dbName);
        Table table = new Table(tableName);
        for (FieldSchema col : cols) {
            // if user has privileges on column, add to filtered list, else discard
            Set<List<DBModelAuthorizable>> inputHierarchy = new HashSet<List<DBModelAuthorizable>>();
            Set<List<DBModelAuthorizable>> outputHierarchy = new HashSet<List<DBModelAuthorizable>>();
            List<DBModelAuthorizable> externalAuthorizableHierarchy = new ArrayList<DBModelAuthorizable>();
            externalAuthorizableHierarchy.add(hiveAuthzBinding.getAuthServer());
            externalAuthorizableHierarchy.add(database);
            externalAuthorizableHierarchy.add(table);
            externalAuthorizableHierarchy.add(new Column(col.getName()));
            inputHierarchy.add(externalAuthorizableHierarchy);

            try {
                // do the authorization by new HiveAuthzBinding with PrivilegeCache
                hiveBindingWithPrivilegeCache.authorize(operation, columnMetaDataPrivilege, subject, inputHierarchy,
                        outputHierarchy);
                filteredResult.add(col);
            } catch (AuthorizationException e) {
                // squash the exception, user doesn't have privileges, so the column is
                // not added to
                // filtered list.
            }
        }
        return filteredResult;
    }

    public static List<String> filterShowDatabases(HiveAuthzBinding hiveAuthzBinding, List<String> queryResult,
            HiveOperation operation, String userName) throws SemanticException {
        List<String> filteredResult = new ArrayList<String>();
        Subject subject = new Subject(userName);
        HiveAuthzBinding hiveBindingWithPrivilegeCache = getHiveBindingWithPrivilegeCache(hiveAuthzBinding,
                userName);

        HiveAuthzPrivileges anyPrivilege = new HiveAuthzPrivileges.AuthzPrivilegeBuilder()
                .addInputObjectPriviledge(AuthorizableType.Column,
                        EnumSet.of(DBModelAction.SELECT, DBModelAction.INSERT))
                .addInputObjectPriviledge(AuthorizableType.URI, EnumSet.of(DBModelAction.SELECT))
                .setOperationScope(HiveOperationScope.CONNECT).setOperationType(HiveOperationType.QUERY).build();

        for (String dbName : queryResult) {
            // if user has privileges on database, add to filtered list, else discard
            Database database = null;

            // if default is not restricted, continue
            if (DEFAULT_DATABASE_NAME.equalsIgnoreCase(dbName) && "false".equalsIgnoreCase(hiveAuthzBinding
                    .getAuthzConf().get(HiveAuthzConf.AuthzConfVars.AUTHZ_RESTRICT_DEFAULT_DB.getVar(), "false"))) {
                filteredResult.add(DEFAULT_DATABASE_NAME);
                continue;
            }

            database = new Database(dbName);

            Set<List<DBModelAuthorizable>> inputHierarchy = new HashSet<List<DBModelAuthorizable>>();
            Set<List<DBModelAuthorizable>> outputHierarchy = new HashSet<List<DBModelAuthorizable>>();
            List<DBModelAuthorizable> externalAuthorizableHierarchy = new ArrayList<DBModelAuthorizable>();
            externalAuthorizableHierarchy.add(hiveAuthzBinding.getAuthServer());
            externalAuthorizableHierarchy.add(database);
            externalAuthorizableHierarchy.add(Table.ALL);
            externalAuthorizableHierarchy.add(Column.ALL);
            inputHierarchy.add(externalAuthorizableHierarchy);

            try {
                // do the authorization by new HiveAuthzBinding with PrivilegeCache
                hiveBindingWithPrivilegeCache.authorize(operation, anyPrivilege, subject, inputHierarchy,
                        outputHierarchy);
                filteredResult.add(database.getName());
            } catch (AuthorizationException e) {
                // squash the exception, user doesn't have privileges, so the table is
                // not added to
                // filtered list.
            }
        }

        return filteredResult;
    }

    /**
     * Check if the given read entity is a table that has parents of type Table
     * Hive compiler performs a query rewrite by replacing view with its definition. In the process, tt captures both
     * the original view and the tables/view that it selects from .
     * The access authorization is only interested in the top level views and not the underlying tables.
     * @param readEntity
     * @return
     */
    private boolean isChildTabForView(ReadEntity readEntity) {
        // If this is a table added for view, then we need to skip that
        if (!readEntity.getType().equals(Type.TABLE) && !readEntity.getType().equals(Type.PARTITION)) {
            return false;
        }
        if (readEntity.getParents() != null && readEntity.getParents().size() > 0) {
            for (ReadEntity parentEntity : readEntity.getParents()) {
                if (!parentEntity.getType().equals(Type.TABLE)) {
                    return false;
                }
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Returns the hooks specified in a configuration variable.  The hooks are returned in a list in
     * the order they were specified in the configuration variable.
     *
     * @param hookConfVar The configuration variable specifying a comma separated list of the hook
     *                    class names.
     * @return            A list of the hooks, in the order they are listed in the value of hookConfVar
     * @throws Exception
     */
    private static <T extends Hook> List<T> getHooks(String csHooks) throws Exception {

        List<T> hooks = new ArrayList<T>();
        if (csHooks.isEmpty()) {
            return hooks;
        }
        for (String hookClass : Splitter.on(",").omitEmptyStrings().trimResults().split(csHooks)) {
            try {
                @SuppressWarnings("unchecked")
                T hook = (T) Class.forName(hookClass, true, JavaUtils.getClassLoader()).newInstance();
                hooks.add(hook);
            } catch (ClassNotFoundException e) {
                LOG.error(hookClass + " Class not found:" + e.getMessage());
                throw e;
            }
        }

        return hooks;
    }

    // Check if the given entity is identified as dummy by Hive compilers.
    private boolean isDummyEntity(Entity entity) {
        return entity.isDummy();
    }

    // create hiveBinding with PrivilegeCache
    static HiveAuthzBinding getHiveBindingWithPrivilegeCache(HiveAuthzBinding hiveAuthzBinding, String userName)
            throws SemanticException {
        // get the original HiveAuthzBinding, and get the user's privileges by AuthorizationProvider
        AuthorizationProvider authProvider = hiveAuthzBinding.getCurrentAuthProvider();
        try {
            Set<String> groups;
            try {
                groups = authProvider.getGroupMapping().getGroups(userName);
            } catch (SentryGroupNotFoundException e) {
                groups = Collections.emptySet();
                LOG.debug("Could not find groups for user: " + userName);
            }
            Set<String> userPrivileges = authProvider.getPolicyEngine().getPrivileges(groups,
                    Sets.newHashSet(userName), hiveAuthzBinding.getActiveRoleSet(),
                    hiveAuthzBinding.getAuthServer());

            // create PrivilegeCache using user's privileges
            PrivilegeCache privilegeCache = new SimplePrivilegeCache(userPrivileges);
            // create new instance of HiveAuthzBinding whose backend provider should be SimpleCacheProviderBackend
            return new HiveAuthzBinding(HiveAuthzBinding.HiveHook.HiveServer2, hiveAuthzBinding.getHiveConf(),
                    hiveAuthzBinding.getAuthzConf(), privilegeCache);
        } catch (Exception e) {
            LOG.error("Can not create HiveAuthzBinding with privilege cache.");
            throw new SemanticException(e);
        }
    }

    private static boolean hasPrefixMatch(List<String> prefixList, final String str) {
        for (String prefix : prefixList) {
            if (str.startsWith(prefix)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Set the Serde URI privileges. If the URI privileges are not set, which serdeURI will be null,
     * the URI authorization checks will be skipped.
     */
    protected void setSerdeURI(String serdeClassName) throws SemanticException {
        if (!serdeURIPrivilegesEnabled) {
            return;
        }

        // WhiteList Serde Jar can be used by any users. WhiteList checking is
        // done by comparing the Java package name. The assumption is cluster
        // admin will ensure there is no Java namespace collision.
        // e.g org.apache.hadoop.hive.serde2 is used by hive and cluster admin should
        // ensure no custom Serde class is introduced under the same namespace.
        if (!hasPrefixMatch(serdeWhiteList, serdeClassName)) {
            try {
                CodeSource serdeSrc = Class
                        .forName(serdeClassName, true, Utilities.getSessionSpecifiedClassLoader())
                        .getProtectionDomain().getCodeSource();
                if (serdeSrc == null) {
                    throw new SemanticException("Could not resolve the jar for Serde class " + serdeClassName);
                }

                String serdeJar = serdeSrc.getLocation().getPath();
                if (serdeJar == null || serdeJar.isEmpty()) {
                    throw new SemanticException(
                            "Could not find the jar for Serde class " + serdeClassName + "to validate privileges");
                }

                serdeURI = parseURI(serdeSrc.getLocation().toString(), true);
            } catch (ClassNotFoundException e) {
                throw new SemanticException("Error retrieving Serde class:" + e.getMessage(), e);
            }
        }
    }

    protected Subject getCurrentSubject(HiveSemanticAnalyzerHookContext context) {
        // Extract the username from the hook context
        return new Subject(context.getUserName());
    }

    /**
     * Returns true if the ASTNode tree is an ALTER VIEW AS SELECT operation.
     * <p>
     * The ASTNode with an ALTER VIEW AS SELECT is formed as follows:*
     *   Root: TOK_ALTERVIEW
     *     Child(0): TOK_TABNAME
     *     Child(1): TOK_QUERY    <-- This is the SELECT operation
     *
     * @param ast The ASTNode that Hive created while parsing the ALTER VIEW operation.
     * @return True if it is an ALTER VIEW AS SELECT operation; False otherwise.
     */
    protected boolean isAlterViewAsOperation(ASTNode ast) {
        if (ast == null || ast.getToken().getType() != HiveParser.TOK_ALTERVIEW) {
            return false;
        }

        if (ast.getChildCount() <= 1 || ast.getChild(1).getType() != HiveParser.TOK_QUERY) {
            return false;
        }

        return true;
    }

    @Override
    public String toString() {
        StringBuilder strb = new StringBuilder();
        strb.append("currDB=").append(currDB).append(", currTab=").append(currTab).append(", udfURIs=")
                .append(udfURIs).append(", serdeURI=").append(serdeURI).append(", partitionURI=")
                .append(partitionURI).append(", indexURI=").append(indexURI).append(", currOutDB=")
                .append(currOutDB).append(", currOutTab=").append(currOutTab).append(", serdeWhiteList=")
                .append(serdeWhiteList).append(", serdeURIPrivilegesEnabled=").append(serdeURIPrivilegesEnabled)
                .append(", isDescTableBasic=").append(isDescTableBasic);

        return strb.toString();
    }
}