org.apache.sentry.provider.file.SimpleFileProviderBackend.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sentry.provider.file.SimpleFileProviderBackend.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.provider.file;

import static org.apache.sentry.core.common.utils.SentryConstants.ROLE_SPLITTER;

import java.io.IOException;
import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.sentry.core.common.ActiveRoleSet;
import org.apache.sentry.core.common.Authorizable;
import org.apache.sentry.core.common.exception.SentryConfigurationException;
import org.apache.sentry.core.common.utils.PolicyFiles;
import org.apache.sentry.policy.common.PrivilegeUtils;
import org.apache.sentry.core.common.validator.PrivilegeValidator;
import org.apache.sentry.core.common.validator.PrivilegeValidatorContext;
import org.apache.sentry.core.common.utils.PolicyFileConstants;
import org.apache.sentry.provider.common.CacheProvider;
import org.apache.sentry.provider.common.ProviderBackend;
import org.apache.sentry.provider.common.ProviderBackendContext;
import org.apache.sentry.provider.common.TableCache;
import org.apache.shiro.config.Ini;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import com.google.common.collect.Table.Cell;

public class SimpleFileProviderBackend extends CacheProvider implements ProviderBackend {

    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleFileProviderBackend.class);

    private final FileSystem fileSystem;
    private final Path resourcePath;
    private final Configuration conf;
    private final List<String> configErrors;
    private final List<String> configWarnings;
    private TableCache cache;
    /**
     * Each group, role, and privilege in groupRolePrivilegeTable is
     * interned using a weak interner so that we only store each string
     * once.
     */
    private final Interner<String> stringInterner;

    private ImmutableList<PrivilegeValidator> validators;
    private boolean allowPerDatabaseSection;
    private volatile boolean initialized;

    public SimpleFileProviderBackend(Configuration conf, String resourcePath) throws IOException {
        this(conf, new Path(resourcePath));
    }

    public SimpleFileProviderBackend(Configuration conf, Path resourcePath) throws IOException {
        this.resourcePath = resourcePath;
        this.fileSystem = resourcePath.getFileSystem(conf);
        this.conf = conf;
        this.configErrors = Lists.newArrayList();
        this.configWarnings = Lists.newArrayList();
        this.validators = ImmutableList.of();
        this.allowPerDatabaseSection = true;
        this.initialized = false;
        this.stringInterner = Interners.newWeakInterner();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void initialize(ProviderBackendContext context) {
        if (initialized) {
            throw new IllegalStateException("Backend has already been initialized, cannot be initialized twice");
        }
        this.validators = context.getValidators();
        this.allowPerDatabaseSection = context.isAllowPerDatabase();
        final Table<String, String, Set<String>> table = parse();
        this.cache = new TableCache() {
            @Override
            public Table<String, String, Set<String>> getCache() {
                return table;
            }
        };
        super.initialize(cache);
        this.initialized = true;
    }

    @Override
    public ImmutableSet<String> getPrivileges(Set<String> groups, Set<String> users, ActiveRoleSet roleSet,
            Authorizable... authorizableHierarchy) {
        // SimpleFileProviderBackend doesn't support getPrivileges for user now.
        return getPrivileges(groups, roleSet, authorizableHierarchy);
    }

    @Override
    public void close() {
        // SENTRY-847 will use HiveAuthBinding again, so groupRolePrivilegeTable shouldn't clear itself
    }

    @Override
    public void validatePolicy(boolean strictValidation) throws SentryConfigurationException {
        if (!initialized) {
            throw new IllegalStateException("Backend has not been properly initialized");
        }
        List<String> localConfigErrors = Lists.newArrayList(configErrors);
        List<String> localConfigWarnings = Lists.newArrayList(configWarnings);
        if (strictValidation && !localConfigWarnings.isEmpty() || !localConfigErrors.isEmpty()) {
            localConfigErrors.add("Failed to process global policy file " + resourcePath);
            SentryConfigurationException e = new SentryConfigurationException("");
            e.setConfigErrors(localConfigErrors);
            e.setConfigWarnings(localConfigWarnings);
            throw e;
        }
    }

    private Table<String, String, Set<String>> parse() {
        configErrors.clear();
        configWarnings.clear();
        Table<String, String, Set<String>> groupRolePrivilegeTable = HashBasedTable.create();
        Table<String, String, Set<String>> groupRolePrivilegeTableTemp = HashBasedTable.create();
        Ini ini;
        LOGGER.info("Parsing " + resourcePath);
        LOGGER.info("Filesystem: " + fileSystem.getUri());
        try {
            try {
                ini = PolicyFiles.loadFromPath(fileSystem, resourcePath);
            } catch (IOException e) {
                configErrors.add("Failed to read policy file " + resourcePath + " Error: " + e.getMessage());
                throw new SentryConfigurationException("Error loading policy file " + resourcePath, e);
            } catch (IllegalArgumentException e) {
                configErrors.add("Failed to read policy file " + resourcePath + " Error: " + e.getMessage());
                throw new SentryConfigurationException("Error loading policy file " + resourcePath, e);
            }

            if (LOGGER.isDebugEnabled()) {
                for (String sectionName : ini.getSectionNames()) {
                    LOGGER.debug("Section: " + sectionName);
                    Ini.Section section = ini.get(sectionName);
                    for (String key : section.keySet()) {
                        String value = section.get(key);
                        LOGGER.debug(key + " = " + value);
                    }
                }
            }
            parseIni(null, ini, validators, resourcePath, groupRolePrivilegeTableTemp);
            mergeResult(groupRolePrivilegeTable, groupRolePrivilegeTableTemp);
            groupRolePrivilegeTableTemp.clear();
            Ini.Section filesSection = ini.getSection(PolicyFileConstants.DATABASES);
            if (filesSection == null) {
                LOGGER.info("Section " + PolicyFileConstants.DATABASES + " needs no further processing");
            } else if (!allowPerDatabaseSection) {
                String msg = "Per-db policy file is not expected in this configuration.";
                throw new SentryConfigurationException(msg);
            } else {
                for (Map.Entry<String, String> entry : filesSection.entrySet()) {
                    String database = Strings.nullToEmpty(entry.getKey()).trim().toLowerCase();
                    Path perDbPolicy = new Path(Strings.nullToEmpty(entry.getValue()).trim());
                    if (isRelative(perDbPolicy)) {
                        perDbPolicy = new Path(resourcePath.getParent(), perDbPolicy);
                    }
                    try {
                        LOGGER.debug("Parsing " + perDbPolicy);
                        Ini perDbIni = PolicyFiles.loadFromPath(perDbPolicy.getFileSystem(conf), perDbPolicy);
                        if (perDbIni.containsKey(PolicyFileConstants.USERS)) {
                            configErrors.add("Per-db policy file cannot contain " + PolicyFileConstants.USERS
                                    + " section in " + perDbPolicy);
                            throw new SentryConfigurationException(
                                    "Per-db policy files cannot contain " + PolicyFileConstants.USERS + " section");
                        }
                        if (perDbIni.containsKey(PolicyFileConstants.DATABASES)) {
                            configErrors.add("Per-db policy files cannot contain " + PolicyFileConstants.DATABASES
                                    + " section in " + perDbPolicy);
                            throw new SentryConfigurationException("Per-db policy files cannot contain "
                                    + PolicyFileConstants.DATABASES + " section");
                        }
                        parseIni(database, perDbIni, validators, perDbPolicy, groupRolePrivilegeTableTemp);
                        mergeResult(groupRolePrivilegeTable, groupRolePrivilegeTableTemp);
                        groupRolePrivilegeTableTemp.clear();
                    } catch (Exception e) {
                        configErrors.add(
                                "Failed to read per-DB policy file " + perDbPolicy + " Error: " + e.getMessage());
                        LOGGER.error("Error processing key " + entry.getKey() + ", skipping " + entry.getValue(),
                                e);
                    }
                }
            }
        } catch (Exception e) {
            configErrors.add("Error processing file " + resourcePath + ".  Message: " + e.getMessage());
            LOGGER.error("Error processing file, ignoring " + resourcePath, e);
        }

        return groupRolePrivilegeTable;
    }

    /**
     * Relative for our purposes is no scheme, no authority
     * and a non-absolute path portion.
     */
    private boolean isRelative(Path path) {
        URI uri = path.toUri();
        return uri.getAuthority() == null && uri.getScheme() == null && !path.isUriPathAbsolute();
    }

    private void mergeResult(Table<String, String, Set<String>> groupRolePrivilegeTable,
            Table<String, String, Set<String>> groupRolePrivilegeTableTemp) {
        for (Cell<String, String, Set<String>> cell : groupRolePrivilegeTableTemp.cellSet()) {
            String groupName = cell.getRowKey();
            String roleName = cell.getColumnKey();
            Set<String> privileges = groupRolePrivilegeTable.get(groupName, roleName);
            if (privileges == null) {
                privileges = new HashSet<String>();
                groupRolePrivilegeTable.put(groupName, roleName, privileges);
            }
            privileges.addAll(cell.getValue());
        }
    }

    private void parseIni(String database, Ini ini, List<? extends PrivilegeValidator> validators, Path policyPath,
            Table<String, String, Set<String>> groupRolePrivilegeTable) {
        Ini.Section privilegesSection = ini.getSection(PolicyFileConstants.ROLES);
        boolean invalidConfiguration = false;
        if (privilegesSection == null) {
            String errMsg = String.format("Section %s empty for %s", PolicyFileConstants.ROLES, policyPath);
            LOGGER.warn(errMsg);
            configErrors.add(errMsg);
            invalidConfiguration = true;
        }
        Ini.Section groupsSection = ini.getSection(PolicyFileConstants.GROUPS);
        if (groupsSection == null) {
            String warnMsg = String.format("Section %s empty for %s", PolicyFileConstants.GROUPS, policyPath);
            LOGGER.warn(warnMsg);
            configErrors.add(warnMsg);
            invalidConfiguration = true;
        }
        if (!invalidConfiguration) {
            parsePrivileges(database, privilegesSection, groupsSection, validators, policyPath,
                    groupRolePrivilegeTable);
        }
    }

    private void parsePrivileges(@Nullable String database, Ini.Section rolesSection, Ini.Section groupsSection,
            List<? extends PrivilegeValidator> validators, Path policyPath,
            Table<String, String, Set<String>> groupRolePrivilegeTable) {
        Multimap<String, String> roleNameToPrivilegeMap = HashMultimap.create();
        for (Map.Entry<String, String> entry : rolesSection.entrySet()) {
            String roleName = stringInterner.intern(Strings.nullToEmpty(entry.getKey()).trim());
            String roleValue = Strings.nullToEmpty(entry.getValue()).trim();
            boolean invalidConfiguration = false;
            if (roleName.isEmpty()) {
                String errMsg = String.format("Empty role name encountered in %s", policyPath);
                LOGGER.warn(errMsg);
                configErrors.add(errMsg);
                invalidConfiguration = true;
            }
            if (roleValue.isEmpty()) {
                String errMsg = String.format("Empty role value encountered in %s", policyPath);
                LOGGER.warn(errMsg);
                configErrors.add(errMsg);
                invalidConfiguration = true;
            }
            if (roleNameToPrivilegeMap.containsKey(roleName)) {
                String warnMsg = String.format("Role %s defined twice in %s", roleName, policyPath);
                LOGGER.warn(warnMsg);
                configWarnings.add(warnMsg);
            }
            Set<String> privileges = PrivilegeUtils.toPrivilegeStrings(roleValue);
            if (!invalidConfiguration && privileges != null) {
                Set<String> internedPrivileges = Sets.newHashSet();
                for (String privilege : privileges) {
                    for (PrivilegeValidator validator : validators) {
                        validator.validate(new PrivilegeValidatorContext(database, privilege.trim()));
                    }
                    internedPrivileges.add(stringInterner.intern(privilege));
                }
                roleNameToPrivilegeMap.putAll(roleName, internedPrivileges);
            }
        }
        Splitter roleSplitter = ROLE_SPLITTER.omitEmptyStrings().trimResults();
        for (Map.Entry<String, String> entry : groupsSection.entrySet()) {
            String groupName = stringInterner.intern(Strings.nullToEmpty(entry.getKey()).trim());
            String groupPrivileges = Strings.nullToEmpty(entry.getValue()).trim();
            for (String roleName : roleSplitter.split(groupPrivileges)) {
                roleName = stringInterner.intern(roleName);
                if (roleNameToPrivilegeMap.containsKey(roleName)) {
                    Set<String> privileges = groupRolePrivilegeTable.get(groupName, roleName);
                    if (privileges == null) {
                        privileges = new HashSet<String>();
                        groupRolePrivilegeTable.put(groupName, roleName, privileges);
                    }
                    privileges.addAll(roleNameToPrivilegeMap.get(roleName));
                } else {
                    String warnMsg = String.format(
                            "Role %s for group %s does not exist in privileges section in %s", roleName, groupName,
                            policyPath);
                    LOGGER.warn(warnMsg);
                    configWarnings.add(warnMsg);
                }
            }
        }
    }

    /**
     * Returns backing table of group-role-privileges cache.
     * Caller must not modify the returned table.
     * @return backing table of cache.
     */
    public Table<String, String, Set<String>> getGroupRolePrivilegeTable() {
        return this.cache.getCache();
    }
}