001package co.codewizards.cloudstore.rest.server.ldap; 002 003import static java.util.Objects.*; 004 005import java.util.ArrayList; 006import java.util.HashMap; 007import java.util.List; 008import java.util.Map; 009 010import javax.naming.AuthenticationException; 011import javax.naming.Context; 012import javax.naming.NamingEnumeration; 013import javax.naming.NamingException; 014import javax.naming.directory.DirContext; 015import javax.naming.directory.InitialDirContext; 016import javax.naming.directory.SearchControls; 017import javax.naming.directory.SearchResult; 018 019import co.codewizards.cloudstore.core.util.IOUtil; 020import co.codewizards.cloudstore.rest.server.auth.Auth; 021import co.codewizards.cloudstore.rest.server.auth.NotAuthorizedException; 022/** 023 * Authentication flow used by this client: 024 * At first DirContext is created, based on provided url, adminDn and adminPassword. 025 * Then on this DirContext instance search is executed for given LDAP query and queryDN. 026 * 027 * Search has SUBTREE_SCOPE. 028 * @see <a href="http://docs.oracle.com/javase/7/docs/api/javax/naming/directory/SearchControls.html#SUBTREE_SCOPE">SUBTREE_SCOPE</a> 029 * 030 * Query should contain template variable ${login}(one or more) which is replaced with userName from provided Auth object 031 * 032 * If search returns any results then credentials of DirContext are replaced with result's DN (as PRINCIPAL) 033 * and password from provided Auth (as CREDENTIALS), and lookup() is called. 034 * 035 * If lookup() doesn't throw AuthenticationException then authentication succeeded. 036 * @author Wojtek Wilk - wilk.wojtek at gmail.com 037 */ 038public class QueryLdapClient implements LdapClient{ 039 040 private static final String TEMPLATE_VARIABLE = "login"; 041 042 private final String query; 043 private final String queryDn; 044 private final String url; 045 private final String adminDn; 046 private final char[] adminPassword; 047 048 public QueryLdapClient(String query, String queryDn, String url, String bindDn, char[] password) { 049 this.query = requireNonNull(query, "query"); 050 this.queryDn = requireNonNull(queryDn, "queryDn"); 051 this.url = requireNonNull(url, "url"); 052 this.adminDn = requireNonNull(bindDn, "bindDn"); 053 this.adminPassword = requireNonNull(password, "password"); 054 } 055 056 @Override 057 public String authenticate(final Auth auth) { 058 try{ 059 final LdapConfig config = new LdapConfig(url, adminDn, adminPassword); 060 final DirContext context = new InitialDirContext(config); 061 062 List<String> usersDns = findAllUsersThatMatchQuery(context, auth); 063 064 for(String userDn : usersDns) 065 if(tryAuthenticate(context, userDn, auth.getPassword())) 066 return auth.getUserName(); 067 }catch(NamingException e){ 068 throw new RuntimeException(e); 069 } 070 throw new NotAuthorizedException(); 071 } 072 073 private List<String> findAllUsersThatMatchQuery(final DirContext context, final Auth auth) throws NamingException{ 074 final NamingEnumeration<SearchResult> results = findUsersWithQuery(context, auth.getUserName()); 075 List<String> usersDns = new ArrayList<>(); 076 while(results.hasMore()) 077 usersDns.add(results.next().getNameInNamespace()); 078 return usersDns; 079 } 080 081 private NamingEnumeration<SearchResult> findUsersWithQuery(DirContext context, String userName) throws NamingException{ 082 final SearchControls searchControls = new SearchControls(); 083 searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); 084 final String replacedQuery = convertTemplate(query, userName); 085 return context.search(queryDn, replacedQuery, searchControls); 086 } 087 088 private boolean tryAuthenticate(final DirContext context, final String userName, final char[] password) throws NamingException{ 089 try{ 090 context.addToEnvironment(Context.SECURITY_PRINCIPAL, userName); 091 context.addToEnvironment(Context.SECURITY_CREDENTIALS, password); 092 context.lookup(userName); 093 return true; 094 } catch(AuthenticationException e){ 095 return false; 096 } 097 } 098 099 private String convertTemplate(final String template, final String username){ 100 final Map<String, String> map = new HashMap<String, String>(1); 101 map.put(TEMPLATE_VARIABLE, username); 102 return IOUtil.replaceTemplateVariables(template, map); 103 } 104}