/**
 * $RCSfile$
 * $Revision: 2814 $
 * $Date: 2005-09-13 16:41:10 -0300 (Tue, 13 Sep 2005) $
 *
 * Copyright (C) 2004 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package org.jivesoftware.wildfire.auth;

import org.jivesoftware.util.*;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.wildfire.user.UserNotFoundException;
import org.jivesoftware.wildfire.user.UserManager;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Authentication service.
 *
 * Users of Jive that wish to change the AuthProvider implementation used to authenticate users
 * can set the <code>AuthProvider.className</code> Jive property. For example, if
 * you have altered Jive to use LDAP for user information, you'd want to send a custom
 * implementation of AuthFactory to make LDAP authToken queries. After changing the
 * <code>AuthProvider.className</code> Jive property, you must restart your application
 * server.<p>
 * <p/>
 * The getAuthToken method that takes servlet request and response objects as arguments can be
 * used to implement single sign-on. Additionally, two helper methods are provided for securely
 * encrypting and decrypting login information so that it can be stored as a cookie value to
 * implement auto-login.<p>
 *
 * @author Matt Tucker
 */
public class AuthFactory {

    private static AuthProvider authProvider = null;
    private static MessageDigest digest;

    static {
        // Load an auth provider.
        String className = JiveGlobals.getXMLProperty("provider.auth.className",
                "org.jivesoftware.wildfire.auth.DefaultAuthProvider");
        try {
            Class c = ClassUtils.forName(className);
            authProvider = (AuthProvider)c.newInstance();
        }
        catch (Exception e) {
            Log.error("Error loading auth provider: " + className, e);
            authProvider = new DefaultAuthProvider();
        }
        // Create a message digest instance.
        try {
            digest = MessageDigest.getInstance("SHA");
        }
        catch (NoSuchAlgorithmException e) {
            Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
        }
    }

    /**
     * Returns true if the currently installed {@link AuthProvider} supports authentication
     * using plain-text passwords according to JEP-0078. Plain-text authentication is
     * not secure and should generally only be used over a TLS/SSL connection.
     *
     * @return true if plain text password authentication is supported.
     */
    public static boolean isPlainSupported() {
        return authProvider.isPlainSupported();
    }

    /**
     * Returns true if the currently installed {@link AuthProvider} supports
     * digest authentication according to JEP-0078.
     *
     * @return true if digest authentication is supported.
     */
    public static boolean isDigestSupported() {
        return authProvider.isDigestSupported();
    }

    /**
     * Returns the user's password. This method will throw an UnsupportedOperationException
     * if this operation is not supported by the backend user store.
     *
     * @param username the username of the user.
     * @return the user's password.
     * @throws UserNotFoundException if the given user could not be found.
     * @throws UnsupportedOperationException if the provider does not
     *      support the operation (this is an optional operation).
     */
    public static String getPassword(String username) throws UserNotFoundException,
            UnsupportedOperationException {
        return UserManager.getUserProvider().getPassword(username);
    }

    /**
     * Authenticates a user with a username and plain text password and returns and
     * AuthToken. If the username and password do not match the record of
     * any user in the system, this method throws an UnauthorizedException.
     *
     * @param username the username.
     * @param password the password.
     * @return an AuthToken token if the username and password are correct.
     * @throws UnauthorizedException if the username and password do not match any existing user.
     */
    public static AuthToken authenticate(String username, String password)
            throws UnauthorizedException
    {
        authProvider.authenticate(username, password);
        return new AuthToken(username);
    }

    /**
     * Authenticates a user with a username, token, and digest and returns an AuthToken.
     * The digest should be generated using the {@link #createDigest(String, String)} method.
     * If the username and digest do not match the record of any user in the system, the
     * method throws an UnauthorizedException.
     *
     * @param username the username.
     * @param token the token that was used with plain-text password to generate the digest.
     * @param digest the digest generated from plain-text password and unique token.
     * @return an AuthToken token if the username and digest are correct for the user's
     *      password and given token.
     * @throws UnauthorizedException if the username and password do not match any
     *      existing user.
     */
    public static AuthToken authenticate(String username, String token, String digest)
            throws UnauthorizedException
    {
        authProvider.authenticate(username, token, digest);
        return new AuthToken(username);
    }

    /**
     * Returns a digest given a token and password, according to JEP-0078.
     *
     * @param token the token used in the digest.
     * @param password the plain-text password to be digested.
     * @return the digested result as a hex string.
     */
    public static String createDigest(String token, String password) {
        synchronized (digest) {
            digest.update(token.getBytes());
            return StringUtils.encodeHex(digest.digest(password.getBytes()));
        }
    }
}