package au.net.causal.projo.prefs.security;
import java.io.Console;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.Scanner;
import org.apache.commons.lang3.StringUtils;
import au.net.causal.projo.prefs.PreferencesException;
/**
* Password source that reads passwords from the console.
* <p>
*
* By default, if a non-echoing method of reading passwords from the console is not available, this password source throws an exception when attempting
* to read a password. However, if having passwords echoed on the screen is not a problem, the {@link #setEchoAllowed(boolean)} method can be used to control
* this behaviour.
*
* @author prunge
*/
public class ConsoleUiPasswordSource implements UiPromptPasswordSource
{
private boolean echoAllowed;
private String echoWarningText = "Warning: password will be echoed to the screen!";
private String prompt = "Configuration master password";
private String verifyPrompt = "Re-enter password";
private String verifyErrorMessage = "Passwords were not the same.";
public static void main(String... args)
throws Exception
{
ConsoleUiPasswordSource s = new ConsoleUiPasswordSource();
s.setEchoAllowed(true);
char[] p = s.readPassword(Mode.ENCRYPTION);
System.err.println("The password is: " + String.valueOf(p));
}
/**
* Returns whether the password is allowed to be echoed on the screen.
*
* @return true if the password can be echoed to the console, false if not.
*
* @see #setEchoAllowed(boolean)
* @see #setEchoWarningText(String)
*/
public boolean isEchoAllowed()
{
return(echoAllowed);
}
/**
* Sets whether the password entered by the user may be echoed to the console.
* <p>
*
* This source will always try to get a non-echoing password when possible. However, when this capability is not available, if this setting is enabled,
* password may still be entered by the user, however it will be echoed to the screen. If this setting is disabled, an error will occur in the
* application instead.
*
* @param echoAllowed true to allow echo, false not to.
*
* @see #isEchoAllowed()
* @see #setEchoWarningText(String)
*/
public void setEchoAllowed(boolean echoAllowed)
{
this.echoAllowed = echoAllowed;
}
/**
* If password echo is allowed, and when there is no capability to read passwords from the console without echoing, this warning text is displayed to
* the user to inform them that others may be able to read the password from the screen.
*
* @return the echo warning text.
*
* @see #setEchoWarningText(String)
*/
public String getEchoWarningText()
{
return(echoWarningText);
}
/**
* Sets the warning text that is written to the console before the prompt when entering the password will cause it to echo on the screen.
* <p>
*
* Only used when echo is allowed.
*
* @param echoWarningText the echo warning text.
*
* @see #getEchoWarningText()
*/
public void setEchoWarningText(String echoWarningText)
{
this.echoWarningText = echoWarningText;
}
/**
* Returns the message displayed when the password and the verify password are not the same.
*
* @return the verify error message.
*
* @see #setVerifyErrorMessage(String)
*/
public String getVerifyErrorMessage()
{
return(verifyErrorMessage);
}
/**
* Sets the message displayed when the password and the verify password are not the same.
*
* @param verifyErrorMessage the verify error message.
*
* @see #getVerifyErrorMessage()
*/
public void setVerifyErrorMessage(String verifyErrorMessage)
{
this.verifyErrorMessage = verifyErrorMessage;
}
@Override
public char[] readPassword(Mode mode) throws PreferencesException
{
char[] password;
Console console = System.console();
if (console == null)
{
if (isEchoAllowed())
{
//Since we are echoing the password anyway no need to make the user validate by typing second time
if (!StringUtils.isEmpty(getEchoWarningText()))
System.err.println(getEchoWarningText());
if (!StringUtils.isEmpty(getPasswordPrompt()))
System.err.print(getPasswordPrompt() + ": ");
@SuppressWarnings("resource") //Don't want to close System.in
Scanner sc = new Scanner(System.in);
try
{
password = sc.nextLine().toCharArray();
}
catch (NoSuchElementException e)
{
//End of System.in stream possible I guess, regard as a cancel
password = null;
}
}
else
throw new PreferencesException("Console not available, cannot read password from user securely.");
}
else
{
if (mode == Mode.ENCRYPTION)
{
boolean passwordsAreTheSame;
do
{
if (!StringUtils.isEmpty(getPasswordPrompt()))
System.err.print(getPasswordPrompt() + ": ");
password = console.readPassword();
//User cancelled
if (password.length == 0)
return(null);
if (!StringUtils.isEmpty(getVerifyPrompt()))
System.err.print(getVerifyPrompt() + ": ");
char[] verifyPassword = console.readPassword();
passwordsAreTheSame = Arrays.equals(password, verifyPassword);
if (!passwordsAreTheSame && !StringUtils.isEmpty(getVerifyErrorMessage()))
System.err.println(getVerifyErrorMessage());
}
while (!passwordsAreTheSame);
}
else
{
if (!StringUtils.isEmpty(getPasswordPrompt()))
System.err.print(getPasswordPrompt() + ": ");
password = console.readPassword();
}
}
//If the user just presses ENTER regard as a cancel
if (password != null && password.length == 0)
password = null;
return(password);
}
@Override
public String getPasswordPrompt()
{
return(prompt);
}
@Override
public void setPasswordPrompt(String prompt)
{
this.prompt = prompt;
}
public String getVerifyPrompt()
{
return(verifyPrompt);
}
public void setVerifyPrompt(String verifyPrompt)
{
this.verifyPrompt = verifyPrompt;
}
}