/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.auth.ldap;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ldap.AuthenticationException;
import org.springframework.ldap.CommunicationException;
import org.springframework.ldap.InvalidNameException;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.NameClassPairCallbackHandler;
import org.springframework.ldap.core.SearchExecutor;
import com.emc.storageos.auth.impl.LdapFailureHandler;
import com.emc.storageos.auth.impl.LdapOrADServer;
import com.emc.storageos.auth.impl.LdapServerList;
import com.emc.storageos.auth.StorageOSAuthenticationHandler;
import com.emc.storageos.services.util.AlertsLogger;
import com.emc.storageos.svcs.errorhandling.resources.UnauthorizedException;
/**
* Authentication handler for LDAP providers
*/
public class StorageOSLdapAuthenticationHandler implements
StorageOSAuthenticationHandler {
private Logger _log = LoggerFactory.getLogger(StorageOSLdapAuthenticationHandler.class);
private AlertsLogger _alertLog = AlertsLogger.getAlertsLogger();
private Set<String> _domains;
private String _rawFilter;
private String _searchBase;
private int _scope = SearchControls.SUBTREE_SCOPE;
private int _timeLimit = 1000;
private long _countLimit = 1000;
private LdapFailureHandler _failureHandler = new LdapFailureHandler();
private LdapServerList _ldapServers;
public StorageOSLdapAuthenticationHandler() {
super();
}
/*
* @see com.emc.storageos.auth.StorageOSAuthenticationHandler#authenticate(org.apache.commons.httpclient.Credentials)
*/
@Override
public boolean authenticate(final Credentials credentials) {
UsernamePasswordCredentials usernamePasswordCredentials = (UsernamePasswordCredentials) credentials;
if (null == usernamePasswordCredentials.getUserName()
|| usernamePasswordCredentials.getUserName().isEmpty()
|| null == usernamePasswordCredentials.getPassword()
|| usernamePasswordCredentials.getPassword().isEmpty()) {
_log.error("Illegal credentials username or password cannot be null or empty");
return false;
}
return doAuthentication(usernamePasswordCredentials);
}
/**
* Do authentication to ad/ldap. Will try all context sources building from URLs, in input order.
* If any one has connection failure, move that to the end and try next one until all.
* @param credentials
* @return
*/
private boolean doAuthentication(UsernamePasswordCredentials credentials) {
List<LdapOrADServer> connectedServers = _ldapServers.getConnectedServers();
for (LdapOrADServer server : connectedServers) {
try {
return doAuthenticationOverSingleServer(server, credentials);
} catch (CommunicationException e) { // Continue only if communicate issue happens on last server
_failureHandler.handle(_ldapServers, server);
_alertLog.error(MessageFormat.format("Connection to LDAP server {0} failed for domain(s) {1}. {2}",
Arrays.toString(server.getContextSource().getUrls()), _domains, e.getMessage()));
}
}
throw UnauthorizedException.unauthorized.ldapCommunicationException();
}
private boolean doAuthenticationOverSingleServer(LdapOrADServer server, UsernamePasswordCredentials usernamePasswordCredentials) {
_log.info("Do authentication to the server {}", server.getContextSource().getUrls()[0]);
String password = usernamePasswordCredentials.getPassword();
List<String> dns = new ArrayList<String>();
final String filter = LdapFilterUtil.getPersonFilterWithValues(_rawFilter, usernamePasswordCredentials.getUserName());
_log.debug("Filter for authentication is {}", filter);
LdapTemplate ldapTemplate = new LdapTemplate(server.getContextSource());
ldapTemplate.setIgnorePartialResultException(true); // To avoid the exceptions due to referrals returned
try {
ldapTemplate.search(new StorageOSSearchExecutor(filter), new StorageOSNameClassPairCallbackHandler(dns));
} catch (CommunicationException e) {
_log.warn("Connection to LDAP server {} failed", Arrays.toString(server.getContextSource().getUrls()));
throw e;
} catch (AuthenticationException e) {
_alertLog
.error(MessageFormat
.format("Manager bind failed during search for user {0} in domain(s) {1}. Check manager DN and password. {2}. "
+
"Note that any change to the manager DN username or password in the authentication provider must be manually changed in ViPR.",
usernamePasswordCredentials.getUserName(), _domains, e.getMessage()));
throw UnauthorizedException.unauthorized.managerBindFailed();
} catch (InvalidNameException e) {
_alertLog.error(MessageFormat.format(
"Search failed because the search path provided is syntactically invalid for user {0}. {1}",
usernamePasswordCredentials.getUserName(), e.getMessage()));
throw UnauthorizedException.unauthorized.userSearchFailed();
} catch (Exception e) {
_alertLog.error(MessageFormat.format(
"Search or bind failed. An exception was thrown while trying to authenticate user {0}. {1}",
usernamePasswordCredentials.getUserName(), e.getMessage()));
throw UnauthorizedException.unauthorized.bindSearchGenericException();
}
if (dns.isEmpty()) {
_log.info("Search for " + filter + " returned 0 results.");
return false;
}
if (dns.size() > 1) {
_log.warn("Search for " + filter + " returned multiple results, which is not allowed.");
return false;
}
try {
DirContext test = server.getContextSource().getContext(dns.get(0), password);
if (test != null) {
try {
test.close();
} catch (NamingException e) {
_log.error("Failed to close test context", e);
}
_log.info("Authenticate user {} against server {} successfully", usernamePasswordCredentials.getUserName(), server.getContextSource().getUrls()[0]);
return true;
}
} catch (AuthenticationException e) {
_log.warn("Failed to authenticate user {}", usernamePasswordCredentials.getUserName());
return false;
} catch (CommunicationException e) {
_alertLog.error(MessageFormat.format("Connection to LDAP server {0} failed for domain(s) {1}. {2}",
Arrays.toString(server.getContextSource().getUrls()), _domains, e.getMessage()));
throw e;
} catch (Exception e) {
_alertLog.error(MessageFormat.format("Second bind failed. An exception was thrown while trying to authenticate user {0}. {1}",
usernamePasswordCredentials.getUserName(), e.getMessage()));
throw UnauthorizedException.unauthorized.bindSearchGenericException();
}
return false;
}
/*
* @see com.emc.storageos.auth.StorageOSAuthenticationHandler#supports(org.apache.commons.httpclient.Credentials)
*/
@Override
public boolean supports(final Credentials credentials) {
if (null != credentials && credentials.getClass().isAssignableFrom(UsernamePasswordCredentials.class)) {
String username = ((UsernamePasswordCredentials) credentials).getUserName();
if (null != username) {
String[] usernameParts = username.split("@");
return usernameParts.length > 1 && _domains.contains(usernameParts[1].toLowerCase());
}
}
return false;
}
/**
* @see com.emc.storageos.auth.StorageOSAuthenticationHandler#setFailureHandler(LdapFailureHandler)
* @param failureHandler
*/
@Override
public void setFailureHandler(LdapFailureHandler failureHandler) {
this._failureHandler = failureHandler;
}
private SearchControls getSearchControls() {
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(_scope);
searchControls.setReturningAttributes(new String[0]);
searchControls.setTimeLimit(_timeLimit);
searchControls.setCountLimit(_countLimit);
return searchControls;
}
public void setDomains(final Set<String> stringSet) {
_domains = stringSet;
}
public Set<String> getDomains() {
return _domains;
}
public void setFilter(final String filter) {
_rawFilter = filter;
}
public void setSearchBase(final String searchBase) {
_searchBase = searchBase;
}
public void setLdapServers(LdapServerList ldapServers) {
_ldapServers = ldapServers;
}
public LdapServerList getLdapServers() {
return _ldapServers;
}
private class StorageOSSearchExecutor implements SearchExecutor {
private String _filter;
public StorageOSSearchExecutor(String filter) {
_filter = filter;
}
@Override
public NamingEnumeration<SearchResult> executeSearch(DirContext context) throws NamingException {
return context.search(_searchBase, _filter, getSearchControls());
}
}
private class StorageOSNameClassPairCallbackHandler implements NameClassPairCallbackHandler {
private List<String> _dns;
public StorageOSNameClassPairCallbackHandler(List<String> dns) {
super();
_dns = dns;
}
@Override
public void handleNameClassPair(NameClassPair nameClassPair) {
_dns.add(nameClassPair.getNameInNamespace());
}
}
}