/*
 * Copyright (c) 1999-2003 Novell, Inc. All Rights Reserved.
 *
 * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES.
 * USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE AGREEMENT
 * ACCOMPANYING THE SOFTWARE DEVELOPMENT KIT (SDK) THAT CONTAINS THIS WORK.
 * PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS TO DEVELOPER A
 * ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S SAMPLE CODE IN ITS
 * PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION RIGHTS TO MARKET,
 * DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT OF DEVELOPER'S
 * PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR DEVELOPER'S
 * CUSTOMERS WITH RESPECT TO THIS CODE.
 */

import com.novell.security.sso.*;

import java.security.*;
import java.text.*;
import java.util.*;
import javax.naming.*;
import javax.naming.ldap.*;

/**
 * Class that demonstrates how to utilize the basic functionality of the
 * SecretStore Java LDAP API. Note that it is necessary to provide correct
 * environment properties before this test will work. The properties are
 * provided only for demonstration.
 *
 * @author Steve Kinser
 * @version 3.2
 */
public class JSSOTest extends Object {
    /**
     * The LdapContext that serves as the handle to the LDAP server where
     * SecretStore resides.
     */
    private LdapContext ctx;

    /**
     * The passoword instance variable.
     */
    private char[] password;

    /**
     * The secret value instance variable.
     */
    private byte[] value;

    /**
     * The handle to SecretStore.
     */
    private SecretStore store;

