/**
 * 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
 *
 * Initial developer: Florent BENOIT
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

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

import java.security.Principal;
import java.security.acl.Group;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;

import javax.security.auth.Subject;
import javax.security.auth.login.AccountExpiredException;
import javax.security.auth.login.CredentialExpiredException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import org.mortbay.http.HttpRequest;

import org.objectweb.jonas.security.auth.callback.NoInputCallbackHandler;

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

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

/**
 * <p>
 * Implementation of a JAAS Realm. (by a wrapper) It uses the entry
 * JAAS_CONFIG_NAME from the JAAS config file
 * @author Alexandre Thaveau (JAAS support with JOnAS)
 * @author Marc-Antoine Bourgeot (JAAS support with JOnAS)
 * @author Florent Benoit (Jetty 5.x)
 */
public class JAAS extends Standard {

    /**
     * Name used in the JAAS config file
     */
    private static final String JAAS_CONFIG_NAME = "jetty";

    /**
     * Default Constructor
     */
    public JAAS() {
        super();
    }

    /**
     * Default Constructor
     * @param name of the realm
     */
    public JAAS(String name) {
        super();
        setName(name);
    }

    /**
     * Authenticate a user with a specific username and credentials
     * @param username name of the user
     * @param credentials credential of the user
     * @param request httprequest
     * @return a Jetty principal
     */
    public Principal authenticate(String username, Object credentials, HttpRequest request) {

        // No authentication can be made with a null username
        if (username == null) {
            return null;
        }

        Principal jettyPrincipal = (Principal) getUsers().get(username);
        // User previously authenticated --> remove from the cache
        if (jettyPrincipal != null) {
            removeUser(username);
        }

        NoInputCallbackHandler noInputCH = null;
        LoginContext loginContext = null;
        if (credentials instanceof X509Certificate[]) {
            // Format the DN as a special username
            String headerCertificate = "##DN##";
            X509Certificate[] certs = (X509Certificate[]) credentials;

            username = certs[0].getSubjectDN().getName();
            String usernameCert = headerCertificate.concat(username.replace('=', '#').replace(',', '%').replace(' ',
                    '$'));
            // Fill the callback handler for the login module with DN and
            // certificate
            noInputCH = new NoInputCallbackHandler(usernameCert, "", certs[0]);
        } else {
            // Fill the callback handler for the login module with username and
            // password
            noInputCH = new NoInputCallbackHandler(username, (String) credentials, null);
        }

        //Establish a LoginContext to use for authentication
        try {
            loginContext = new LoginContext(JAAS_CONFIG_NAME, noInputCH);
        } catch (LoginException e) {
            getLogger().log(BasicLevel.WARN, "loginException : unable to create a LoginContext for : '" + username + "'. Error : " + e.getMessage());
            return null;
        }

        // Negotiate a login via this LoginContext
        Subject subject = null;
        try {
            loginContext.login();
            subject = loginContext.getSubject();
            if (subject == null) {
                getLogger().log(BasicLevel.ERROR, "failedLogin for user :" + username);
                return null;
            }
        } catch (AccountExpiredException e) {
            if (getLogger().isLoggable(BasicLevel.ERROR)) {
                getLogger().log(BasicLevel.ERROR, "accountExpired for user :" + username);
            }
            return null;
        } catch (CredentialExpiredException e) {
            if (getLogger().isLoggable(BasicLevel.ERROR)) {
                getLogger().log(BasicLevel.ERROR, "credentialExpired for user :" + username);
            }
            return null;
        } catch (FailedLoginException e) {
            if (getLogger().isLoggable(BasicLevel.ERROR)) {
                getLogger().log(BasicLevel.ERROR, "failedLogin for user :" + username);
            }
            return null;
        } catch (LoginException e) {
            if (getLogger().isLoggable(BasicLevel.ERROR)) {
                getLogger().log(BasicLevel.ERROR, "loginException for user :" + username);
            }
            return null;
        }

        // Get credentials iterators from the subject (first found)
        //Iterator credentialsIterator =
        // subject.getPrivateCredentials().iterator();
        //String credential = (String) credentialsIterator.next();

        // Retrieve first principal name found (without groups)
        Iterator iterator = subject.getPrincipals(Principal.class).iterator();
        String userName = null;
        while (iterator.hasNext() && (userName == null)) {
            Principal principal = (Principal) iterator.next();
            if (!(principal instanceof Group)) {
                userName = principal.getName();
            }
        }

        // No name --> error
        if (userName == null) {
            getLogger().log(BasicLevel.ERROR, "No Username found in the subject");
            return null;
        }

        // Retrieve all roles of the user (Roles are members of the Group class)
        iterator = subject.getPrincipals(Group.class).iterator();
        ArrayList roles = new ArrayList();
        while (iterator.hasNext()) {
            Group group = (Group) iterator.next();
            Enumeration e = group.members();
            while (e.hasMoreElements()) {
                Principal p = (Principal) e.nextElement();
                roles.add(p.getName());
            }
        }

        // Create a JettyPrincipal for Jetty
        JettyPrincipal principal = new JettyPrincipal(userName, roles);

        // Register the subject in the security context
        //SecurityContext ctx = new SecurityContext(subject);
        SecurityContext ctx = new SecurityContext(userName, roles);
        SecurityCurrent current = SecurityCurrent.getCurrent();
        current.setSecurityContext(ctx);

        // Add to cache
        addUser(username, principal);

        return principal;
    }

}