blob: 6f799c154209575711a88dc0cffdac506cf5813e [file] [log] [blame]
/**
* 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;
}
}