/**
 *
 */
package org.springframework.security.ui.ntlm.ldap.authenticator;

import org.springframework.security.Authentication;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.ldap.SpringSecurityContextSource;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.providers.ldap.authenticator.BindAuthenticator;
import org.springframework.security.ui.ntlm.NtlmUsernamePasswordAuthenticationToken;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.core.DirContextOperations;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.Iterator;

/**
 * Loads the UserDetails if authentication was already performed by NTLM (indicated by the type of authentication
 * token submitted). Otherwise falls back to the parent class behaviour, attempting to bind as the user.
 *
 * @author sylvain.mougenot
 *
 */
public class NtlmAwareLdapAuthenticator extends BindAuthenticator {
    //~ Static fields/initializers =====================================================================================

    private static final Log logger = LogFactory.getLog(NtlmAwareLdapAuthenticator.class);


    //~ Constructors ===================================================================================================

	public NtlmAwareLdapAuthenticator(SpringSecurityContextSource contextSource) {
		super(contextSource);
	}

    //~ Methods ========================================================================================================

    /**
     * Loads the user context information without binding.
	 */
	protected DirContextOperations loadUser(String aUserDn, String aUserName) {
		SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(getContextSource());

		try {
			DirContextOperations user =  template.retrieveEntry(aUserDn, getUserAttributes());

			return user;
		} catch (NameNotFoundException e) {
			// This will be thrown if an invalid user name is used and the method may
			// be called multiple times to try different names, so we trap the exception.
			if (logger.isDebugEnabled()) {
				logger.debug("Failed to load user " + aUserDn + ": " + e.getMessage(), e);
			}
		}
		return null;
	}

	/**
	 * If the supplied <tt>Authentication</tt> object is of type <tt>NtlmUsernamePasswordAuthenticationToken</tt>,
     * the information stored in the user's directory entry is loaded without attempting to authenticate them.
     * Otherwise the parent class is called to perform a bind operation to authenticate the user.
	 */
	public DirContextOperations authenticate(Authentication authentication) {
        if (!(authentication instanceof NtlmUsernamePasswordAuthenticationToken)) {
            // Not NTLM authenticated, so call the base class to authenticate the user.
            return super.authenticate(authentication);
        }

        if (!authentication.isAuthenticated()) {
            throw new BadCredentialsException("Unauthenticated NTLM authentication token found");
        }

        if (logger.isDebugEnabled()) {
			logger.debug("authenticate(NtlmUsernamePasswordAuthenticationToken) - start"); //$NON-NLS-1$
		}

		final String userName = authentication.getName();
		DirContextOperations user = null;

		// If DN patterns are configured, try authenticating with them directly
		Iterator myDns = getUserDns(userName).iterator();

		// tries them all until we found something
		while (myDns.hasNext() && (user == null)) {
			user = loadUser((String) myDns.next(), userName);
		}

		// Otherwise use the configured locator to find the user
		// and authenticate with the returned DN.
		if ((user == null) && (getUserSearch() != null)) {
			DirContextOperations userFromSearch = getUserSearch().searchForUser(userName);
			// lancer l'identificvation
			user = loadUser(userFromSearch.getDn().toString(), userName);
		}

		// Failed to locate the user in the LDAP directory
		if (user == null) {
			throw new BadCredentialsException(messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
		}

		if (logger.isDebugEnabled()) {
			logger.debug("authenticate(NtlmUsernamePasswordAuthenticationToken) - end"); //$NON-NLS-1$
		}
		return user;
	}
}
