You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by ab...@apache.org on 2021/12/03 06:39:33 UTC
[druid] branch master updated: Fix classNotFoundException when connecting to secure LDAP (#11978)
This is an automated email from the ASF dual-hosted git repository.
abhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 5033845 Fix classNotFoundException when connecting to secure LDAP (#11978)
5033845 is described below
commit 503384569aea81bc20b786e050bb47de37c898f2
Author: Abhishek Agarwal <14...@users.noreply.github.com>
AuthorDate: Fri Dec 3 12:08:19 2021 +0530
Fix classNotFoundException when connecting to secure LDAP (#11978)
This PR fixes a problem where the com.sun.jndi.ldap.Connection tries to build BasicSecuritySSLSocketFactory when calling LDAPCredentialsValidator.validateCredentials since BasicSecuritySSLSocketFactory is in extension class loader and not visible to system classloader.
---
.../validator/LDAPCredentialsValidator.java | 33 ++++++-
.../validator/LDAPCredentialsValidatorTest.java | 107 +++++++++++++++++++++
2 files changed, 139 insertions(+), 1 deletion(-)
diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authentication/validator/LDAPCredentialsValidator.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authentication/validator/LDAPCredentialsValidator.java
index 1db8799..9665f52 100644
--- a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authentication/validator/LDAPCredentialsValidator.java
+++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authentication/validator/LDAPCredentialsValidator.java
@@ -22,6 +22,7 @@ package org.apache.druid.security.basic.authentication.validator;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
+import com.google.common.annotations.VisibleForTesting;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.metadata.PasswordProvider;
@@ -43,6 +44,7 @@ import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapName;
+
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -58,6 +60,9 @@ public class LDAPCredentialsValidator implements CredentialsValidator
private final LruBlockCache cache;
private final BasicAuthLDAPConfig ldapConfig;
+ // Custom overrides that can be passed via tests
+ @Nullable
+ private final Properties overrideProperties;
@JsonCreator
public LDAPCredentialsValidator(
@@ -91,6 +96,19 @@ public class LDAPCredentialsValidator implements CredentialsValidator
this.ldapConfig.getCredentialVerifyDuration(),
this.ldapConfig.getCredentialMaxDuration()
);
+ this.overrideProperties = null;
+ }
+
+ @VisibleForTesting
+ public LDAPCredentialsValidator(
+ final BasicAuthLDAPConfig ldapConfig,
+ final LruBlockCache cache,
+ final Properties overrideProperties
+ )
+ {
+ this.ldapConfig = ldapConfig;
+ this.cache = cache;
+ this.overrideProperties = overrideProperties;
}
Properties bindProperties(BasicAuthLDAPConfig ldapConfig)
@@ -119,6 +137,9 @@ public class LDAPCredentialsValidator implements CredentialsValidator
properties.put(Context.SECURITY_PROTOCOL, "ssl");
properties.put("java.naming.ldap.factory.socket", BasicSecuritySSLSocketFactory.class.getName());
}
+ if (null != overrideProperties) {
+ properties.putAll(overrideProperties);
+ }
return properties;
}
@@ -139,7 +160,11 @@ public class LDAPCredentialsValidator implements CredentialsValidator
contextMap.put(BasicAuthUtils.SEARCH_RESULT_CONTEXT_KEY, principal.getSearchResult());
return new AuthenticationResult(username, authorizerName, authenticatorName, contextMap);
} else {
+ ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
try {
+ // Set the context classloader same as the loader of this class so that BasicSecuritySSLSocketFactory
+ // class can be found
+ Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
InitialDirContext dirContext = new InitialDirContext(bindProperties(this.ldapConfig));
try {
userResult = getLdapUserObject(this.ldapConfig, dirContext, username);
@@ -162,6 +187,9 @@ public class LDAPCredentialsValidator implements CredentialsValidator
LOG.error(e, "Exception during user lookup");
return null;
}
+ finally {
+ Thread.currentThread().setContextClassLoader(currentClassLoader);
+ }
if (!validatePassword(this.ldapConfig, userDn, password)) {
LOG.debug("Password incorrect for LDAP user %s", username);
@@ -213,8 +241,10 @@ public class LDAPCredentialsValidator implements CredentialsValidator
boolean validatePassword(BasicAuthLDAPConfig ldapConfig, LdapName userDn, char[] password)
{
InitialDirContext context = null;
+ ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
try {
+ Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
context = new InitialDirContext(userProperties(ldapConfig, userDn, password));
return true;
}
@@ -235,10 +265,11 @@ public class LDAPCredentialsValidator implements CredentialsValidator
LOG.warn("Exception closing LDAP context");
// ignored
}
+ Thread.currentThread().setContextClassLoader(currentClassLoader);
}
}
- private static class LruBlockCache extends LinkedHashMap<String, LdapUserPrincipal>
+ public static class LruBlockCache extends LinkedHashMap<String, LdapUserPrincipal>
{
private static final long serialVersionUID = 7509410739092012261L;
diff --git a/extensions-core/druid-basic-security/src/test/java/org/apache/druid/security/authentication/validator/LDAPCredentialsValidatorTest.java b/extensions-core/druid-basic-security/src/test/java/org/apache/druid/security/authentication/validator/LDAPCredentialsValidatorTest.java
index 56aee16..aabdea3 100644
--- a/extensions-core/druid-basic-security/src/test/java/org/apache/druid/security/authentication/validator/LDAPCredentialsValidatorTest.java
+++ b/extensions-core/druid-basic-security/src/test/java/org/apache/druid/security/authentication/validator/LDAPCredentialsValidatorTest.java
@@ -19,12 +19,43 @@
package org.apache.druid.security.authentication.validator;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.metadata.DefaultPasswordProvider;
+import org.apache.druid.security.basic.BasicAuthLDAPConfig;
+import org.apache.druid.security.basic.BasicAuthUtils;
import org.apache.druid.security.basic.authentication.validator.LDAPCredentialsValidator;
import org.junit.Assert;
import org.junit.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+
+import javax.naming.Context;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapContext;
+import javax.naming.spi.InitialContextFactory;
+
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Properties;
public class LDAPCredentialsValidatorTest
{
+ private static final BasicAuthLDAPConfig LDAP_CONFIG = new BasicAuthLDAPConfig(
+ "ldaps://my-ldap-url",
+ "bindUser",
+ new DefaultPasswordProvider("bindPassword"),
+ "",
+ "",
+ "",
+ BasicAuthUtils.DEFAULT_KEY_ITERATIONS,
+ BasicAuthUtils.DEFAULT_CREDENTIAL_VERIFY_DURATION_SECONDS,
+ BasicAuthUtils.DEFAULT_CREDENTIAL_MAX_DURATION_SECONDS,
+ BasicAuthUtils.DEFAULT_CREDENTIAL_CACHE_SIZE);
+
@Test
public void testEncodeForLDAP_noSpecialChars()
{
@@ -44,4 +75,80 @@ public class LDAPCredentialsValidatorTest
Assert.assertEquals(expectedWildcardTrue, encodedWildcardTrue);
Assert.assertEquals(expectedWildcardFalse, encodedWildcardFalse);
}
+
+ /**
+ * This doesn't test password validation.
+ */
+ @Test
+ public void testValidateCredentials()
+ {
+ Properties properties = new Properties();
+ properties.put(Context.INITIAL_CONTEXT_FACTORY, MockContextFactory.class.getName());
+ LDAPCredentialsValidator validator = new LDAPCredentialsValidator(
+ LDAP_CONFIG,
+ new LDAPCredentialsValidator.LruBlockCache(
+ 3600,
+ 3600,
+ 100
+ ),
+ properties
+ );
+ validator.validateCredentials("ldap", "ldap", "validUser", "password".toCharArray());
+ }
+
+ public static class MockContextFactory implements InitialContextFactory
+ {
+ @Override
+ public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException
+ {
+ LdapContext context = Mockito.mock(LdapContext.class);
+
+ String encodedUsername = LDAPCredentialsValidator.encodeForLDAP("validUser", true);
+ SearchResult result = Mockito.mock(SearchResult.class);
+ Mockito.when(result.getNameInNamespace()).thenReturn("uid=user,ou=Users,dc=example,dc=org");
+ Iterator<SearchResult> results = Collections.singletonList(result).iterator();
+
+ Mockito.when(
+ context.search(
+ ArgumentMatchers.eq(LDAP_CONFIG.getBaseDn()),
+ ArgumentMatchers.eq(StringUtils.format(LDAP_CONFIG.getUserSearch(), encodedUsername)),
+ ArgumentMatchers.any(SearchControls.class))
+ ).thenReturn(new NamingEnumeration<SearchResult>()
+ {
+ @Override
+ public SearchResult next()
+ {
+ return results.next();
+ }
+
+ @Override
+ public boolean hasMore()
+ {
+ return results.hasNext();
+ }
+
+ @Override
+ public void close()
+ {
+ // No-op
+ }
+
+ @Override
+ public boolean hasMoreElements()
+ {
+ return results.hasNext();
+ }
+
+ @Override
+ public SearchResult nextElement()
+ {
+ return results.next();
+ }
+ });
+
+ return context;
+ }
+ }
+
+
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org