Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.sentry.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(); } }