| /* |
| * 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.provider.file.PolicyFileConstants.DATABASES; |
| import static org.apache.sentry.provider.file.PolicyFileConstants.GROUPS; |
| import static org.apache.sentry.provider.file.PolicyFileConstants.ROLES; |
| import static org.apache.sentry.provider.file.PolicyFileConstants.ROLE_SPLITTER; |
| import static org.apache.sentry.provider.file.PolicyFileConstants.USERS; |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| 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.AccessURI; |
| import org.apache.sentry.core.Authorizable; |
| import org.apache.sentry.core.Database; |
| import org.apache.shiro.config.ConfigurationException; |
| import org.apache.shiro.config.Ini; |
| import org.apache.shiro.util.PermissionUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Splitter; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.HashMultimap; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSetMultimap; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Multimap; |
| import com.google.common.collect.Sets; |
| |
| public class SimplePolicyEngine implements PolicyEngine { |
| |
| private static final Logger LOGGER = LoggerFactory |
| .getLogger(SimplePolicyEngine.class); |
| |
| |
| |
| private final FileSystem fileSystem; |
| private final Path resourcePath; |
| private final String serverName; |
| private final List<Path> perDbResources = Lists.newArrayList(); |
| private final AtomicReference<Roles> rolesReference; |
| public final static String ACCESS_ALLOW_URI_PER_DB_POLICYFILE = "sentry.allow.uri.db.policyfile"; |
| |
| public SimplePolicyEngine(String resourcePath, String serverName) throws IOException { |
| this(new Configuration(), new Path(resourcePath), serverName); |
| } |
| @VisibleForTesting |
| public SimplePolicyEngine(Configuration conf, Path resourcePath, String serverName) throws IOException { |
| this.resourcePath = resourcePath; |
| this.serverName = serverName; |
| this.fileSystem = resourcePath.getFileSystem(conf); |
| this.rolesReference = new AtomicReference<Roles>(); |
| this.rolesReference.set(new Roles()); |
| parse(); |
| } |
| |
| /** |
| * Parse the resource. Should not be used in the normal course |
| */ |
| protected void parse() { |
| LOGGER.info("Parsing " + resourcePath); |
| Roles roles = new Roles(); |
| try { |
| perDbResources.clear(); |
| Ini ini = PolicyFiles.loadFromPath(fileSystem, resourcePath); |
| 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); |
| } |
| } |
| } |
| ImmutableSetMultimap<String, String> globalRoles; |
| Map<String, ImmutableSetMultimap<String, String>> perDatabaseRoles = Maps.newHashMap(); |
| globalRoles = parseIni(null, ini); |
| Ini.Section filesSection = ini.getSection(DATABASES); |
| if(filesSection == null) { |
| LOGGER.info("Section " + DATABASES + " needs no further processing"); |
| } 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.info("Parsing " + perDbPolicy); |
| Ini perDbIni = PolicyFiles.loadFromPath(fileSystem, perDbPolicy); |
| if(perDbIni.containsKey(USERS)) { |
| throw new ConfigurationException("Per-db policy files cannot contain " + USERS + " section"); |
| } |
| if(perDbIni.containsKey(DATABASES)) { |
| throw new ConfigurationException("Per-db policy files cannot contain " + DATABASES + " section"); |
| } |
| ImmutableSetMultimap<String, String> currentDbRoles = parseIni(database, perDbIni); |
| perDatabaseRoles.put(database, currentDbRoles); |
| perDbResources.add(perDbPolicy); |
| } catch (Exception e) { |
| LOGGER.error("Error processing key " + entry.getKey() + ", skipping " + entry.getValue(), e); |
| } |
| } |
| } |
| roles = new Roles(globalRoles, ImmutableMap.copyOf(perDatabaseRoles)); |
| } catch (Exception e) { |
| LOGGER.error("Error processing file, ignoring " + resourcePath, e); |
| } |
| rolesReference.set(roles); |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| protected long getModificationTime() throws IOException { |
| // if resource path has been deleted, throw all exceptions |
| long result = fileSystem.getFileStatus(resourcePath).getModificationTime(); |
| for(Path perDbPolicy : perDbResources) { |
| try { |
| result = Math.max(result, fileSystem.getFileStatus(perDbPolicy).getModificationTime()); |
| } catch (FileNotFoundException e) { |
| // if a per-db file has been deleted, wait until the main |
| // policy file has been updated before refreshing |
| } |
| } |
| return result; |
| } |
| |
| private ImmutableSetMultimap<String, String> parseIni(String database, Ini ini) { |
| Ini.Section privilegesSection = ini.getSection(ROLES); |
| boolean invalidConfiguration = false; |
| if (privilegesSection == null) { |
| LOGGER.warn("Section {} empty for {}", ROLES, resourcePath); |
| invalidConfiguration = true; |
| } |
| Ini.Section groupsSection = ini.getSection(GROUPS); |
| if (groupsSection == null) { |
| LOGGER.warn("Section {} empty for {}", GROUPS, resourcePath); |
| invalidConfiguration = true; |
| } |
| if (!invalidConfiguration) { |
| return parsePermissions(database, privilegesSection, groupsSection); |
| } |
| return ImmutableSetMultimap.of(); |
| } |
| |
| private ImmutableSetMultimap<String, String> parsePermissions(@Nullable String database, |
| Ini.Section rolesSection, Ini.Section groupsSection) { |
| ImmutableSetMultimap.Builder<String, String> resultBuilder = ImmutableSetMultimap.builder(); |
| Multimap<String, String> roleNameToPrivilegeMap = HashMultimap |
| .create(); |
| List<? extends RoleValidator> validators = Lists.newArrayList( |
| new ServersAllIsInvalid(), |
| new DatabaseMustMatch(), |
| new DatabaseRequiredInRole(), |
| new ServerNameMustMatch(serverName)); |
| for (Map.Entry<String, String> entry : rolesSection.entrySet()) { |
| String roleName = Strings.nullToEmpty(entry.getKey()).trim(); |
| String roleValue = Strings.nullToEmpty(entry.getValue()).trim(); |
| boolean invalidConfiguration = false; |
| if (roleName.isEmpty()) { |
| LOGGER.warn("Empty role name encountered in {}", resourcePath); |
| invalidConfiguration = true; |
| } |
| if (roleValue.isEmpty()) { |
| LOGGER.warn("Empty role value encountered in {}", resourcePath); |
| invalidConfiguration = true; |
| } |
| if (roleNameToPrivilegeMap.containsKey(roleName)) { |
| LOGGER.warn("Role {} defined twice in {}", roleName, |
| resourcePath); |
| } |
| Set<String> roles = PermissionUtils |
| .toPermissionStrings(roleValue); |
| if (!invalidConfiguration && roles != null) { |
| for(String role : roles) { |
| for(RoleValidator validator : validators) { |
| validator.validate(database, role.trim()); |
| } |
| } |
| roleNameToPrivilegeMap.putAll(roleName, roles); |
| } |
| } |
| Splitter roleSplitter = ROLE_SPLITTER.omitEmptyStrings().trimResults(); |
| for (Map.Entry<String, String> entry : groupsSection.entrySet()) { |
| String groupName = Strings.nullToEmpty(entry.getKey()).trim(); |
| String groupPrivileges = Strings.nullToEmpty(entry.getValue()).trim(); |
| Collection<String> resolvedGroupPrivileges = Sets.newHashSet(); |
| for (String roleName : roleSplitter.split(groupPrivileges)) { |
| if (roleNameToPrivilegeMap.containsKey(roleName)) { |
| resolvedGroupPrivileges.addAll(roleNameToPrivilegeMap |
| .get(roleName)); |
| } else { |
| LOGGER.warn("Role {} for group {} does not exist in privileges section in {}", |
| new Object[] { roleName, groupName, resourcePath }); |
| } |
| } |
| resultBuilder.putAll(groupName, resolvedGroupPrivileges); |
| } |
| return resultBuilder.build(); |
| } |
| |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public ImmutableSetMultimap<String, String> getPermissions(List<Authorizable> authorizables, List<String> groups) { |
| Roles roles = rolesReference.get(); |
| String database = null; |
| Boolean isURI = false; |
| for(Authorizable authorizable : authorizables) { |
| if(authorizable instanceof Database) { |
| database = authorizable.getName(); |
| } |
| if (authorizable instanceof AccessURI) { |
| isURI = true; |
| } |
| } |
| |
| if(LOGGER.isDebugEnabled()) { |
| LOGGER.debug("Getting permissions for {} via {}", groups, database); |
| } |
| ImmutableSetMultimap.Builder<String, String> resultBuilder = ImmutableSetMultimap.builder(); |
| for(String group : groups) { |
| resultBuilder.putAll(group, roles.getRoles(database, group, isURI)); |
| } |
| ImmutableSetMultimap<String, String> result = resultBuilder.build(); |
| if(LOGGER.isDebugEnabled()) { |
| LOGGER.debug("result = " + result); |
| } |
| return result; |
| } |
| } |