package co.codewizards.cloudstore.rest.server.ldap;
import static co.codewizards.cloudstore.core.util.AssertUtil.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import co.codewizards.cloudstore.core.util.IOUtil;
import co.codewizards.cloudstore.rest.server.auth.Auth;
import co.codewizards.cloudstore.rest.server.auth.NotAuthorizedException;
/**
* Authentication flow used by this client:
* At first DirContext is created, based on provided url, adminDn and adminPassword.
* Then on this DirContext instance search is executed for given LDAP query and queryDN.
*
* Search has SUBTREE_SCOPE.
* @see <a href="http://docs.oracle.com/javase/7/docs/api/javax/naming/directory/SearchControls.html#SUBTREE_SCOPE">SUBTREE_SCOPE</a>
*
* Query should contain template variable ${login}(one or more) which is replaced with userName from provided Auth object
*
* If search returns any results then credentials of DirContext are replaced with result's DN (as PRINCIPAL)
* and password from provided Auth (as CREDENTIALS), and lookup() is called.
*
* If lookup() doesn't throw AuthenticationException then authentication succeeded.
* @author Wojtek Wilk - wilk.wojtek at gmail.com
*/
public class QueryLdapClient implements LdapClient{
private static final String TEMPLATE_VARIABLE = "login";
private final String query;
private final String queryDn;
private final String url;
private final String adminDn;
private final char[] adminPassword;
public QueryLdapClient(String query, String queryDn, String url, String bindDn, char[] password) {
this.query = assertNotNull(query, "query");
this.queryDn = assertNotNull(queryDn, "queryDn");
this.url = assertNotNull(url, "url");
this.adminDn = assertNotNull(bindDn, "bindDn");
this.adminPassword = assertNotNull(password, "password");
}
@Override
public String authenticate(final Auth auth) {
try{
final LdapConfig config = new LdapConfig(url, adminDn, adminPassword);
final DirContext context = new InitialDirContext(config);
List<String> usersDns = findAllUsersThatMatchQuery(context, auth);
for(String userDn : usersDns)
if(tryAuthenticate(context, userDn, auth.getPassword()))
return auth.getUserName();
}catch(NamingException e){
throw new RuntimeException(e);
}
throw new NotAuthorizedException();
}
private List<String> findAllUsersThatMatchQuery(final DirContext context, final Auth auth) throws NamingException{
final NamingEnumeration<SearchResult> results = findUsersWithQuery(context, auth.getUserName());
List<String> usersDns = new ArrayList<>();
while(results.hasMore())
usersDns.add(results.next().getNameInNamespace());
return usersDns;
}
private NamingEnumeration<SearchResult> findUsersWithQuery(DirContext context, String userName) throws NamingException{
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
final String replacedQuery = convertTemplate(query, userName);
return context.search(queryDn, replacedQuery, searchControls);
}
private boolean tryAuthenticate(final DirContext context, final String userName, final char[] password) throws NamingException{
try{
context.addToEnvironment(Context.SECURITY_PRINCIPAL, userName);
context.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
context.lookup(userName);
return true;
} catch(AuthenticationException e){
return false;
}
}
private String convertTemplate(final String template, final String username){
final Map<String, String> map = new HashMap<String, String>(1);
map.put(TEMPLATE_VARIABLE, username);
return IOUtil.replaceTemplateVariables(template, map);
}
}