    /**
     * The starting point of this program.
     *
     * @param args The command line arguments.
     */
    public static void main(String[] args) {
        try {
            //construct an object of this class
            JSSOTest test = new JSSOTest();

            //run the tests and then shut it down
            test.runTest();
            test.runSharedSecretTest();
        } catch (SSException ex) {
            if (ex.hasRoot()) {
                System.err.println(ex.getRoot().getClass().getName());
                System.err.println(ex.getMessage());
            } else {
                System.err.println(ex.getErrorCode() + " " + ex.getMessage());
            }
            ex.printStackTrace();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Constructs a JSSOTest object.
     *
     * @throws NamingException If a NamingException occurred.
     */
    public JSSOTest() throws Exception {
        password = new char[]{'t', 'e', 's', 't'};
        value = new byte[]{0x74, 0x65, 0x73, 0x74};

        setup();
    }

    /**
     * Overrides the default clean-up behavior. Closes connections and ensures
     * that passwords are not left in memory.
     *
     * @throws Throwable the Exception raised by this method.
     */
    protected void finalize() throws Throwable {
        System.out.print("\nCleaning up...");
        ctx.close();

        Arrays.fill(password, '\u0000');
        Arrays.fill(value, (byte) 0x00);

        System.out.print("  done");
    }

    /**
     * Sets up the SecretStore handle.
     * <br><br>
     * <b>NOTE</b>: For this to work, the Trusted Root Certificate for the LDAP
     * server must be in the default truststore located in the
     * <java.home>/lib/security/cacerts file.
     *
     * @throws NamingException If a NamingException occurred.
     */
    protected void setup() throws Exception {
        System.out.print("\nSetting up the environment...");

        //ensure the default JSSE provider is added by dynamically adding it
        try {
            Class cls = Class.forName("com.sun.net.ssl.internal.ssl.Provider");
            Provider sunJsse = (Provider) cls.newInstance();
            Security.addProvider(sunJsse);
        } catch (Throwable t) {
            //do nothing, may already be added or another provider may be in use
        }

        //form the LdapContext environment properties
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.SECURITY_PROTOCOL, "ssl");
        env.put(Context.PROVIDER_URL, "ldap://127.0.0.1:636");
        env.put(Context.SECURITY_PRINCIPAL, "cn=admin,o=novell");
        env.put(Context.SECURITY_CREDENTIALS, password);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        System.out.print("  done");

        //get the LdapContext handle
        System.out.print("\nSetting up SSL and logging in...");
        LdapContext initial = new InitialLdapContext(env, null);
        System.out.print("  done");

        //get handle on user object
        ctx = (LdapContext) initial.lookup("cn=admin,o=novell");

        //form the SecretStore environment properties
        Hashtable senv = new Hashtable();
        senv.put(SecretStore.SECRET_STORE,
                "com.novell.security.sso.ldap.jndi.JNDISecretStore");
        senv.put(SecretStore.TARGET_DN, "cn=admin,o=novell");
        senv.put(SecretStore.HANDLE, ctx);

        //get the secretStore handle
        System.out.print("\nAccessing SecretStore...");
        store = SecretStore.getInstance(senv);

        /* For the NCP Implementation do the following instead.
        Hashtable ncpenv = new Hashtable();
        ncpenv.put(SecretStore.SECRET_STORE,
              "com.novell.security.sso.ncp.NCPSecretStore");

        System.out.print("\nAccessing SecretStore...");
        store = SecretStore.getInstance(ncpenv);
        System.out.print("  done");
        */

        System.out.print("  done");
    }

    /**
     * Method that demonstrates how to use the basic functionality of the
     * SecretStore Java API.
     *
     * @throws SSException If a SSException occurred.
     */
    public void runTest() throws SSException {
        System.out.print("\n\nPerforming test");
        DateFormat formater = DateFormat.getDateTimeInstance();

        //get a handle to the secret
        Secret secret = store.getSecret("\\\\Novell.com\\test");

        //Create the secret with name collision detection disabled. Also, ensure
        //the secret is enhanced protected.
        System.out.print("\n\nSetting the value: " + new String(value));
        secret.setEnhancedProtected(true);
        secret.setValue(value);

        //read the secret, ensuring that we get the latest data from SecretStore
        System.out.print("\n\nReading the Secret: ");
        secret.read();
        System.out.print("\nValue: " + new String(secret.getValue()));
        System.out.print("\nLast Accessed: ");
        Date date = secret.getAccessTime();

        if (date.getTime() != 0) {
            System.out.print(formater.format(date));
        } else {
            System.out.print("*** Disabled by the Administrator ***");
        }

        System.out.print("\nCreated: " + formater.format(
                secret.getCreateTime()));
        System.out.print("\nLast Modified: " + formater.format(
                secret.getModifyTime()));
        System.out.print("\nStatus Flags: " + secret.getStatus());

        //delete the secret
        System.out.print("\n\nDeleting the Secret: \\\\Novell.com\\test");
        secret.delete();

        //Create the secret and enable name collision detection. Also, increase
        //security by providing an enhanced protection password.
        System.out.print("\n\nCreating another secret");
        Secret secretb = store.getSecret("\\\\Novell.com\\testme");
        secretb.setCheckingSecretIDCollision(true);
        secretb.setEnhancedProtected(true);
        secretb.setEnhancedProtectionPassword(password);
        secretb.setValue(value);

        //get general SecretStore information
        System.out.print("\n\nGetting Service Information");
        System.out.print("\nServer Version: " + Integer.toHexString(
                store.getServerVersion()));
        System.out.print("\nServer Crypto Strength: " +
                store.getServerCryptoStrength());
        System.out.print("\nClient Version: " + Integer.toHexString(
                store.getClientVersion()));
        System.out.print("\nClient Crypto Strength: " +
                store.getClientCryptoStrength());
        System.out.print("\nLocked Secret Count: " +
                store.getNumLockedSecrets());
        System.out.print("\nSecret Count: " + store.getNumSecrets());
        System.out.print("\nStatus: " + store.getStatus());


        //get a list of all the secret identifiers in SecretStore
        System.out.println("\n\nEnumerating the secret identifiers");
        StringTokenizer tokenizer = store.enumerateSecrets();
        while (tokenizer.hasMoreTokens()) {
            System.out.print('\n' + tokenizer.nextToken());
        }

        //remove SecretStore from the object
        System.out.print("\n\nRemoving the SecretStore");
        store.removeSecretStore();

        System.out.println("\nTest Finished");
    }

    /**
     * Method that demonstrates how to use the shared secret functionality
     * of the SecretStore Java API. Note that all of the methods of the Secret
     * class can still be used by objects of the SharedSecret class because the
     * SharedSecret class extends from the Secret class.
     *
     * @throws SSException If a SSException occurred.
     */
    public void runSharedSecretTest() throws SSException {
        System.out.print("\n\nPerforming shared secret test");
        DateFormat formater = DateFormat.getDateTimeInstance();

        //get a handle to the shared secret and don't autoload - doesn't exist
        SharedSecret secret = new SharedSecret(store,
                SharedSecret.CREDENTIAL_TYPE, "Groupwise", false);

        //Write the secret with name collision disabled. Also, ensure
        //the secret is enhanced protected.
        System.out.print("\n\nSetting the username: johndoe");
        SharedEntry usernameEntry = new SharedEntry("Username", "johndoe");
        secret.addEntry(usernameEntry);
        System.out.print("\n\nSetting the password: mypassword");
        SharedEntry passwordEntry = new SharedEntry("Password", "mypassword");
        secret.addEntry(passwordEntry);
        secret.setEnhancedProtected(true);
        System.out.print("\n\nCommitting the changes");
        secret.setValue();

        //get a new handle to the shared secret and autoload
        secret = new SharedSecret(store,
                SharedSecret.CREDENTIAL_TYPE, "Groupwise");
        System.out.print("\n\nReading the shared secret: ");
        usernameEntry = secret.getEntry("Username");
        if (usernameEntry != null) {
            System.out.print("\nUsername: " + usernameEntry.getValue());
        }
        passwordEntry = secret.getEntry("Password");
        if (passwordEntry != null) {
            System.out.print("\nPassword: " + passwordEntry.getValue());
        }
        System.out.print("\nLast accessed: ");
        Date date = secret.getAccessTime();

        if (date.getTime() != 0) {
            System.out.print(formater.format(date));
        } else {
            System.out.print("*** Disabled by the Administrator ***");
        }

        System.out.print("\nCreated: " + formater.format(
                secret.getCreateTime()));
        System.out.print("\nLast modified: " + formater.format(
                secret.getModifyTime()));
        System.out.print("\nStatus flags: " + secret.getStatus());

        //modify the shared secret by removing the password, leave the username
        System.out.print("\n\nRemoving the password from the shared secret");
        secret.removeEntry(passwordEntry);
        secret.setValue();

        //delete the secret
        System.out.print("\n\nDeleting the shared secret");
        secret.delete();

        //Create the secret with name collision enabled. Also,
        //increase security by providing an enhanced protection password.
        System.out.print("\n\nCreating another shared secret");
        secret = new SharedSecret(store, SharedSecret.APPLICATION_TYPE,
                "TestMe");
        secret.setCheckingSecretIDCollision(true);
        secret.setEnhancedProtected(true);
        secret.setEnhancedProtectionPassword(password);
        SharedEntry entry = new SharedEntry("MyAppEntry", "testvalue");
        secret.addEntry(entry);
        secret.setValue();

        //get general SecretStore information
        System.out.print("\n\nGetting service information");
        System.out.print("\nServer Version: " + Integer.toHexString(
                store.getServerVersion()));
        System.out.print("\nServer Crypto Strength: " +
                store.getServerCryptoStrength());
        System.out.print("\nClient Version: " + Integer.toHexString(
                store.getClientVersion()));
        System.out.print("\nClient Crypto Strength: " +
                store.getClientCryptoStrength());
        System.out.print("\nLocked Secret Count: " +
                store.getNumLockedSecrets());
        System.out.print("\nSecret Count: " + store.getNumSecrets());
        System.out.print("\nStatus: " + store.getStatus());


        //get a list of all the shared secret identifiers in SecretStore
        System.out.println("\n\nEnumerating the shared secret identifiers");
        StringTokenizer tokenizer = store.enumerateSecrets(0,
                SharedSecret.APPLICATION_TYPE);
        while (tokenizer.hasMoreTokens()) {
            System.out.print('\n' + tokenizer.nextToken());
        }

        //remove SecretStore from the object
        System.out.print("\n\nRemoving the SecretStore");
        store.removeSecretStore();

        System.out.println("\nShared secret test finished");
    }
}