/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2004 Bull S.A.
 * Contact: jonas-team@objectweb.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas.security.realm.web.catalina55;

import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.realm.RealmBase;

import org.objectweb.jonas.common.Log;
import org.objectweb.jonas.security.SecurityService;
import org.objectweb.jonas.security.realm.factory.JResource;
import org.objectweb.jonas.security.realm.factory.JResourceException;
import org.objectweb.jonas.security.realm.principals.User;
import org.objectweb.jonas.service.ServiceManager;

import org.objectweb.security.context.SecurityContext;
import org.objectweb.security.context.SecurityCurrent;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * <p>
 * Implementation of a Realm. (by a wrapper) Use any JOnAS realm by specifying
 * the resource name
 *
 * @author Florent Benoit
 */
public class Standard extends RealmBase {

    /**
     * Descriptive information about this Realm implementation.
     */
    private static final String NAME = "JRealmCatalina50";

    /**
     * Descriptive information about this Realm implementation.
     */
    private static final String INFO = "org.objectweb.jonas.security.realm.JRealmCatalina50/1.0";

    /**
     * The logger used in JOnAS
     */
    private static Logger logger = null;

    /**
     * The resource we will use to authenticate users and identify associated
     * roles.
     */
    private JResource jResource = null;

    /**
     * The name of the resource
     */
    private String resourceName = null;

    /**
     * Reference to the JOnAS security service
     */
    private SecurityService securityService = null;

    /**
     * Return descriptive information about this Realm implementation and the
     * corresponding version number, in the format
     * <code>&lt;description&gt;/&lt;version&gt;</code>.
     *
     * @return the info.
     */
    public String getInfo() {
        return INFO;
    }

    /**
     * Return the resource name we will be using.
     *
     * @return the resource name.
     */
    public String getResourceName() {
        return resourceName;
    }

    /**
     * Set the resource name we will be using.
     *
     * @param resourceName The new resource name
     */
    public void setResourceName(String resourceName) {
        this.resourceName = resourceName;

    }

    /**
     * Return the Principal associated with the specified username and
     * credentials, if there is one; otherwise return <code>null</code>.
     *
     * @param username Username of the Principal to look up
     * @param credentials Password or other credentials to use in authenticating
     *        this username
     * @return the principal associated
     */
    public Principal authenticate(String username, String credentials) {

        // No authentication can be made with a null username
        if (username == null) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "No username so no authentication");
            }
            return null;
        }

        // Does a user with this username exist?
        User user = null;
        try {
            user = jResource.findUser(username);
        } catch (Exception jre) {
            // could not retrieve user
            logger.log(BasicLevel.ERROR, "Can not find the user : " + jre.getMessage());
            return null;
        }

        // User was not found
        if (user == null) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "User " + username + " not found.");
            }
            return null;
        }

        boolean validated = jResource.isValidUser(user, credentials);
        if (!validated) {
            logger.log(BasicLevel.ERROR, "The password for the user " + username + " is not valid");
            return null;
        }

        ArrayList combinedRoles = null;
        try {
            combinedRoles = jResource.getArrayListCombinedRoles(user);
        } catch (JResourceException jre) {
            logger.log(BasicLevel.ERROR, jre.getMessage());
            return null;
        }

        GenericPrincipal principal = new GenericPrincipal(this, user.getName(), user.getPassword(), combinedRoles);
        SecurityContext ctx = new SecurityContext(principal.getName(), combinedRoles);
        SecurityCurrent current = SecurityCurrent.getCurrent();
        current.setSecurityContext(ctx);

        return principal;
    }

    /**
     * Return the Principal associated with the specified chain of X509 client
     * certificates. If there is none, return <code>null</code>.
     *
     * @param cert Array of client certificates, with the first one in the array
     *        being the certificate of the client itself.
     * @return the associated Principal
     */
    public Principal authenticate(X509Certificate[] cert) {
        String dn = cert[0].getSubjectDN().getName();
        return authenticate(dn, "tomcat");
    }

    /**
     * Return a short name for this Realm implementation.
     *
     * @return the name
     */
    protected String getName() {
        return NAME;
    }

    /**
     * Return the password associated with the given principal's user name.
     *
     * @param username the given principal's user name.
     * @return the password associated.
     */
    protected String getPassword(String username) {
        return null;
    }

    /**
     * Return the Principal associated with the given user name.
     *
     * @param username the given principal's user name.
     * @return the Principal associated
     */
    protected Principal getPrincipal(String username) {
        return null;
    }

    /**
     * Prepare for active use of the public methods of this Component.
     *
     * @exception LifecycleException if this component detects a fatal error
     *            that prevents it from being started
     */
    public synchronized void start() throws LifecycleException {

        if (logger == null) {
            logger = Log.getLogger(Log.JONAS_SECURITY_PREFIX);
        }

        // Get the Security Service
        try {
            securityService = (SecurityService) ServiceManager.getInstance().getSecurityService();
        } catch (Exception e) {
            // Can't retrieve Security service
            throw new LifecycleException("can't retrieve Security service");
        }

        // Get the resource from the security service
        jResource = securityService.getJResource(resourceName);
        if (jResource == null) {
            throw new LifecycleException("Can't retrieve resource '" + resourceName + "' from the security service");
        }

        // Perform normal superclass initialization
        super.start();

    }

    /**
     * Gracefully shut down active use of the public methods of this Component.
     *
     * @exception LifecycleException if this component detects a fatal error
     *            that needs to be reported
     */
    public synchronized void stop() throws LifecycleException {
        // Perform normal superclass finalization
        super.stop();

        // Release reference to our resource
        jResource = null;
    }

    /**
     * Log a message on the Logger associated with our Container (if any)
     *
     * @param message Message to be logged
     */
    protected void log(String message) {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, message);
        }
    }

}