All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.util.PasswordReader Maven / Gradle / Ivy

/*
 * Copyright 2013-2019 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright (C) 2013-2019 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.util;



import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStreamReader;
import java.util.Arrays;

import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;

import static com.unboundid.util.UtilityMessages.*;



/**
 * This class provides a mechanism for reading a password from the command line
 * in a way that attempts to prevent it from being displayed.  If it is
 * available (i.e., Java SE 6 or later), the
 * {@code java.io.Console.readPassword} method will be used to accomplish this.
 * For Java SE 5 clients, a more primitive approach must be taken, which
 * requires flooding standard output with backspace characters using a
 * high-priority thread.  This has only a limited effectiveness, but it is the
 * best option available for older Java versions.
 */
@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
public final class PasswordReader
{
  /**
   * The input stream from which to read the password.  This should only be set
   * when running unit tests.
   */
  private static volatile BufferedReader TEST_READER = null;



  /**
   * The default value to use for the environment variable.  This should only
   * be set when running unit tests.
   */
  private static volatile String DEFAULT_ENVIRONMENT_VARIABLE_VALUE = null;



  /**
   * The name of an environment variable that can be used to specify the path
   * to a file that contains the password to be read.  This is also
   * predominantly intended for use when running unit tests, and may be
   * necessary for tests running in a separate process that can't use the
   * {@code TEST_READER}.
   */
  private static final String PASSWORD_FILE_ENVIRONMENT_VARIABLE =
       "LDAP_SDK_PASSWORD_READER_PASSWORD_FILE";



  /**
   * Creates a new instance of this password reader thread.
   */
  private PasswordReader()
  {
    // No implementation is required.
  }



  /**
   * Reads a password from the console as a character array.
   *
   * @return  The characters that comprise the password that was read.
   *
   * @throws  LDAPException  If a problem is encountered while trying to read
   *                         the password.
   */
  public static char[] readPasswordChars()
         throws LDAPException
  {
    // If an input stream is available, then read the password from it.
    final BufferedReader testReader = TEST_READER;
    if (testReader != null)
    {
      try
      {
        return testReader.readLine().toCharArray();
      }
      catch (final Exception e)
      {
        Debug.debugException(e);
        throw new LDAPException(ResultCode.LOCAL_ERROR,
             ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)),
             e);
      }
    }


    // If a password input file environment variable has been set, then read
    // the password from that file.
    final String environmentVariableValue = StaticUtils.getEnvironmentVariable(
         PASSWORD_FILE_ENVIRONMENT_VARIABLE,
         DEFAULT_ENVIRONMENT_VARIABLE_VALUE);
    if (environmentVariableValue != null)
    {
      try
      {
        final File f = new File(environmentVariableValue);
        final PasswordFileReader r = new PasswordFileReader();
        return r.readPassword(f);
      }
      catch (final Exception e)
      {
        Debug.debugException(e);
        throw new LDAPException(ResultCode.LOCAL_ERROR,
             ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)),
             e);
      }
    }


    if (System.console() == null)
    {
      throw new LDAPException(ResultCode.LOCAL_ERROR,
           ERR_PW_READER_CANNOT_READ_PW_WITH_NO_CONSOLE.get());
    }

    return System.console().readPassword();
  }



  /**
   * Reads a password from the console as a byte array.
   *
   * @return  The characters that comprise the password that was read.
   *
   * @throws  LDAPException  If a problem is encountered while trying to read
   *                         the password.
   */
  public static byte[] readPassword()
         throws LDAPException
  {
    // Get the characters that make up the password.
    final char[] pwChars = readPasswordChars();

    // Convert the password to bytes.
    final ByteStringBuffer buffer = new ByteStringBuffer();
    buffer.append(pwChars);
    Arrays.fill(pwChars, '\u0000');
    final byte[] pwBytes = buffer.toByteArray();
    buffer.clear(true);
    return pwBytes;
  }



  /**
   * This is a legacy method that now does nothing.  It was required by a
   * former version of this class when older versions of Java were still
   * supported, and is retained only for the purpose of API backward
   * compatibility.
   *
   * @deprecated  This method is no longer used.
   */
  @Deprecated()
  public void run()
  {
    // No implementation is required.
  }



  /**
   * Specifies the lines that should be used as input when reading the password.
   * This should only be set when running unit tests, and the
   * {@link #setTestReader(BufferedReader)} method should be called with a value
   * of {@code null} before the end of the test to ensure that the password
   * reader is reverted back to its normal behavior.
   *
   * @param  lines  The lines of input that should be provided to the password
   *                reader instead of actually obtaining them interactively.
   *                It must not be {@code null} but may be empty.
   */
  @InternalUseOnly()
  public static void setTestReaderLines(final String... lines)
  {
    final ByteStringBuffer buffer = new ByteStringBuffer();
    for (final String line : lines)
    {
      buffer.append(line);
      buffer.append(StaticUtils.EOL_BYTES);
    }

    TEST_READER = new BufferedReader(new InputStreamReader(
         new ByteArrayInputStream(buffer.toByteArray())));
  }



  /**
   * Specifies the input stream from which to read the password.  This should
   * only be set when running unit tests, and this method should be called
   * again with a value of {@code null} before the end of the test to ensure
   * that the password reader is reverted back to its normal behavior.
   *
   * @param  reader  The input stream from which to read the password.  It may
   *                 be {@code null} to obtain the password from the normal
   *                 means.
   */
  @InternalUseOnly()
  public static void setTestReader(final BufferedReader reader)
  {
    TEST_READER = reader;
  }



  /**
   * Sets the default value that should be used for the environment variable if
   * it is not set.  This is only intended for use in testing purposes.
   *
   * @param  value  The default value that should be used for the environment
   *                variable if it is not set.  It may be {@code null} if
   */
  @InternalUseOnly()
  static void setDefaultEnvironmentVariableValue(final String value)
  {
    DEFAULT_ENVIRONMENT_VARIABLE_VALUE = value;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy