Skip to content

Commit

Permalink
ADH-2554
Browse files Browse the repository at this point in the history
  • Loading branch information
Asmoday committed Jul 15, 2024
1 parent e1c5e16 commit 45e8fe8
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 6 deletions.
1 change: 1 addition & 0 deletions common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
Original file line number Diff line number Diff line change
Expand Up @@ -4337,6 +4337,7 @@ public static enum ConfVars {
HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD("hive.server2.authentication.ldap.bindpw", null,
"The password for the bind user, to be used to search for the full name of the user being authenticated.\n" +
"If the username is specified, this parameter must also be specified."),
HIVE_SERVER2_PLAIN_LDAP_USE_PATTERN_FILTER("hive.server2.authentication.ldap.usePatternFilter", false, ""),
HIVE_SERVER2_CUSTOM_AUTHENTICATION_CLASS("hive.server2.custom.authentication.class", null,
"Custom authentication class. Used when property\n" +
"'hive.server2.authentication' is set to 'CUSTOM'. Provided class\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.apache.hive.service.auth.ldap.UserFilterFactory;
import org.apache.hive.service.auth.ldap.UserGroupSearchFilterFactory;
import org.apache.hive.service.auth.ldap.UserSearchFilterFactory;
import org.apache.hive.service.auth.ldap.PatternFilterFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -48,9 +49,9 @@ public class LdapAuthenticationProviderImpl implements PasswdAuthenticationProvi

private static final List<FilterFactory> FILTER_FACTORIES = ImmutableList.<FilterFactory>of(
new UserGroupSearchFilterFactory(),
new CustomQueryFilterFactory(),
new ChainFilterFactory(new UserSearchFilterFactory(), new UserFilterFactory(),
new GroupFilterFactory())
new CustomQueryFilterFactory(),
new ChainFilterFactory(new UserSearchFilterFactory(), new UserFilterFactory(),
new GroupFilterFactory()), new PatternFilterFactory()
);

private final HiveConf conf;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.apache.hive.service.auth.ldap;

import org.apache.hadoop.hive.conf.HiveConf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.security.sasl.AuthenticationException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class PatternFilterFactory implements FilterFactory {

@Override
public Filter getInstance(HiveConf conf) {
final boolean usePatternFilter = conf.getBoolVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USE_PATTERN_FILTER);

if (usePatternFilter) {
final List<String> userPatterns = LdapUtils.parseDnPatterns(conf,
HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN);
final List<String> groupBases = LdapUtils.patternsToBaseDns(LdapUtils.parseDnPatterns(conf,
HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN));
final List<String> userBases = LdapUtils.patternsToBaseDns(userPatterns);
return new PatternFilter(userBases, groupBases);
} else {
return null;
}
}


private static final class PatternFilter implements Filter {

private static final Logger LOG = LoggerFactory.getLogger(PatternFilter.class);

private final List<String> userBases;
private final List<String> groupBases;

public PatternFilter(List<String> userBases, List<String> groupBases) {
this.userBases = userBases;
this.groupBases = groupBases;
}

@Override
public void apply(DirSearch client, String user) throws AuthenticationException {
LOG.info("Authenticating user '{}' using {}", user,
PatternFilter.class.getSimpleName());
try {
String userDn = client.findUserDn(user);
String baseDn = LdapUtils.extractBaseDn(userDn);
if (!userBases.contains(baseDn)) {
LOG.debug("{} does not contain user {} base DN {}",
String.join(":", userBases), userDn, baseDn);
List<String> userGroups = client.findGroupsForUser(userDn).stream()
.map(LdapUtils::extractBaseDn)
.collect(Collectors.toList());
Optional<String> intersectWithBaseGroupsCount = userGroups.stream()
.filter(groupBases::contains)
.findAny();
if (!intersectWithBaseGroupsCount.isPresent()) {
LOG.debug("{} is not a member of any group {}", userDn, String.join(":", groupBases));
throw new AuthenticationException(
String.format("Authentication failed: User %s with baseDn %s not a member of specified lists: %s ; %s",
userDn, baseDn, String.join(":", userBases), String.join(":", groupBases)));
}
}
LOG.info("Authentication succeeded based on user-group pattern membership");
} catch (Exception e) {
throw new AuthenticationException(e.getMessage());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -744,4 +744,70 @@ public void testUserAndGroupSearchFilterNegative() throws Exception {
testCase.assertAuthenticateFails(USER3.credentialsWithId());
testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId());
}
@Test
public void testPatternFilterPositiveUserPattern() {
testCase = defaultBuilder()
.userDNPatterns("uid=%s,ou=People,dc=example,dc=com")
.enablePatternFilter()
.build();

testCase.assertAuthenticatePasses(USER1.credentialsWithId());
testCase.assertAuthenticatePasses(USER2.credentialsWithId());
}

@Test
public void testPatternFilterPositiveGroupPattern() {
testCase = defaultBuilder()
.userDNPatterns("uid=%s,ou=DummyPeople,dc=example,dc=com")
.groupDNPatterns("uid=%s,ou=Groups,dc=example,dc=com")
.enablePatternFilter()
.build();

testCase.assertAuthenticatePasses(USER1.credentialsWithDn());
testCase.assertAuthenticatePasses(USER2.credentialsWithDn());
}

@Test
public void testDirectUserMembershipPatternFilterPositive() {
testCase = defaultBuilder()
.userDNPatterns(
"sAMAccountName=%s,ou=Engineering,dc=ad,dc=example,dc=com",
"sAMAccountName=%s,ou=Management,dc=ad,dc=example,dc=com")
.groupDNPatterns(
"sAMAccountName=%s,ou=Teams,dc=ad,dc=example,dc=com",
"sAMAccountName=%s,ou=Resources,dc=ad,dc=example,dc=com")
.guidKey("sAMAccountName")
.userMembershipKey("memberOf")
.enablePatternFilter()
.build();

testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithId());
testCase.assertAuthenticatePasses(ENGINEER_2.credentialsWithId());
testCase.assertAuthenticatePasses(MANAGER_1.credentialsWithId());
testCase.assertAuthenticatePasses(MANAGER_2.credentialsWithId());
}

@Test
public void testPatternFilterNegativeEmptyDn() {
testCase = defaultBuilder()
.baseDN("ou=People,dc=example,dc=com")
.userDNPatterns("")
.groupDNPatterns("")
.enablePatternFilter()
.build();

testCase.assertAuthenticateFails(ADMIN_1.credentialsWithId());
}

@Test
public void testPatternFilterNegativeWrongData() {
testCase = defaultBuilder()
.userDNPatterns("uid=%s,ou=DummyPeople,dc=example,dc=com")
.groupDNPatterns("uid=%s,ou=DummyGroups,dc=example,dc=com")
.enablePatternFilter()
.build();

testCase.assertAuthenticateFails(USER1.credentialsWithDn());
testCase.assertAuthenticateFails(USER2.credentialsWithDn());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void assertAuthenticateFails(String user, String password) {

public static final class Builder {

private final Map<HiveConf.ConfVars, String> overrides = new EnumMap<>(HiveConf.ConfVars.class);
private final Map<HiveConf.ConfVars, Object> overrides = new EnumMap<>(HiveConf.ConfVars.class);
private HiveConf conf;

public Builder baseDN(String baseDN) {
Expand Down Expand Up @@ -138,17 +138,33 @@ public Builder groupBaseDN(String groupBaseDN) {
groupBaseDN);
}

public Builder enablePatternFilter() {
return setVarOnce(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USE_PATTERN_FILTER, true);
}

private Builder setVarOnce(HiveConf.ConfVars confVar, String value) {
Preconditions.checkState(!overrides.containsKey(confVar),
"Property %s has been set already", confVar);
overrides.put(confVar, value);
return this;
}

private Builder setVarOnce(HiveConf.ConfVars confVar, boolean value) {
Preconditions.checkState(!overrides.containsKey(confVar),
"Property %s has been set already", confVar);
overrides.put(confVar, value);
return this;
}

private void overrideHiveConf() {
conf.set("hive.root.logger", "DEBUG,console");
for (Map.Entry<HiveConf.ConfVars, String> entry : overrides.entrySet()) {
conf.setVar(entry.getKey(), entry.getValue());
for (Map.Entry<HiveConf.ConfVars, Object> entry : overrides.entrySet()) {
if (entry.getValue() instanceof String) {
conf.setVar(entry.getKey(),(String) entry.getValue());
}
else if (entry.getValue() instanceof Boolean) {
conf.setBoolVar(entry.getKey(), (Boolean) entry.getValue());
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.apache.hive.service.auth.ldap;

import org.apache.hadoop.hive.conf.HiveConf;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import javax.naming.NamingException;
import javax.security.sasl.AuthenticationException;

import java.util.Collections;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class TestPatternFilter {

private FilterFactory factory;
private HiveConf conf;

@Mock
private DirSearch search;

@Before
public void setup() {
conf = new HiveConf();
factory = new PatternFilterFactory();
}

@Test
public void testFactory() {
conf.unset(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USE_PATTERN_FILTER.varname);
assertNull(factory.getInstance(conf));

conf.setBoolVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USE_PATTERN_FILTER, false);
assertNull(factory.getInstance(conf));

conf.setBoolVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USE_PATTERN_FILTER, true);
assertNotNull(factory.getInstance(conf));
}

@Test
public void testApplyPositive() throws AuthenticationException, NamingException {
conf.setBoolVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USE_PATTERN_FILTER, true);
conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
"sAMAccountname=%s,OU=ldap_ou,DC=example,DC=com:sAMAccountname=%s,OU=hive_ou,DC=example,DC=com");
conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN,
"CN=%s,OU=ldap_ou,DC=example,DC=com:CN=%s,OU=hive_ou,DC=example,DC=com");
when(search.findUserDn(eq("User1")))
.thenReturn("CN=User1,OU=ldap_ou,DC=example,DC=com");
when(search.findUserDn(eq("User2")))
.thenReturn("CN=User2,OU=hive_ou,DC=example,DC=com");

when(search.findUserDn(eq("User3")))
.thenReturn("CN=User3,OU=hive_test_ou,DC=example,DC=com");
when(search.findGroupsForUser("CN=User3,OU=hive_test_ou,DC=example,DC=com"))
.thenReturn(Collections.singletonList("CN=group1,OU=ldap_ou,DC=example,DC=com"));

when(search.findUserDn(eq("User4")))
.thenReturn("CN=User4,OU=hive_test_ou,DC=example,DC=com");
when(search.findGroupsForUser("CN=User4,OU=hive_test_ou,DC=example,DC=com"))
.thenReturn(Collections.singletonList("CN=group2,OU=hive_ou,DC=example,DC=com"));

Filter filter = factory.getInstance(conf);
filter.apply(search, "User1");
filter.apply(search, "User2");
filter.apply(search, "User3");
filter.apply(search, "User4");
}

@Test(expected = AuthenticationException.class)
public void testApplyNegative() throws AuthenticationException, NamingException {
conf.setBoolVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USE_PATTERN_FILTER, true);
conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
"sAMAccountname=%s,OU=ldap_ou,DC=example,DC=com:sAMAccountname=%s,OU=hive_ou,DC=example,DC=com");
conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN,
"CN=%s,OU=ldap_ou,DC=example,DC=com:CN=%s,OU=hive_ou,DC=example,DC=com");

when(search.findUserDn(eq("User2")))
.thenReturn("CN=User2,OU=hive_test_ou,DC=example,DC=com");
Filter filter = factory.getInstance(conf);
filter.apply(search, "User1");
filter.apply(search, "User2");
}
}

0 comments on commit 45e8fe8

Please sign in to comment.