/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.authenticate;

import java.sql.SQLException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;

/**
 * A stackable authentication method
 * based on the DSpace internal "EPerson" database.
 * See the <code>AuthenticationMethod</code> interface for more details.
 * <p>
 * The <em>username</em> is the E-Person's email address,
 * and and the <em>password</em> (given to the <code>authenticate()</code>
 * method) must match the EPerson password.
 * <p>
 * This is the default method for a new DSpace configuration.
 * If you are implementing a new "explicit" authentication method,
 * use this class as a model.
 * <p>
 * You can use this (or another explicit) method in the stack to
 * implement HTTP Basic Authentication for servlets, by passing the
 * Basic Auth username and password to the <code>AuthenticationManager</code>.
 *
 * @author Larry Stone
 * @version $Revision$
 */
public class PasswordAuthentication
    implements AuthenticationMethod {

    /** log4j category */
    private static Logger log = Logger.getLogger(PasswordAuthentication.class);

    /**
     * Look to see if this email address is allowed to register.
     * <p>
     * The configuration key domain.valid is examined
     * in authentication-password.cfg to see what domains are valid.
     * <p>
     * Example - aber.ac.uk domain : @aber.ac.uk
     * Example - MIT domain and all .ac.uk domains: @mit.edu, .ac.uk
     */
    public boolean canSelfRegister(Context context,
                                   HttpServletRequest request,
                                   String email)
                                                 throws SQLException
    {
        // Is there anything set in domain.valid?
        String domains = ConfigurationManager.getProperty("authentication-password", "domain.valid");
        if ((domains == null) || (domains.trim().equals("")))
        {
            // No conditions set, so must be able to self register
            return true;
        }
        else
        {
            // Itterate through all domains
            String[] options = domains.trim().split(",");
            String check;
            email = email.trim().toLowerCase();
            for (int i = 0; i < options.length; i++)
            {
                check = options[i].trim().toLowerCase();
                if (email.endsWith(check))
                {
                    // A match, so we can register this user
                    return true;
                }
            }
            
            // No match
            return false;
        }    
    }

    /**
     *  Nothing extra to initialize.
     */
    public void initEPerson(Context context, HttpServletRequest request,
            EPerson eperson)
        throws SQLException
    {
    }

    /**
     * We always allow the user to change their password.
     */
    public boolean allowSetPassword(Context context,
                                    HttpServletRequest request,
                                    String username)
        throws SQLException
    {
        return true;
    }

    /**
     * This is an explicit method, since it needs username and password
     * from some source.
     * @return false
     */
    public boolean isImplicit()
    {
        return false;
    }

    /**
     * Add authenticated users to the group defined in authentication-password.cfg by
     * the login.specialgroup key.
     */
    public int[] getSpecialGroups(Context context, HttpServletRequest request)
    {
        // Prevents anonymous users from being added to this group, and the second check
		// ensures they are password users
		try
		{
			if (!context.getCurrentUser().getMetadata("password").equals(""))
			{
				String groupName = ConfigurationManager.getProperty("authentication-password", "login.specialgroup");
				if ((groupName != null) && (!groupName.trim().equals("")))
				{
				    Group specialGroup = Group.findByName(context, groupName);
					if (specialGroup == null)
					{
						// Oops - the group isn't there.
						log.warn(LogManager.getHeader(context,
								"password_specialgroup",
								"Group defined in modules/authentication-password.cfg login.specialgroup does not exist"));
						return new int[0];
					} else
					{
						return new int[] { specialGroup.getID() };
					}
				}
			}
		}
		catch (Exception e) {
			// The user is not a password user, so we don't need to worry about them
		}
		return new int[0];
    }

    /**
     * Check credentials: username must match the email address of an
     * EPerson record, and that EPerson must be allowed to login.
     * Password must match its password.  Also checks for EPerson that
     * is only allowed to login via an implicit method
     * and returns <code>CERT_REQUIRED</code> if that is the case.
     *
     * @param context
     *  DSpace context, will be modified (EPerson set) upon success.
     *
     * @param username
     *  Username (or email address) when method is explicit. Use null for
     *  implicit method.
     *
     * @param password
     *  Password for explicit auth, or null for implicit method.
     *
     * @param realm
     *  Realm is an extra parameter used by some authentication methods, leave null if
     *  not applicable.
     *
     * @param request
     *  The HTTP request that started this operation, or null if not applicable.
     *
     * @return One of:
     *   SUCCESS, BAD_CREDENTIALS, CERT_REQUIRED, NO_SUCH_USER, BAD_ARGS
     * <p>Meaning:
     * <br>SUCCESS         - authenticated OK.
     * <br>BAD_CREDENTIALS - user exists, but assword doesn't match
     * <br>CERT_REQUIRED   - not allowed to login this way without X.509 cert.
     * <br>NO_SUCH_USER    - no EPerson with matching email address.
     * <br>BAD_ARGS        - missing username, or user matched but cannot login.
     */
    public int authenticate(Context context,
                            String username,
                            String password,
                            String realm,
                            HttpServletRequest request)
        throws SQLException
    {
        if (username != null && password != null)
        {
            EPerson eperson = null;
            log.info(LogManager.getHeader(context, "authenticate", "attempting password auth of user="+username));
            try
            {
                eperson = EPerson.findByEmail(context, username.toLowerCase());
            }
            catch (AuthorizeException e)
            {
                log.trace("Failed to authorize looking up EPerson", e);
            }

            if (eperson == null)
            {
                // lookup failed.
                return NO_SUCH_USER;
            }
            else if (!eperson.canLogIn())
            {
                // cannot login this way
                return BAD_ARGS;
            }
            else if (eperson.getRequireCertificate())
            {
                // this user can only login with x.509 certificate
                log.warn(LogManager.getHeader(context, "authenticate", "rejecting PasswordAuthentication because "+username+" requires certificate."));
                return CERT_REQUIRED;
            }
            else if (eperson.checkPassword(password))
            {
                // login is ok if password matches:
                context.setCurrentUser(eperson);
                log.info(LogManager.getHeader(context, "authenticate", "type=PasswordAuthentication"));
                return SUCCESS;
            }
            else
            {
                return BAD_CREDENTIALS;
            }
        }

        // BAD_ARGS always defers to the next authentication method.
        // It means this method cannot use the given credentials.
        else
        {
            return BAD_ARGS;
        }
    }

    /**
     * Returns URL of password-login servlet.
     *
     * @param context
     *  DSpace context, will be modified (EPerson set) upon success.
     *
     * @param request
     *  The HTTP request that started this operation, or null if not applicable.
     *
     * @param response
     *  The HTTP response from the servlet method.
     *
     * @return fully-qualified URL
     */
    public String loginPageURL(Context context,
                            HttpServletRequest request,
                            HttpServletResponse response)
    {
        return response.encodeRedirectURL(request.getContextPath() +
                                          "/password-login");
    }

    /**
     * Returns message key for title of the "login" page, to use
     * in a menu showing the choice of multiple login methods.
     *
     * @param context
     *  DSpace context, will be modified (EPerson set) upon success.
     *
     * @return Message key to look up in i18n message catalog.
     */
    public String loginPageTitle(Context context)
    {
        return "org.dspace.eperson.PasswordAuthentication.title";
    }
}
