/* See LICENSE for licensing and NOTICE for copyright. */ package org.ldaptive.auth; import java.util.Arrays; import org.ldaptive.Credential; import org.ldaptive.LdapEntry; import org.ldaptive.LdapException; import org.ldaptive.LdapUtils; import org.ldaptive.ReturnAttributes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides functionality to authenticate users against an ldap directory. * * @author Middleware Services */ public class Authenticator { /** NoOp entry resolver. */ private static final EntryResolver NOOP_RESOLVER = new NoOpEntryResolver(); /** Logger for this class. */ protected final Logger logger = LoggerFactory.getLogger(getClass()); /** For finding user DNs. */ private DnResolver dnResolver; /** Handler to handle authentication. */ private AuthenticationHandler authenticationHandler; /** For finding user entries. */ private EntryResolver entryResolver; /** User attributes to return. Concatenated to {@link AuthenticationRequest#getReturnAttributes()}. */ private String[] returnAttributes; /** Handlers to handle authentication responses. */ private AuthenticationResponseHandler[] authenticationResponseHandlers; /** Whether to execute the entry resolver on authentication failure. */ private boolean resolveEntryOnFailure; /** Default constructor. */ public Authenticator() {} /** * Creates a new authenticator. * * @param resolver dn resolver * @param handler authentication handler */ public Authenticator(final DnResolver resolver, final AuthenticationHandler handler) { setDnResolver(resolver); setAuthenticationHandler(handler); } /** * Returns the DN resolver. * * @return DN resolver */ public DnResolver getDnResolver() { return dnResolver; } /** * Sets the DN resolver. * * @param resolver for finding DNs */ public void setDnResolver(final DnResolver resolver) { dnResolver = resolver; } /** * Returns the authentication handler. * * @return authentication handler */ public AuthenticationHandler getAuthenticationHandler() { return authenticationHandler; } /** * Sets the authentication handler. * * @param handler for performing authentication */ public void setAuthenticationHandler(final AuthenticationHandler handler) { authenticationHandler = handler; } /** * Returns the entry resolver. * * @return entry resolver */ public EntryResolver getEntryResolver() { return entryResolver; } /** * Sets the entry resolver. * * @param resolver for finding entries */ public void setEntryResolver(final EntryResolver resolver) { entryResolver = resolver; } /** * Returns whether to execute the entry resolver on authentication failure. * * @return whether to execute the entry resolver on authentication failure */ public boolean getResolveEntryOnFailure() { return resolveEntryOnFailure; } /** * Sets whether to execute the entry resolver on authentication failure. * * @param b whether to execute the entry resolver */ public void setResolveEntryOnFailure(final boolean b) { resolveEntryOnFailure = b; } /** * Returns the return attributes. * * @return attributes to return */ public String[] getReturnAttributes() { return returnAttributes; } /** * Sets the return attributes. * * @param attrs return attributes */ public void setReturnAttributes(final String... attrs) { returnAttributes = attrs; } /** * Returns the authentication response handlers. * * @return authentication response handlers */ public AuthenticationResponseHandler[] getAuthenticationResponseHandlers() { return authenticationResponseHandlers; } /** * Sets the authentication response handlers. * * @param handlers authentication response handlers */ public void setAuthenticationResponseHandlers(final AuthenticationResponseHandler... handlers) { authenticationResponseHandlers = handlers; } /** * This will attempt to find the DN for the supplied user. {@link DnResolver#resolve(User)} is invoked to perform this * operation. * * @param user to find DN for * * @return user DN * * @throws LdapException if an LDAP error occurs during resolution */ public String resolveDn(final User user) throws LdapException { return dnResolver.resolve(user); } /** * Authenticate the user in the supplied request. * * @param request authentication request * * @return response containing the ldap entry of the user authenticated * * @throws LdapException if an LDAP error occurs */ public AuthenticationResponse authenticate(final AuthenticationRequest request) throws LdapException { return authenticate(resolveDn(request.getUser()), request); } /** * Validates input and performs authentication using an {@link AuthenticationHandler}. Executes any configured {@link * AuthenticationResponseHandler}. * * @param dn to authenticate as * @param request containing authentication parameters * * @return ldap entry for the supplied DN * * @throws LdapException if an LDAP error occurs */ protected AuthenticationResponse authenticate(final String dn, final AuthenticationRequest request) throws LdapException { logger.debug("authenticate dn={} with request={}", dn, request); final AuthenticationResponse invalidInput = validateInput(dn, request); if (invalidInput != null) { return invalidInput; } LdapEntry entry = null; final AuthenticationRequest processedRequest = processRequest(dn, request); AuthenticationHandlerResponse response = null; try { final AuthenticationCriteria ac = new AuthenticationCriteria(dn, processedRequest); // attempt to authenticate as this dn response = getAuthenticationHandler().authenticate(ac); // resolve the entry entry = resolveEntry(ac, response); } finally { if (response != null && response.getConnection() != null) { response.getConnection().close(); } } logger.info("Authentication {} for dn: {}", response.getResult() ? "succeeded" : "failed", dn); final AuthenticationResponse authResponse = new AuthenticationResponse( response.getResult() ? AuthenticationResultCode.AUTHENTICATION_HANDLER_SUCCESS : AuthenticationResultCode.AUTHENTICATION_HANDLER_FAILURE, response.getResultCode(), dn, entry, response.getMessage(), response.getControls(), response.getMessageId()); // execute authentication response handlers if (getAuthenticationResponseHandlers() != null && getAuthenticationResponseHandlers().length > 0) { for (AuthenticationResponseHandler ah : getAuthenticationResponseHandlers()) { ah.handle(authResponse); } } logger.debug("authenticate response={} for dn={} with request={}", response, dn, processedRequest); return authResponse; } /** * Validates the authentication request and resolved DN. Returns an authentication response if validation failed. * * @param dn to validate * @param request to validate * * @return authentication response if validation failed, otherwise null */ protected AuthenticationResponse validateInput(final String dn, final AuthenticationRequest request) { AuthenticationResponse response = null; final Credential credential = request.getCredential(); if (credential == null || credential.getBytes() == null) { response = new AuthenticationResponse( AuthenticationResultCode.INVALID_CREDENTIAL, null, dn, null, "Credential cannot be null", null, -1); } else if (credential.getBytes().length == 0) { response = new AuthenticationResponse( AuthenticationResultCode.INVALID_CREDENTIAL, null, dn, null, "Credential cannot be empty", null, -1); } else if (dn == null) { response = new AuthenticationResponse( AuthenticationResultCode.DN_RESOLUTION_FAILURE, null, dn, null, "DN cannot be null", null, -1); } else if (dn.isEmpty()) { response = new AuthenticationResponse( AuthenticationResultCode.DN_RESOLUTION_FAILURE, null, dn, null, "DN cannot be empty", null, -1); } return response; } /** * Creates a new authentication request applying any applicable configuration on this authenticator. Returns the * supplied request if no configuration is applied. * * @param dn to process * @param request to process * * @return authentication request */ protected AuthenticationRequest processRequest(final String dn, final AuthenticationRequest request) { if (returnAttributes == null) { return request; } final AuthenticationRequest newRequest = AuthenticationRequest.newAuthenticationRequest(request); newRequest.setReturnAttributes(LdapUtils.concatArrays(newRequest.getReturnAttributes(), returnAttributes)); return newRequest; } /** * Attempts to find the ldap entry for the supplied DN. If an entry resolver has been configured it is used. A {@link * SearchEntryResolver} is used if return attributes have been requested. If none of these criteria is met, a {@link * NoOpDnResolver} is used. * * @param criteria needed by the entry resolver * @param response from the authentication handler * * @return ldap entry * * @throws LdapException if an error occurs resolving the entry */ protected LdapEntry resolveEntry(final AuthenticationCriteria criteria, final AuthenticationHandlerResponse response) throws LdapException { LdapEntry entry = null; EntryResolver er; if (resolveEntryOnFailure || response.getResult()) { if (entryResolver != null) { er = entryResolver; } else if (!ReturnAttributes.NONE.equalsAttributes(criteria.getAuthenticationRequest().getReturnAttributes())) { if (dnResolver instanceof AggregateDnResolver) { er = ((AggregateDnResolver) dnResolver).createEntryResolver(new SearchEntryResolver()); } else { er = new SearchEntryResolver(); } } else { er = NOOP_RESOLVER; } try { entry = er.resolve(criteria, response); logger.trace("resolved entry={} with resolver={}", entry, er); } catch (LdapException e) { logger.debug("entry resolution failed for resolver={}", er, e); } } if (entry == null) { entry = NOOP_RESOLVER.resolve(criteria, response); logger.trace("resolved entry={} with resolver={}", entry, NOOP_RESOLVER); } return entry; } @Override public String toString() { return String.format( "[%s@%d::dnResolver=%s, authenticationHandler=%s, entryResolver=%s, returnAttributes=%s, " + "authenticationResponseHandlers=%s]", getClass().getName(), hashCode(), getDnResolver(), getAuthenticationHandler(), getEntryResolver(), Arrays.toString(getReturnAttributes()), Arrays.toString(getAuthenticationResponseHandlers())); } }