/*
 * 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.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import org.apache.log4j.Logger;

/**
 * Superclass of LoginModules that map usernames to user IDs.  Translates
 * the username provided by the user into a numeric user ID used within ACS.
 * A subclass of this class is needed in any login configuration that uses a
 * PasswordLoginModule.
 *
 * @author Sameer Ajmani
 **/
public abstract class MappingLoginModule implements LoginModule {

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

    private BigDecimal m_userID = null;

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

    // implements LoginModule
    public void initialize(Subject subject,
                           CallbackHandler handler,
                           Map shared,
                           Map options) {
        m_subject = subject;
        m_handler = handler;
        m_shared = shared;
        m_options = options;
        // TODO: support "debug" option
    }

    /**
     * Reads the username from the shared data and calls
     * <code>getUserID</code>.
     *
     * @return <code>true</code>.
     *
     * @throws FailedLoginException if no username is available.
     *
     * @throws LoginException if an error occurs.  Propagates exceptions
     * thrown by <code>getUserID</code>.
     *
     * @see #getUserID(String)
     **/
    public boolean login() throws LoginException {
        s_log.debug("START login");
        m_userID = getUserID(getUsername());
        s_log.debug("SUCCESS login");
        return true;
    }

    /**
     * Reads the username from shared data.
     *
     * @return the username.
     *
     * @throws FailedLoginException if no username is available.
     **/
    private String getUsername() throws FailedLoginException {
        String username = (String)
            m_shared.get(PasswordLoginModule.NAME_KEY);
        if (username == null) {
            s_log.debug("FAILURE no username available");
            throw new FailedLoginException("no username available");
        }
        return username;
    }

    /**
     * Maps a username to a user ID.
     *
     * @param username the username to map
     *
     * @return the user ID corresponding to the given username.
     *
     * @throws AccountNotFoundException if no user ID exists for the given
     * username.
     *
     * @throws LoginException if an error occurs.
     **/
    protected abstract BigDecimal getUserID(String username)
        throws AccountNotFoundException, LoginException;

    /**
     * Adds the user ID to the Subject in a PartyPrincipal.
     *
     * @return <code>true</code>.
     **/
    public boolean commit() throws LoginException {
        s_log.debug("START commit");
        if (m_userID != null) {
            m_subject.getPrincipals().add(new PartyPrincipal(m_userID));
            s_log.debug("SUCCESS added new principal");
        }
        s_log.debug("END commit");
        return true;
    }

    /**
     * Trivial implementation; does nothing.
     *
     * @return <code>true</code>.
     **/
    public boolean abort() throws LoginException {
        s_log.debug("abort");
        return true;
    }

    /**
     * Trivial implementation; does nothing.
     *
     * @return <code>true</code>.
     **/
    public boolean logout() throws LoginException {
        s_log.debug("logout");
        return true;
    }
}
