/*
 * $Header: /home/cvspublic/jakarta-slide/wck/src/org/apache/slide/simple/authentication/JAASLoginModule.java,v 1.3 2005/02/09 18:01:43 ozeigermann Exp $
 * $Revision: 1.3 $
 * $Date: 2005/02/09 18:01:43 $
 *
 * ====================================================================
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.apache.slide.simple.authentication;

import java.io.IOException;
import java.security.Principal;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;

import org.apache.slide.common.Domain;
import org.apache.slide.jaas.spi.SlideGroup;
import org.apache.slide.jaas.spi.SlideLoginModule;
import org.apache.slide.jaas.spi.SlidePrincipal;
import org.apache.slide.jaas.spi.SlideRole;

/**
 * JAAS LoginModule for authenticating against the native user base of a custom
 * store.
 * 
 * <p>
 * The implementation bypasses Slide's internal security checking and requires
 * it to be turned off. Authentication and session handling is done by your
 * custom implementatin of
 * {@link org.apache.slide.simple.authentication.SessionAuthenticationManager}.
 * </p>
 * 
 * Options include (to be specified in JAAS login configuration file):
 * <ul>
 * <li><code>factoryClassName</code>: classname of implementation of
 * {@link org.apache.slide.simple.authentication.SessionAuthenticationManager};
 * defaults to dummy implementation, but really should be pointing to your
 * custom implementation
 * <li><code>slideRole</code>: role to be assumed for users unknown to
 * Slide; <code>root</code> is default
 * </ul>
 */
public class JAASLoginModule extends SlideLoginModule {

    protected static final String DEFAULT_ROLE = "root";

    protected static final String DEFAULT_MANAGER = "org.apache.slide.simple.reference.FakeSessionManager";

    protected static Object STATIC_LOCK = new Object();

    protected static String factoryClassName;

    protected static SessionAuthenticationManager factory;

    protected String slideDummyRole;

    public static boolean isInitialized() {
        return (factoryClassName != null);
    }

    public static Object getConnectionForUser(Principal principal) throws Exception {
        Object conn = null;
        if (factory != null) {
            conn = factory.getAuthenticationSession(principal.getName());
        }
        return conn;
    }

    public static void closeConnection(Object connection) throws Exception {
        // might be we have not been initialized or there has not been a login
        // and thus there is no connection associated to this thread
        if (factory != null && connection != null) {
            factory.closeAuthenticationSession(connection);
        }
    }

    public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {

        m_subject = subject;
        m_callbackHandler = callbackHandler;
        m_sharedState = sharedState;

        synchronized (STATIC_LOCK) {
            // be sure to have a single factory instance
            if (factoryClassName == null) {
                factoryClassName = (String) options.get("factoryClassName");
                if (factoryClassName == null) {
                    factoryClassName = DEFAULT_MANAGER;
                }
                try {
                    factory = (SessionAuthenticationManager) Class.forName(factoryClassName).newInstance();
                } catch (Exception e) {
                    throw new IllegalArgumentException("Could not create factory " + factoryClassName);
                }
            }
        }
        slideDummyRole = (String) options.get("slideRole");
        if (slideDummyRole == null) {
            slideDummyRole = DEFAULT_ROLE;
        }
    }

    public boolean login() throws LoginException {

        if (m_callbackHandler == null) {
            // we need a CallbackHandler to do our work
            throw new LoginException("No callback handler");
        }

        Callback[] callbacks = new Callback[2];
        callbacks[0] = new NameCallback("Username: ");
        callbacks[1] = new PasswordCallback("Password: ", false);

        try {
            // prompt for username and password
            m_callbackHandler.handle(callbacks);

            final String username = ((NameCallback) callbacks[0]).getName();
            final char[] password = ((PasswordCallback) callbacks[1]).getPassword();

            if (username == null) {
                throw new LoginException("No user name entered");
            }
            if (password == null) {
                Domain.warn("No password entered");
                return false;
//                throw new LoginException("No password entered");
            }

            // share username and password with other LoginModules
            m_sharedState.put("javax.security.auth.login.name", username);
            m_sharedState.put("javax.security.auth.login.password", password);

            Object conn = null;
            try {
                conn = factory.getAuthenticationSession(username, new String(password));
                if (conn == null) {
                    Domain.warn("Could not authenticate user " + username);
                    return false;
//                    throw new LoginException("Could not authenticate");
                }
            } catch (Exception e) {
                throw new LoginException("Could not create session: " + e.getMessage());
            } finally {
                // need to close the connection we just authenticated with
                if (conn != null) {
                    try {
                        factory.closeAuthenticationSession(conn);
                    } catch (Exception e1) {
                        Domain.error("Could not close connection", e1);
                    }
                }
            }

            m_principal = new SlidePrincipal(username);
            m_group = new SlideGroup();
            m_roles = new Principal[1];
            SlideRole slideRole = new SlideRole(slideDummyRole);
            // for Tomcat (information taken from SlideLoginModule.login)
            m_roles[0] = slideRole;
            // for Jetty (information taken from SlideLoginModule.login)
            m_group.addMember(slideRole);
            m_authenticated = true;
            return true;

        } catch (UnsupportedCallbackException e) {
            final String msg = "Failure during login()";
            Domain.error("[SlideLoginModule] - " + msg, e);
            throw new LoginException(msg);
        } catch (IOException e) {
            final String msg = "Failure during login()";
            Domain.error("[SlideLoginModule] - " + msg, e);
            throw new LoginException(msg);
        }
    }
}