/*
 * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the
 * License at http://www.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.kernel.security;

import java.math.BigDecimal;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import org.apache.log4j.Logger;

/**
 * Logs in a user for password recovery if the user has a valid
 * authentication URL parameter.
 *
 * @author Sameer Ajmani
 **/
public class RecoveryLoginModule extends UserLoginModule {

    public static final String versionId = "$Id: //core-platform/dev/src/com/arsdigita/kernel/security/RecoveryLoginModule.java#10 $ by $Author: dennis $, $DateTime: 2004/04/07 16:07:11 $";
    private static final Logger s_log =
        Logger.getLogger(RecoveryLoginModule.class.getName());

    private static final String URL_PARAM_NAME = "ad_user_recover";

    // fields set by initialize()
    private Subject m_subject;
    private CallbackHandler m_handler;
    private Map m_shared;
    private Map m_options;

    /**
     * Creates a UserLoginModule that uses a URLManager to manage the
     * credential value.  Overrides <code>URLManager.shouldSetValue()</code>
     * to return <code>false</code> so that the recover credential is never
     * propagated to future requests.
     **/
    public RecoveryLoginModule() {
        super(new URLManager(java.util.Collections.EMPTY_SET) {
                /**
                 * Ensures setValue() is never called: the recovery credential
                 * should not be propagated to other pages.
                 *
                 * @return false
                 **/
                protected boolean shouldSetValue(String value)
                    throws LoginException {
                    return false;
                }
            });
    }

    // implements LoginModule
    public void initialize(Subject subject,
                           CallbackHandler handler,
                           Map shared,
                           Map options) {
        super.initialize(subject, handler, shared, options);
        m_subject = subject;
        m_handler = handler;
        m_shared  = shared;
        m_options = options;
    }

    /**
     * If this login module succeeded, sets the Subject as "recovering".
     * Ignores the results of other login modules.
     *
     * @return <code>super.commit()</code>.
     *
     * @throws LoginException if an error occurs.
     **/
    public boolean commit()
        throws LoginException {
        if (credentialIsSet()) {
            // this login module succeeded
            setRecovering();
        }
        return super.commit();
    }

    /**
     * Ignores the results of the other login modules and calls commit().
     *
     * @return <code>this.commit()</code>.
     *
     * @throws LoginException if an error occurs.
     **/
    public boolean abort()
        throws LoginException {
        return this.commit();
    }

    /**
     * Gets the name of the credential.
     *
     * @return getParamName()
     *
     * @throws LoginException if an error occurs.
     **/
    protected String getCredentialName()
        throws LoginException {
        return getParamName();
    }

    /**
     * Sets this login module's Subject as "recovering".  Adds a "recovery
     * credential" to this Subject's set of public credentials.  The
     * recovery credential is used check whether the user is recovering.
     *
     * @see #isRecovering(Subject)
     **/
    private void setRecovering() {
        s_log.debug("setting recovery credential");
        m_subject.getPublicCredentials().add(getParamName());
    }

    /**
     * Determines whether the given Subject is recovering by checking
     * whether it has a recovery credential.
     *
     * @return <code>true</code> if the Subject has the recovery credential,
     * <code>false</code> otherwise.
     **/
    public static boolean isRecovering(Subject subject) {
        return subject.getPublicCredentials().contains(getParamName());
    }

    /**
     * Returns the name of this login module's URL parameter.  To allow a
     * user to recover from a lost password, provide the user with a link to
     * the change password page with this URL parameter set to
     * <code>getParamValue()</code>.
     *
     * @return the name of the recovery login URL parameter.
     * @see #getParamValue(BigDecimal)
     **/
    public static String getParamName() {
        return URL_PARAM_NAME;
    }

    /**
     * Returns a URL parameter value that will allow the given user to log
     * in and change their password without entering their old password.
     * Use with care, as this value also allows the user to log in as usual.
     *
     * @param userID the ID of the user that needs to recover
     *
     * @return the value of the recovery login URL parameter.
     *
     * @throws CredentialEncodingException if unable to create the value.
     *
     * @see #getParamName()
     **/
    public static String getParamValue(BigDecimal userID)
        throws CredentialEncodingException {
        return Credential
            .create(userID.toString(),
                    1000 * CredentialLoginModule.TIMEOUT_SECS)
            .toString();
    }
}
