Skip to content

HADOOP-19598. LdapAuthenticationHandler supports configuring multiple ldapUrls. #7772

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;

import javax.naming.Context;
Expand Down Expand Up @@ -100,11 +102,19 @@ public class LdapAuthenticationHandler implements AuthenticationHandler {
*/
public static final String ENABLE_START_TLS = TYPE + ".enablestarttls";

/**
* Constant for the number of attempts per LDAP server URL.
*/
public static final String NUM_ATTEMPTS = TYPE + ".numattempts";

public static final String NUM_ATTEMPTS_DEFAULT = "2";

private String ldapDomain;
private String baseDN;
private String providerUrl;
private Boolean enableStartTls;
private Boolean disableHostNameVerification;
private Iterator<String> ldapUrls;
private int numAttempts;

/**
* Configure StartTLS LDAP extension for this handler.
Expand Down Expand Up @@ -138,25 +148,31 @@ public String getType() {
@Override
public void init(Properties config) throws ServletException {
this.baseDN = config.getProperty(BASE_DN);
this.providerUrl = config.getProperty(PROVIDER_URL);
String providerUrl = config.getProperty(PROVIDER_URL);
if (providerUrl == null) {
throw new NullPointerException("The LDAP URI can not be null");
}
this.ldapUrls = Arrays.asList(providerUrl.split(",")).iterator();
this.ldapDomain = config.getProperty(LDAP_BIND_DOMAIN);
this.enableStartTls =
Boolean.valueOf(config.getProperty(ENABLE_START_TLS, "false"));
this.numAttempts =
Integer.parseInt(config.getProperty(NUM_ATTEMPTS, NUM_ATTEMPTS_DEFAULT));

if (this.providerUrl == null) {
throw new NullPointerException("The LDAP URI can not be null");
}
if (!((this.baseDN == null)
^ (this.ldapDomain == null))) {
throw new IllegalArgumentException(
"Either LDAP base DN or LDAP domain value needs to be specified");
}
if (this.enableStartTls) {
String tmp = this.providerUrl.toLowerCase();
if (tmp.startsWith("ldaps")) {
throw new IllegalArgumentException(
"Can not use ldaps and StartTLS option at the same time");
while (ldapUrls.hasNext()) {
String tmp = ldapUrls.next().toLowerCase();
if (tmp.startsWith("ldaps")) {
throw new IllegalArgumentException(
"Can not use ldaps and StartTLS option at the same time");
}
}

}
}

Expand Down Expand Up @@ -234,22 +250,33 @@ private AuthenticationToken authenticateUser(String userName,
bindDN = "uid=" + userName + "," + baseDN;
}

if (this.enableStartTls) {
authenticateWithTlsExtension(bindDN, password);
} else {
authenticateWithoutTlsExtension(bindDN, password);
while (ldapUrls.hasNext()){
String currentLdapUrl = ldapUrls.next();
for (int attempt = 1; attempt <= numAttempts; attempt++) {
try {
if (this.enableStartTls) {
authenticateWithTlsExtension(bindDN, password, currentLdapUrl);
} else {
authenticateWithoutTlsExtension(bindDN, password, currentLdapUrl);
}
return new AuthenticationToken(userName, userName, TYPE);
} catch (AuthenticationException e) {
logger.warn("Failed to authenticate for user {} (attempt={}/{}) using {}. " +
"Exception: ", bindDN, attempt, numAttempts, currentLdapUrl, e);
}
}
}

return new AuthenticationToken(userName, userName, TYPE);
return null;
}

private void authenticateWithTlsExtension(String userDN, String password)
throws AuthenticationException {
private void authenticateWithTlsExtension(String userDN, String password,
String ldapUrl) throws AuthenticationException {
LdapContext ctx = null;
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, providerUrl);
env.put(Context.PROVIDER_URL, ldapUrl);

try {
// Create initial context
Expand Down Expand Up @@ -290,12 +317,12 @@ public boolean verify(String hostname, SSLSession session) {
}
}

private void authenticateWithoutTlsExtension(String userDN, String password)
throws AuthenticationException {
private void authenticateWithoutTlsExtension(String userDN, String password,
String ldapUrl) throws AuthenticationException {
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, providerUrl);
env.put(Context.PROVIDER_URL, ldapUrl);
env.put(Context.SECURITY_AUTHENTICATION, SECURITY_AUTHENTICATION);
env.put(Context.SECURITY_PRINCIPAL, userDN);
env.put(Context.SECURITY_CREDENTIALS, password);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,32 @@ public void testRequestWithWrongCredentials() throws Exception {
when(request.getHeader(HttpConstants.AUTHORIZATION_HEADER))
.thenReturn(authHeader);

try {
handler.authenticate(request, response);
fail();
} catch (AuthenticationException ex) {
// Expected
} catch (Exception ex) {
fail();
}
assertNull(handler.authenticate(request, response));
}

@Test
@Timeout(value = 60, unit = TimeUnit.SECONDS)
public void testMultiLdapUrl() throws Exception {
LdapAuthenticationHandler handler1 = new LdapAuthenticationHandler();
Properties p1 = new Properties();
p1.setProperty(BASE_DN, LDAP_BASE_DN);
p1.setProperty(PROVIDER_URL, String.format("ldap://errorLdap:389,ldap://%s:%s",
LDAP_SERVER_ADDR, getLdapServer().getPort()));
handler1.init(p1);

HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);

final Base64 base64 = new Base64(0);
String credentials = base64.encodeToString("bjones:p@ssw0rd".getBytes());
String authHeader = HttpConstants.BASIC + " " + credentials;
when(request.getHeader(HttpConstants.AUTHORIZATION_HEADER))
.thenReturn(authHeader);
AuthenticationToken token = handler1.authenticate(request, response);
assertNotNull(token);
verify(response).setStatus(HttpServletResponse.SC_OK);
assertEquals(TYPE, token.getType());
assertEquals("bjones", token.getUserName());
assertEquals("bjones", token.getName());
}
}