package examples.security.rdbmsrealm;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import weblogic.security.acl.BasicRealm;
import weblogic.security.acl.User;
import weblogic.utils.encoders.BASE64Encoder;


/**
 * This is the in-memory representation of a user from the database.
 * The user has a name and password.  The password may be one-way
 * hashed; hashed passwords are stored in the database in the form
 * <tt>{HASH}gobbledygook</tt>, where <tt>HASH</tt> is the name of the
 * hashing algorithm, and <tt>gobbledygook</tt> is the hashed
 * password. <p>
 *
 * You can store passwords either in hashed or plaintext form, and if
 * you wish, you cvan vary this on a per-user basis (for example, use
 * MD5 for one user, SHA for another, and a plaintext password for
 * another user).  However, for security and manageability reasons, we
 * recommend using a single hashing algorithm for all passwords, and
 * not storing any plaintext passwords in the database. <p>
 *
 * This class has a <tt>main</tt> method, which you can use to
 * generate hashed passwords from plaintext passwords.  The hashed
 * passwords can then be stored in your database.
 *
 * @author Copyright (c) 1998-2000 by BEA Systems, Inc. All Rights Reserved.
 */
class RDBMSUser
  extends User
{
  /**
   * The default password-generation algorithm.
   */
  protected static final String ALGORITHM = "SHA";

  /**
   * The realm that created this user object.
   */
  private transient RDBMSRealm realm;

  /**
   * This user's password.  If this is hashed, the md field will
   * contain an instance of an object that performs the hashing
   * algorithm.
   */
  private transient String passwd;

  /**
   * The digest algorithm used to one-way hash this user's password.
   * If the password is not hashed with a known algorithm, or is in
   * plain form, this will be null.
   */
  private transient MessageDigest md;


  /**
   * Create a user with the given name and (maybe hashed) password,
   * from the given realm.
   */
  RDBMSUser(String name, String passwd, RDBMSRealm realm)
  {
    super(name);
    this.realm = realm;

    // Check to see if the password was stored in hashed form, and if
    // it was, whether we can use that hashing algorithm.

    if (passwd != null)
    {
      int rightCurly = passwd.indexOf("}");
    
      if (rightCurly > 0 && passwd.charAt(0) == '{')
      {
	this.passwd = passwd.substring(rightCurly + 1);

	String algorithm = passwd.substring(1, rightCurly);

	try
	{
	  md = MessageDigest.getInstance(algorithm.toUpperCase());
	}
	catch (NoSuchAlgorithmException e)
	{
	  if (realm.log != null)
	  {
	    realm.log.error("digest algorithm \"" + algorithm +
			    "\" not found - assuming plaintext password");
	  } else {
	    System.err.println("Error: digest algorithm \"" + algorithm +
			       "\" not found - assuming plaintext password");
	  }
	}
      } else {
	this.passwd = passwd;
	this.md = null;
      }
    }
  }


  /**
   * Return the realm that created this object.
   */
  public BasicRealm getRealm()
  {
    return realm;
  }

  
  /**
   * Hash the given plaintext with the given digest algorithm, and
   * base64-encode the result.
   *
   * @param md message digest algorithm to hash with
   * @param plaintext text to hash
   * @return base64-encoded hashed text
   */
  static protected String hash(MessageDigest md, String plaintext)
  {
    BASE64Encoder enc = new BASE64Encoder();
      
    return enc.encodeBuffer(md.digest(plaintext.getBytes()));
  }

  
  /**
   * Check a plaintext password against this user's password.  If the
   * object containing the password is not known, authentication will
   * fail.
   *
   * @param plaintext the plaintext password to check
   * @return true if matched, false otherwise
   */
  boolean authenticate(String plaintext)
  {
    String hashed = md != null ? hash(md, plaintext) : plaintext;

    return hashed.equals(passwd);
  }
  

  /**
   * Hash passwords according to the given algorithm.  Plaintext
   * passwords are read from stdin, and the encrypted passwords are
   * printed to stdout.  If no algorithm is specified on the command
   * line, the one specified in ALGORITHM is used.
   *
   * @see #ALGORITHM
   */
  public static void main(String[] args)
    throws IOException
  {
    String algorithm = (args.length >= 1 ? args[0] : ALGORITHM).toUpperCase();
    BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
    MessageDigest md = null;
    String prefix = null;
    String plaintext;

    try
    {
      md = MessageDigest.getInstance(algorithm);
    }
    catch (NoSuchAlgorithmException e)
    {
      // ignore
    }

    if (md == null)
    {
      System.err.println("Error: unknown algorithm \"" + algorithm + "\"");
      System.exit(1);
    }
      
    System.err.println("Enter plaintext passwords, separated by newlines.");
    
    while ((plaintext = r.readLine()) != null)
    {
      String passwd = "{" + algorithm + "}" + hash(md, plaintext);
      
      System.out.println(passwd);

      if (System.out.checkError())
      {
	throw new IOException("output error");
      }
    }

    r.close();
  }
}
