| /** |
| * 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.hadoop.security; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import org.apache.hadoop.classification.InterfaceAudience; |
| import org.apache.hadoop.classification.InterfaceStability; |
| import org.apache.hadoop.conf.Configurable; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.util.ReflectionUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * An implementation of {@link GroupMappingServiceProvider} which |
| * composites other group mapping providers for determining group membership. |
| * This allows to combine existing provider implementations and composite |
| * a virtually new provider without customized development to deal with complex situation. |
| */ |
| @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"}) |
| @InterfaceStability.Evolving |
| public class CompositeGroupsMapping |
| implements GroupMappingServiceProvider, Configurable { |
| |
| public static final String MAPPING_PROVIDERS_CONFIG_KEY = GROUP_MAPPING_CONFIG_PREFIX + ".providers"; |
| public static final String MAPPING_PROVIDERS_COMBINED_CONFIG_KEY = MAPPING_PROVIDERS_CONFIG_KEY + ".combined"; |
| public static final String MAPPING_PROVIDER_CONFIG_PREFIX = GROUP_MAPPING_CONFIG_PREFIX + ".provider"; |
| |
| private static final Logger LOG = |
| LoggerFactory.getLogger(CompositeGroupsMapping.class); |
| |
| private List<GroupMappingServiceProvider> providersList = |
| new ArrayList<GroupMappingServiceProvider>(); |
| |
| private Configuration conf; |
| private boolean combined; |
| |
| |
| |
| /** |
| * Returns list of groups for a user. |
| * |
| * @param user get groups for this user |
| * @return list of groups for a given user |
| */ |
| @Override |
| public synchronized List<String> getGroups(String user) throws IOException { |
| Set<String> groupSet = new TreeSet<String>(); |
| |
| for (GroupMappingServiceProvider provider : providersList) { |
| List<String> groups = Collections.emptyList(); |
| try { |
| groups = provider.getGroups(user); |
| } catch (Exception e) { |
| LOG.warn("Unable to get groups for user {} via {} because: {}", |
| user, provider.getClass().getSimpleName(), e.toString()); |
| LOG.debug("Stacktrace: ", e); |
| } |
| if (!groups.isEmpty()) { |
| groupSet.addAll(groups); |
| if (!combined) break; |
| } |
| } |
| |
| return new ArrayList<>(groupSet); |
| } |
| |
| /** |
| * Caches groups, no need to do that for this provider |
| */ |
| @Override |
| public void cacheGroupsRefresh() throws IOException { |
| // does nothing in this provider of user to groups mapping |
| } |
| |
| /** |
| * Adds groups to cache, no need to do that for this provider |
| * |
| * @param groups unused |
| */ |
| @Override |
| public void cacheGroupsAdd(List<String> groups) throws IOException { |
| // does nothing in this provider of user to groups mapping |
| } |
| |
| @Override |
| public synchronized Set<String> getGroupsSet(String user) throws IOException { |
| Set<String> groupSet = new HashSet<String>(); |
| |
| Set<String> groups = null; |
| for (GroupMappingServiceProvider provider : providersList) { |
| try { |
| groups = provider.getGroupsSet(user); |
| } catch (Exception e) { |
| LOG.warn("Unable to get groups for user {} via {} because: {}", |
| user, provider.getClass().getSimpleName(), e.toString()); |
| LOG.debug("Stacktrace: ", e); |
| } |
| if (groups != null && !groups.isEmpty()) { |
| groupSet.addAll(groups); |
| if (!combined) { |
| break; |
| } |
| } |
| } |
| return groupSet; |
| } |
| |
| @Override |
| public synchronized Configuration getConf() { |
| return conf; |
| } |
| |
| @Override |
| public synchronized void setConf(Configuration conf) { |
| this.conf = conf; |
| |
| this.combined = conf.getBoolean(MAPPING_PROVIDERS_COMBINED_CONFIG_KEY, true); |
| |
| loadMappingProviders(); |
| } |
| |
| private void loadMappingProviders() { |
| String[] providerNames = conf.getStrings(MAPPING_PROVIDERS_CONFIG_KEY, new String[]{}); |
| |
| String providerKey; |
| for (String name : providerNames) { |
| providerKey = MAPPING_PROVIDER_CONFIG_PREFIX + "." + name; |
| Class<?> providerClass = conf.getClass(providerKey, null); |
| if (providerClass == null) { |
| LOG.error("The mapping provider, " + name + " does not have a valid class"); |
| } else { |
| addMappingProvider(name, providerClass); |
| } |
| } |
| } |
| |
| private void addMappingProvider(String providerName, Class<?> providerClass) { |
| Configuration newConf = prepareConf(providerName); |
| GroupMappingServiceProvider provider = |
| (GroupMappingServiceProvider) ReflectionUtils.newInstance(providerClass, newConf); |
| providersList.add(provider); |
| |
| } |
| |
| /* |
| * For any provider specific configuration properties, such as "hadoop.security.group.mapping.ldap.url" |
| * and the like, allow them to be configured as "hadoop.security.group.mapping.provider.PROVIDER-X.ldap.url", |
| * so that a provider such as LdapGroupsMapping can be used to composite a complex one with other providers. |
| */ |
| private Configuration prepareConf(String providerName) { |
| Configuration newConf = new Configuration(); |
| Iterator<Map.Entry<String, String>> entries = conf.iterator(); |
| String providerKey = MAPPING_PROVIDER_CONFIG_PREFIX + "." + providerName; |
| while (entries.hasNext()) { |
| Map.Entry<String, String> entry = entries.next(); |
| String key = entry.getKey(); |
| // get a property like "hadoop.security.group.mapping.provider.PROVIDER-X.ldap.url" |
| if (key.startsWith(providerKey) && !key.equals(providerKey)) { |
| // restore to be the one like "hadoop.security.group.mapping.ldap.url" |
| // so that can be used by original provider. |
| key = key.replace(".provider." + providerName, ""); |
| newConf.set(key, entry.getValue()); |
| } |
| } |
| return newConf; |
| } |
| } |