/*
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

/*
 * @(#)JCKLoginMod6.java	1.3 02/01/12
 *
 * ------------------------------------------------------------------
 *  (C) COPYRIGHT INTERNATIONAL BUSINESS MACHINES CORPORATION 1999.
 *                       ALL RIGHTS RESERVED
 *                         IBM Confidential
 * ------------------------------------------------------------------
 */

package com.sun.cts.util.tests.jaas;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Map;
import java.io.IOException;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.spi.LoginModule;
import javax.security.auth.login.LoginException;
import javax.security.auth.login.FailedLoginException;

/**
 * This file contains a simple LoginModule for purposes of JAAS JCK testing.
 *
 * <p> This JCK LoginModule authenticates users with a password.
 *
 * <p> This LoginModule only recognizes one user:  Rabbit
 * <p> Rabbit's password is:  carrot
 *
 * <p> If the user successfully authenticates itself,
 * a <code>JCKPrincipal</code> with the user's username
 * is added to the Subject.
 *
 * <p> This LoginModule recognizes the debug option.
 * If set to true in the login Configuration,
 * debug messages will be output to the output stream, System.out.
 *
 * @see javax.security.auth.spi.LoginModule
 *
 * @(#)author:  D. Kent Soper
 * @(#)version: 1.1
 * @(#)date:    99/08/27
 */
public class JCKLoginMod6 implements LoginModule {

    // initial state
    private Subject subject;
    private CallbackHandler callbackHandler;
    private Map state;
    private Map options;

    // configurable option
    private boolean debug = false;

    // the authentication status
    private boolean succeeded = false;
    private boolean commitSucceeded = false;

    // username and password
    private String username;
    private char[] password;

    // authnticated subject's principal
    private JCKPrincipal userPrincipal;

    // some test credentials
    private JCKPublicCred userPubCred;
    private JCKPrivateCred passPrivCred;
    private JCKPrivateCred randomPrivCred;

    /**
     * Initialize this <code>LoginModule</code>.
     *<p>
     *
     * @param subject the <code>Subject</code> to be authenticated. <p>
     *
     * @param callbackHandler a <code>CallbackHandler</code> for communicating
     *			with the end user (prompting for usernames and
     *			passwords, for example). <p>
     *
     * @param state shared <code>LoginModule</code> state. <p>
     *
     * @param options options specified in the login
     *			<code>Configuration</code> for this particular
     *			<code>LoginModule</code>.
     */
    public void initialize(Subject subject, CallbackHandler callbackHandler,
			Map state, Map options) {

	    this.subject = subject;
	    this.callbackHandler = callbackHandler;
	    this.state = state;
	    this.options = options;

	    // initialize debug option
	    debug = "true".equalsIgnoreCase((String)options.get("debug"));
    }

    /**
     * Authenticate the user by retrieving a username and password from
     * this LoginModule's options.
     *
     * <p>
     *
     * @return true in all cases since this <code>LoginModule</code>
     *		should not be ignored.
     *
     * @exception FailedLoginException if the authentication fails. <p>
     *
     * @exception LoginException if this <code>LoginModule</code>
     *		is unable to perform the authentication.
     */
    public boolean login() throws LoginException {

        // Retrieve username and password from LoginModule options.
        try {
	        username = (String)options.get("username");
	        password = ((String)options.get("password")).toCharArray();
	    } catch (Exception e) {
	        throw new LoginException("Error retrieving username and "
	                                +"password from LoginModule.");
	    }

	    // print debugging information
	    if (debug) {
	        System.out.println("[JCKLoginMod6] " +
				    "user entered username: " + username);
	        System.out.print("[JCKLoginMod6] " +
				    "user entered password: ");
	        for (int i = 0; i < password.length; i++)
		        System.out.print(password[i]);
	        System.out.println();
	    }

	    // verify the username/password
	    if (username.equals("Rabbit")
	        && password.length == 6
	        && password[0] == 'c'
	        && password[1] == 'a'
	        && password[2] == 'r'
	        && password[3] == 'r'
	        && password[4] == 'o'
	        && password[5] == 't') {

	        // authentication succeeded!!!
	        if (debug)
		        System.out.println("[JCKLoginMod6] " +
				                   "authentication succeeded");
	        succeeded = true;
	        return true;
	    } else {

	        // authentication failed -- clean out state
	        if (debug)
		        System.out.println("[JCKLoginMod6] " +
				                   "authentication failed");
	        succeeded = false;
	        username = null;
	        for (int i = 0; i < password.length; i++)
		        password[i] = ' ';
	        password = null;
	        throw new FailedLoginException("Password Incorrect");
	    }
    }

