diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/RuleBasedLdapGroupsMapping.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/RuleBasedLdapGroupsMapping.java new file mode 100644 index 00000000000..6accf2fdced --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/RuleBasedLdapGroupsMapping.java @@ -0,0 +1,91 @@ +/** + * 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 com.google.common.annotations.VisibleForTesting; +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * This class uses {@link LdapGroupsMapping} for group lookup and applies the + * rule configured on the group names. + */ +@InterfaceAudience.LimitedPrivate({"HDFS"}) +@InterfaceStability.Evolving +public class RuleBasedLdapGroupsMapping extends LdapGroupsMapping { + + public static final String CONVERSION_RULE_KEY = LDAP_CONFIG_PREFIX + + ".conversion.rule"; + + private static final String CONVERSION_RULE_DEFAULT = "none"; + private static final Logger LOG = + LoggerFactory.getLogger(RuleBasedLdapGroupsMapping.class); + + private Rule rule; + + /** + * Supported rules applicable for group name modification. + */ + private enum Rule { + TO_UPPER, TO_LOWER, NONE + } + + @Override + public synchronized void setConf(Configuration conf) { + super.setConf(conf); + String value = conf.get(CONVERSION_RULE_KEY, CONVERSION_RULE_DEFAULT); + try { + rule = Rule.valueOf(value.toUpperCase()); + } catch (IllegalArgumentException iae) { + LOG.warn("Invalid {} configured: '{}'. Using default value: '{}'", + CONVERSION_RULE_KEY, value, CONVERSION_RULE_DEFAULT); + } + } + + /** + * Returns list of groups for a user. + * This calls {@link LdapGroupsMapping}'s getGroups and applies the + * configured rules on group names before returning. + * + * @param user get groups for this user + * @return list of groups for a given user + */ + @Override + public synchronized List getGroups(String user) { + List groups = super.getGroups(user); + switch (rule) { + case TO_UPPER: + return groups.stream().map(StringUtils::toUpperCase).collect( + Collectors.toList()); + case TO_LOWER: + return groups.stream().map(StringUtils::toLowerCase).collect( + Collectors.toList()); + case NONE: + default: + return groups; + } + } + +} diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml index ea26d0699be..6d6ed42eb8c 100644 --- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml +++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml @@ -275,6 +275,19 @@ + + hadoop.security.group.mapping.ldap.conversion.rule + none + + The rule is applied on the group names received from LDAP when + RuleBasedLdapGroupsMapping is configured. + Supported rules are "to_upper", "to_lower" and "none". + to_upper: This will convert all the group names to uppercase. + to_lower: This will convert all the group names to lowercase. + none: This will retain the source formatting, this is default value. + + + hadoop.security.credential.clear-text-fallback true diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java index 66777164013..6ca9c78cb2b 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java @@ -34,6 +34,7 @@ import org.apache.hadoop.io.nativeio.NativeIO; import org.apache.hadoop.security.CompositeGroupsMapping; import org.apache.hadoop.security.HttpCrossOriginFilterInitializer; import org.apache.hadoop.security.LdapGroupsMapping; +import org.apache.hadoop.security.RuleBasedLdapGroupsMapping; import org.apache.hadoop.security.http.CrossOriginFilter; import org.apache.hadoop.security.ssl.SSLFactory; @@ -74,7 +75,8 @@ public class TestCommonConfigurationFields extends TestConfigurationFieldsBase { ZKFailoverController.class, SSLFactory.class, CompositeGroupsMapping.class, - CodecUtil.class + CodecUtil.class, + RuleBasedLdapGroupsMapping.class }; // Initialize used variables diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestRuleBasedLdapGroupsMapping.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestRuleBasedLdapGroupsMapping.java new file mode 100644 index 00000000000..6592c79e3f5 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestRuleBasedLdapGroupsMapping.java @@ -0,0 +1,99 @@ +/** + * 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 org.apache.hadoop.conf.Configuration; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import javax.naming.NamingException; +import java.util.ArrayList; +import java.util.List; + +import static org.apache.hadoop.security.RuleBasedLdapGroupsMapping + .CONVERSION_RULE_KEY; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; + +/** + * Test cases to verify the rules supported by RuleBasedLdapGroupsMapping. + */ +public class TestRuleBasedLdapGroupsMapping { + + @Test + public void testGetGroupsToUpper() throws NamingException { + RuleBasedLdapGroupsMapping groupsMapping = Mockito.spy( + new RuleBasedLdapGroupsMapping()); + List groups = new ArrayList<>(); + groups.add("group1"); + groups.add("group2"); + Mockito.doReturn(groups).when((LdapGroupsMapping) groupsMapping) + .doGetGroups(eq("admin"), anyInt()); + + Configuration conf = new Configuration(); + conf.set(LdapGroupsMapping.LDAP_URL_KEY, "ldap://test"); + conf.set(CONVERSION_RULE_KEY, "to_upper"); + groupsMapping.setConf(conf); + + List groupsUpper = new ArrayList<>(); + groupsUpper.add("GROUP1"); + groupsUpper.add("GROUP2"); + Assert.assertEquals(groupsUpper, groupsMapping.getGroups("admin")); + } + + @Test + public void testGetGroupsToLower() throws NamingException { + RuleBasedLdapGroupsMapping groupsMapping = Mockito.spy( + new RuleBasedLdapGroupsMapping()); + List groups = new ArrayList<>(); + groups.add("GROUP1"); + groups.add("GROUP2"); + Mockito.doReturn(groups).when((LdapGroupsMapping) groupsMapping) + .doGetGroups(eq("admin"), anyInt()); + + Configuration conf = new Configuration(); + conf.set(LdapGroupsMapping.LDAP_URL_KEY, "ldap://test"); + conf.set(CONVERSION_RULE_KEY, "to_lower"); + groupsMapping.setConf(conf); + + List groupsLower = new ArrayList<>(); + groupsLower.add("group1"); + groupsLower.add("group2"); + Assert.assertEquals(groupsLower, groupsMapping.getGroups("admin")); + } + + @Test + public void testGetGroupsInvalidRule() throws NamingException { + RuleBasedLdapGroupsMapping groupsMapping = Mockito.spy( + new RuleBasedLdapGroupsMapping()); + List groups = new ArrayList<>(); + groups.add("group1"); + groups.add("GROUP2"); + Mockito.doReturn(groups).when((LdapGroupsMapping) groupsMapping) + .doGetGroups(eq("admin"), anyInt()); + + Configuration conf = new Configuration(); + conf.set(LdapGroupsMapping.LDAP_URL_KEY, "ldap://test"); + conf.set(CONVERSION_RULE_KEY, "none"); + groupsMapping.setConf(conf); + + Assert.assertEquals(groups, groupsMapping.getGroups("admin")); + } + +}