/* See LICENSE for licensing and NOTICE for copyright. */ package org.ldaptive.auth; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.ldaptive.LdapEntry; import org.ldaptive.LdapException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Looks up a user's DN using multiple DN resolvers. Each DN resolver is invoked on a separate thread. If multiple DNs * are allowed then the first one retrieved is returned. * * @author Middleware Services */ public class AggregateDnResolver implements DnResolver { /** Logger for this class. */ protected final Logger logger = LoggerFactory.getLogger(getClass()); /** To submit operations to. */ private final ExecutorService service; /** Labeled DN resolvers. */ private Map<String, DnResolver> dnResolvers; /** Whether to throw an exception if multiple DNs are found. */ private boolean allowMultipleDns; /** Default constructor. */ public AggregateDnResolver() { service = Executors.newCachedThreadPool(); } /** * Creates a new aggregate dn resolver. * * @param resolvers dn resolvers */ public AggregateDnResolver(final Map<String, DnResolver> resolvers) { this(resolvers, Executors.newCachedThreadPool()); } /** * Creates a new aggregate dn resolver. * * @param resolvers dn resolvers * @param es executor service for invoking DN resolvers */ public AggregateDnResolver(final Map<String, DnResolver> resolvers, final ExecutorService es) { setDnResolvers(resolvers); service = es; } /** * Returns the DN resolvers to aggregate over. * * @return map of label to dn resolver */ public Map<String, DnResolver> getDnResolvers() { return Collections.unmodifiableMap(dnResolvers); } /** * Sets the DN resolvers to aggregate over. * * @param resolvers to set */ public void setDnResolvers(final Map<String, DnResolver> resolvers) { logger.trace("setting dnResolvers: {}", resolvers); dnResolvers = resolvers; } /** * Returns whether DN resolution should fail if multiple DNs are found. * * @return whether an exception will be thrown if multiple DNs are found */ public boolean getAllowMultipleDns() { return allowMultipleDns; } /** * Sets whether DN resolution should fail if multiple DNs are found If false an exception will be thrown if {@link * #resolve(User)} finds that more than one DN resolver returns a DN. Otherwise the first DN found is returned. * * @param b whether multiple DNs are allowed */ public void setAllowMultipleDns(final boolean b) { logger.trace("setting allowMultipleDns: {}", b); allowMultipleDns = b; } /** * Creates an aggregate entry resolver using the labels from the DN resolver and the supplied entry resolver. * * @param resolver used for every label * * @return aggregate entry resolver */ public EntryResolver createEntryResolver(final org.ldaptive.auth.EntryResolver resolver) { final Map<String, org.ldaptive.auth.EntryResolver> resolvers = new HashMap<>(dnResolvers.size()); for (String label : dnResolvers.keySet()) { resolvers.put(label, resolver); } return new EntryResolver(resolvers); } @Override public String resolve(final User user) throws LdapException { final CompletionService<String> cs = new ExecutorCompletionService<>(service); final List<String> results = new ArrayList<>(dnResolvers.size()); for (final Map.Entry<String, DnResolver> entry : dnResolvers.entrySet()) { cs.submit( () -> { final String dn = entry.getValue().resolve(user); logger.debug("DN resolver {} resolved dn {} for user {}", entry.getValue(), dn, user); if (dn != null && !dn.isEmpty()) { return String.format("%s:%s", entry.getKey(), dn); } return null; }); logger.debug("submitted DN resolver {}", entry.getValue()); } for (int i = 1; i <= dnResolvers.size(); i++) { try { logger.trace("waiting on DN resolver {} of {}", i, dnResolvers.size()); final String dn = cs.take().get(); if (dn != null) { results.add(dn); } } catch (ExecutionException e) { if (e.getCause() instanceof LdapException) { throw (LdapException) e.getCause(); } else if (e.getCause() instanceof RuntimeException) { throw (RuntimeException) e.getCause(); } else { logger.warn("ExecutionException thrown, ignoring", e); } } catch (InterruptedException e) { logger.warn("InterruptedException thrown, ignoring", e); } } if (results.size() > 1 && !allowMultipleDns) { throw new LdapException("Found more than (1) DN for: " + user); } logger.debug("resolved aggregate DN {}", results); return results.isEmpty() ? null : results.get(0); } /** Invokes {@link ExecutorService#shutdown()} on the underlying executor service. */ public void shutdown() { service.shutdown(); } @Override protected void finalize() throws Throwable { try { shutdown(); } finally { super.finalize(); } } /** * Used in conjunction with an {@link AggregateDnResolver} to authenticate the resolved DN. In particular, the * resolved DN is expected to be of the form: label:DN where the label indicates the authentication handler to use. * This class only invokes one authentication handler that matches the label found on the DN. */ public static class AuthenticationHandler implements org.ldaptive.auth.AuthenticationHandler { /** Logger for this class. */ protected final Logger logger = LoggerFactory.getLogger(getClass()); /** Labeled authentication handlers. */ private Map<String, org.ldaptive.auth.AuthenticationHandler> authenticationHandlers; /** Default constructor. */ public AuthenticationHandler() {} /** * Creates a new aggregate authentication handler. * * @param handlers authentication handlers */ public AuthenticationHandler(final Map<String, org.ldaptive.auth.AuthenticationHandler> handlers) { setAuthenticationHandlers(handlers); } /** * Returns the authentication handlers to aggregate over. * * @return map of label to authentication handler */ public Map<String, org.ldaptive.auth.AuthenticationHandler> getAuthenticationHandlers() { return Collections.unmodifiableMap(authenticationHandlers); } /** * Sets the authentication handlers to aggregate over. * * @param handlers to set */ public void setAuthenticationHandlers(final Map<String, org.ldaptive.auth.AuthenticationHandler> handlers) { logger.trace("setting authenticationHandlers: {}", handlers); authenticationHandlers = handlers; } @Override public AuthenticationHandlerResponse authenticate(final AuthenticationCriteria criteria) throws LdapException { final String[] labeledDn = criteria.getDn().split(":", 2); final org.ldaptive.auth.AuthenticationHandler ah = authenticationHandlers.get(labeledDn[0]); if (ah == null) { throw new LdapException("Could not find authentication handler for label: " + labeledDn[0]); } return ah.authenticate(new AuthenticationCriteria(labeledDn[1], criteria.getAuthenticationRequest())); } } /** * Used in conjunction with an {@link AggregateDnResolver} to resolve an entry. In particular, the resolved DN is * expected to be of the form: label:DN where the label indicates the entry resolver to use. This class only invokes * one entry resolver that matches the label found on the DN. */ public static class EntryResolver implements org.ldaptive.auth.EntryResolver { /** Logger for this class. */ protected final Logger logger = LoggerFactory.getLogger(getClass()); /** Labeled entry resolvers. */ private Map<String, org.ldaptive.auth.EntryResolver> entryResolvers; /** Default constructor. */ public EntryResolver() {} /** * Creates a new aggregate entry resolver. * * @param resolvers entry resolvers */ public EntryResolver(final Map<String, org.ldaptive.auth.EntryResolver> resolvers) { setEntryResolvers(resolvers); } /** * Returns the entry resolvers to aggregate over. * * @return map of label to entry resolver */ public Map<String, org.ldaptive.auth.EntryResolver> getEntryResolvers() { return Collections.unmodifiableMap(entryResolvers); } /** * Sets the entry resolvers to aggregate over. * * @param resolvers to set */ public void setEntryResolvers(final Map<String, org.ldaptive.auth.EntryResolver> resolvers) { logger.trace("setting entryResolvers: {}", resolvers); entryResolvers = resolvers; } @Override public LdapEntry resolve(final AuthenticationCriteria criteria, final AuthenticationHandlerResponse response) throws LdapException { final String[] labeledDn = criteria.getDn().split(":", 2); final org.ldaptive.auth.EntryResolver er = entryResolvers.get(labeledDn[0]); if (er == null) { throw new LdapException("Could not find entry resolver for label: " + labeledDn[0]); } return er.resolve(new AuthenticationCriteria(labeledDn[1], criteria.getAuthenticationRequest()), response); } } /** * Used in conjunction with an {@link AggregateDnResolver} to execute a list of response handlers. In particular, the * resolved DN is expected to be of the form: label:DN where the label indicates the response handler to use. This * class only invokes the response handlers that matches the label found on the DN. */ public static class AuthenticationResponseHandler implements org.ldaptive.auth.AuthenticationResponseHandler { /** Logger for this class. */ protected final Logger logger = LoggerFactory.getLogger(getClass()); /** Labeled entry resolvers. */ private Map<String, org.ldaptive.auth.AuthenticationResponseHandler[]> responseHandlers; /** Default constructor. */ public AuthenticationResponseHandler() {} /** * Creates a new aggregate authentication response handler. * * @param handlers authentication response handlers */ public AuthenticationResponseHandler(final Map<String, org.ldaptive.auth.AuthenticationResponseHandler[]> handlers) { setAuthenticationResponseHandlers(handlers); } /** * Returns the response handlers to aggregate over. * * @return map of label to response handlers */ public Map<String, org.ldaptive.auth.AuthenticationResponseHandler[]> getAuthenticationResponseHandlers() { return Collections.unmodifiableMap(responseHandlers); } /** * Sets the response handlers to aggregate over. * * @param handlers to set */ public void setAuthenticationResponseHandlers( final Map<String, org.ldaptive.auth.AuthenticationResponseHandler[]> handlers) { logger.trace("setting authenticationResponseHandlers: {}", handlers); responseHandlers = handlers; } @Override public void handle(final AuthenticationResponse response) throws LdapException { final String[] labeledDn = response.getResolvedDn().split(":", 2); final org.ldaptive.auth.AuthenticationResponseHandler[] handlers = responseHandlers.get(labeledDn[0]); if (handlers == null) { throw new LdapException("Could not find response handlers for label: " + labeledDn[0]); } if (handlers.length > 0) { for (org.ldaptive.auth.AuthenticationResponseHandler ah : handlers) { ah.handle(response); } } } } }