    /**
     * <p> This method is called if the LoginContext's
     * overall authentication succeeded
     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
     * succeeded).
     *
     * <p> If this LoginModule's own authentication attempt
     * succeeded (checked by retrieving the private state saved by the
     * <code>login</code> method), then this method associates a
     * <code>JCKPrincipal</code>, <code>JCKPublicCred</code>,
     * and two <code>JCKPrivateCreds</code>
     * with the <code>Subject</code> located in the
     * <code>LoginModuleContext</code>.  If this LoginModule's own
     * authentication attempted failed, then this method removes
     * any state that was originally saved.
     *
     * <p>
     *
     * @exception LoginException if the commit fails.
     *
     * @return true if this LoginModule's own login and commit
     *		attempts succeeded, or false otherwise.
     */
    public boolean commit() throws LoginException {
	    if (succeeded == false) {
	        return false;
	    } else {

	        // Add a Principal (authenticated identity) to the Subject.
	        // Assume the authenticated user is the JCKPrincipal.
	        userPrincipal = new JCKPrincipal(username);

	        // Add some credentials to the Subject.
	        userPubCred = new JCKPublicCred(username);
	        passPrivCred = new JCKPrivateCred(password);
	        randomPrivCred = new JCKPrivateCred((int)(1000000*Math.random()));

	        final Subject s = subject;
	        final JCKPrincipal sp = userPrincipal;
	        final JCKPublicCred pubCred = userPubCred;
	        final JCKPrivateCred privCred1 = passPrivCred;
	        final JCKPrivateCred privCred2 = randomPrivCred;

	        AccessController.doPrivileged (new PrivilegedAction() {
		        public Object run() {
		            s.getPrincipals().add(sp);
		            s.getPublicCredentials().add(pubCred);
		            s.getPrivateCredentials().add(privCred1);
		            s.getPrivateCredentials().add(privCred2);
		            return null;
		        }
	        });

	        if (debug) {
		        System.out.println("[JCKLoginMod6] " +
				            "added JCKPrincipal, JCKPublicCred, and " +
				            "two JCKPrivateCreds to Subject");
	        }


	        // in any case, clean out state
	        username = null;
	        for (int i = 0; i < password.length; i++)
		        password[i] = ' ';
	        password = null;

	        commitSucceeded = true;
	        return true;
	    }
    }

    /**
     * <p> This method is called if the LoginContext's
     * overall authentication failed.
     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
     * did not succeed).
     *
     * <p> If this LoginModule's own authentication attempt
     * succeeded (checked by retrieving the private state saved by the
     * <code>login</code> and <code>commit</code> methods),
     * then this method cleans up any state that was originally saved.
     *
     * <p>
     *
     * @exception LoginException if the abort fails.
     *
     * @return false if this LoginModule's own login and/or commit attempts
     *		failed, and true otherwise.
     */
    public boolean abort() throws LoginException {
	    if (succeeded == false) {
	        return false;
	    } else if (succeeded == true && commitSucceeded == false) {
	        // login succeeded but overall authentication failed
	        username = null;
	        for (int i = 0; i < password.length; i++)
		        password[i] = ' ';
	        password = null;
	    } else {
	        // overall authentication succeeded and commit succeeded,
	        // but someone else's commit failed
	        logout();
	    }
	    return true;
    }

    /**
     * Logout the user.
     *
     * <p> This method removes the <code>JCKPrincipal</code>, the
     * <code>JCKPublicCred</code>, and the two
     * <code>JCKPrivateCreds</code>
     * that were added by the <code>commit</code> method.
     *
     * <p>
     *
     * @exception LoginException if the logout fails.
     *
     * @return true in all cases since this <code>LoginModule</code>
     *          should not be ignored.
     */
    public boolean logout() throws LoginException {

	    final Subject s = subject;
	    final JCKPrincipal sp = userPrincipal;
	    final JCKPublicCred pubCred = userPubCred;
	    final JCKPrivateCred privCred1 = passPrivCred;
	    final JCKPrivateCred privCred2 = randomPrivCred;
	    AccessController.doPrivileged (new PrivilegedAction() {
            public Object run() {
	            s.getPrincipals().remove(sp);
	            s.getPublicCredentials().remove(pubCred);
	            s.getPrivateCredentials().remove(privCred1);
                s.getPrivateCredentials().remove(privCred2);
	            return null;
            }
	    });

	    userPrincipal = null;
	    userPubCred = null;
	    passPrivCred = null;
	    randomPrivCred = null;
	    return true;
    }
}
