/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2004 Bull S.A.
 * Contact: jonas-team@ow2.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.ow2.jonas.security.auth.spi;

import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

import org.ow2.jonas.lib.security.auth.JSigned;
import org.ow2.jonas.lib.security.context.SecurityContext;
import org.ow2.jonas.lib.security.context.SecurityCurrent;



/**
 * This class is used to propagate the Principal and roles to the server
 * It doesn't make any authentication
 * @author Florent Benoit
 */
public class ClientLoginModule implements LoginModule {

    /**
     * Subject used
     */
    private Subject subject = null;

    /**
     * Options for this login module
     */
    private Map options = null;

    /**
     * Name of the principal
     */
    private String principalName = null;

    /**
     * Roles of the principal
     */
    private ArrayList principalRoles = null;

    /**
     * Set SecurityContext for all the JVM ?
     */
    private boolean globalContext = false;

    /**
     * Initialize this LoginModule.
     * This method is called by the LoginContext after this LoginModule has been instantiated. The purpose of this method is to initialize this LoginModule with the relevant information. If this LoginModule does not understand any of the data stored in sharedState or options parameters, they can be ignored.
     * @param subject the Subject to be authenticated.
     * @param callbackHandler a CallbackHandler for communicating with the end user (prompting for usernames and passwords, for example).
     * @param sharedState state shared with other configured LoginModules.
     * @param options options specified in the login Configuration for this particular LoginModule.
     */
    public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
        this.subject = subject;
        this.options = options;
        principalRoles = new ArrayList();
    }


    /**
     * Method to authenticate a Subject (phase 1).
     * The implementation of this method authenticates a Subject. For example, it may prompt for Subject information such as a username and password and then attempt to verify the password. This method saves the result of the authentication attempt as private state within the LoginModule.
     * @return true if the authentication succeeded, or false if this LoginModule should be ignored.
     * @throws LoginException if the authentication fails
     */
    public boolean login() throws LoginException {
        // set context for all the JVM or not ?
        String useGlobalCtx = (String) options.get("globalCtx");
        if ((useGlobalCtx != null) && (Boolean.valueOf(useGlobalCtx).booleanValue())) {
            globalContext = true;
        }
        return true;
    }


    /**
     * Method to commit the authentication process (phase 2).
     * This method is called if the LoginContext's overall authentication succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules succeeded).
     * If this LoginModule's own authentication attempt succeeded (checked by retrieving the private state saved by the login method), then this method associates relevant Principals and Credentials with the Subject located in the LoginModule. If this LoginModule's own authentication attempted failed, then this method removes/destroys any state that was originally saved.
     * @return true if this method succeeded, or false if this LoginModule should be ignored.
     * @throws LoginException if the commit fails
     */
    public boolean commit() throws LoginException {

        // Retrieve only principal name (without groups)
        Set principals = subject.getPrincipals(Principal.class);
        Iterator iterator = principals.iterator();
        while (iterator.hasNext()) {
            Principal principal = (Principal) iterator.next();
            if (!(principal instanceof Group)) {
               principalName = principal.getName();
            }
        }

        // No name --> error
        if (principalName == null) {
            throw new LoginException("There was no previous login module. This login module can only be used in addition to another module which perform the authentication.");
        }

        // Retrieve all roles of the user (Roles are members of the Group.class)
        principals = subject.getPrincipals(Group.class);
        iterator = principals.iterator();
        JSigned jSigned = null;
        while (iterator.hasNext()) {
            Group group = (Group) iterator.next();

            // Signed group (empty group that contains a signature)?
            if (group instanceof JSigned) {
                jSigned = (JSigned) group;
                continue;
            }
            Enumeration e = group.members();
            while (e.hasMoreElements()) {
                Principal p = (Principal) e.nextElement();
                principalRoles.add(p.getName());
            }
        }

        // Propagate username and roles
        SecurityContext ctx = new SecurityContext(principalName, principalRoles);
        // Signed ?
        if (jSigned != null) {
            ctx.setSignature(jSigned.getSignature());
        }
        SecurityCurrent current = SecurityCurrent.getCurrent();
        if (globalContext) {
            current.setGlobalSecurityContext(ctx);
        } else {
            current.setSecurityContext(ctx);
        }

        return true;
    }


    /**
     * Method to abort the authentication process (phase 2).
     * This method is called if the LoginContext's overall authentication failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules did not succeed).
     * If this LoginModule's own authentication attempt succeeded (checked by retrieving the private state saved by the login method), then this method cleans up any state that was originally saved.
     * @return true if this method succeeded, or false if this LoginModule should be ignored.
     * @throws LoginException if the abort fails
     */
    public boolean abort() throws LoginException {

        // Do nothing (as all is done in the commit() phase)
        return true;
    }

    /**
     * Method which logs out a Subject.
     * An implementation of this method might remove/destroy a Subject's Principals and Credentials.
     * @return true if this method succeeded, or false if this LoginModule should be ignored.
     * @throws LoginException if the logout fails
     */
    public boolean logout() throws LoginException {

        // Unset the principal name
        SecurityContext ctx = new SecurityContext();
        SecurityCurrent current = SecurityCurrent.getCurrent();
        current.setSecurityContext(ctx);

        return true;

    }

}
