Skip to content
This repository has been archived by the owner on Jul 6, 2023. It is now read-only.

Commit

Permalink
Merge pull request #161 from sherfert/1.1-prompt
Browse files Browse the repository at this point in the history
Prompt silently if the output is redirected.
  • Loading branch information
sherfert authored Sep 3, 2019
2 parents f4c4954 + 78c1500 commit 4e94b2e
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.neo4j.shell;

import org.junit.Before;
import org.junit.Test;
import org.neo4j.shell.cli.CliArgs;
import org.neo4j.shell.log.AnsiLogger;
Expand All @@ -16,39 +17,47 @@

public class MainIntegrationTest {

@Test
public void connectInteractivelyPromptsOnWrongAuthentication() throws Exception {
private String inputString = String.format( "neo4j%nneo%n" );
private ByteArrayOutputStream baos;
private ConnectionConfig connectionConfig;
private CypherShell shell;
private Main main;

@Before
public void setup() {
// given
// what the user inputs when prompted
String inputString = String.format( "neo4j%nneo%n" );
InputStream inputStream = new ByteArrayInputStream(inputString.getBytes());
InputStream inputStream = new ByteArrayInputStream( inputString.getBytes() );

ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream( baos );

Main main = new Main(inputStream, ps);
main = new Main( inputStream, ps );

CliArgs cliArgs = new CliArgs();
cliArgs.setUsername("", "");
cliArgs.setPassword( "", "" );

Logger logger = new AnsiLogger(cliArgs.getDebugMode());
PrettyConfig prettyConfig = new PrettyConfig(cliArgs);
ConnectionConfig connectionConfig = new ConnectionConfig(
connectionConfig = new ConnectionConfig(
cliArgs.getScheme(),
cliArgs.getHost(),
cliArgs.getPort(),
cliArgs.getUsername(),
cliArgs.getPassword(),
cliArgs.getEncryption());

CypherShell shell = new CypherShell(logger, prettyConfig);
shell = new CypherShell(logger, prettyConfig);
}


@Test
public void promptsOnWrongAuthenticationIfInteractive() throws Exception {
// when
assertEquals("", connectionConfig.username());
assertEquals("", connectionConfig.password());

main.connectMaybeInteractively(shell, connectionConfig, true);
main.connectMaybeInteractively(shell, connectionConfig, true, true);

// then
// should be connected
Expand All @@ -60,4 +69,23 @@ public void connectInteractivelyPromptsOnWrongAuthentication() throws Exception
String out = baos.toString();
assertEquals( String.format( "username: neo4j%npassword: ***%n" ), out );
}

@Test
public void promptsSilentlyOnWrongAuthenticationIfOutputRedirected() throws Exception {
// when
assertEquals("", connectionConfig.username());
assertEquals("", connectionConfig.password());

main.connectMaybeInteractively(shell, connectionConfig, true, false);

// then
// should be connected
assertTrue(shell.isConnected());
// should have prompted silently and set the username and password
assertEquals("neo4j", connectionConfig.username());
assertEquals("neo", connectionConfig.password());

String out = baos.toString();
assertEquals( "", out );
}
}
62 changes: 40 additions & 22 deletions cypher-shell/src/main/java/org/neo4j/shell/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.neo4j.shell.build.Build;
import org.neo4j.shell.cli.CliArgHelper;
import org.neo4j.shell.cli.CliArgs;
import org.neo4j.shell.cli.Format;
import org.neo4j.shell.commands.CommandHelper;
import org.neo4j.shell.exception.CommandException;
import org.neo4j.shell.log.AnsiLogger;
Expand All @@ -15,9 +14,11 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;

import static org.neo4j.shell.ShellRunner.isInputInteractive;
import static org.neo4j.shell.ShellRunner.isOutputInteractive;

public class Main {
static final String NEO_CLIENT_ERROR_SECURITY_UNAUTHORIZED = "Neo.ClientError.Security.Unauthorized";
Expand Down Expand Up @@ -72,7 +73,7 @@ void startShell(@Nonnull CliArgs cliArgs) {
try {
CypherShell shell = new CypherShell(logger, prettyConfig);
// Can only prompt for password if input has not been redirected
connectMaybeInteractively(shell, connectionConfig, isInputInteractive());
connectMaybeInteractively(shell, connectionConfig, isInputInteractive(), isOutputInteractive());

// Construct shellrunner after connecting, due to interrupt handling
ShellRunner shellRunner = ShellRunner.getShellRunner(cliArgs, shell, logger, connectionConfig);
Expand All @@ -92,9 +93,20 @@ void startShell(@Nonnull CliArgs cliArgs) {
/**
* Connect the shell to the server, and try to handle missing passwords and such
*/
void connectMaybeInteractively(@Nonnull CypherShell shell, @Nonnull ConnectionConfig connectionConfig,
boolean interactively)
void connectMaybeInteractively(@Nonnull CypherShell shell,
@Nonnull ConnectionConfig connectionConfig,
boolean inputInteractive,
boolean outputInteractive)
throws Exception {

OutputStream outputStream = outputInteractive ? out : new ThrowawayOutputStream();

ConsoleReader consoleReader = new ConsoleReader(in, outputStream);
// Disable expansion of bangs: !
consoleReader.setExpandEvents(false);
// Ensure Reader does not handle user input for ctrl+C behaviour
consoleReader.setHandleUserInterrupt(false);

try {
shell.connect(connectionConfig);
} catch (AuthenticationException e) {
Expand All @@ -103,19 +115,24 @@ void connectMaybeInteractively(@Nonnull CypherShell shell, @Nonnull ConnectionCo
throw e;
}
// else need to prompt for username and password
if (interactively) {
if (inputInteractive) {
if (connectionConfig.username().isEmpty()) {
connectionConfig.setUsername(promptForNonEmptyText("username", null));
String username = outputInteractive ?
promptForNonEmptyText("username", consoleReader, null) :
promptForText("username", consoleReader, null);
connectionConfig.setUsername(username);
}
if (connectionConfig.password().isEmpty()) {
connectionConfig.setPassword(promptForText("password", '*'));
connectionConfig.setPassword(promptForText("password", consoleReader, '*'));
}
// try again
shell.connect(connectionConfig);
} else {
// Can't prompt because input has been redirected
throw e;
}
} finally {
consoleReader.close();
}
}

Expand All @@ -129,40 +146,41 @@ void connectMaybeInteractively(@Nonnull CypherShell shell, @Nonnull ConnectionCo
* in case of errors
*/
@Nonnull
private String promptForNonEmptyText(@Nonnull String prompt, @Nullable Character mask) throws Exception {
String text = promptForText(prompt, mask);
private String promptForNonEmptyText(@Nonnull String prompt, @Nonnull ConsoleReader consoleReader, @Nullable Character mask) throws Exception {
String text = promptForText(prompt, consoleReader, mask);
if (!text.isEmpty()) {
return text;
}
out.println(prompt + " cannot be empty");
out.println();
return promptForNonEmptyText(prompt, mask);
consoleReader.println( prompt + " cannot be empty" );
consoleReader.println();
return promptForNonEmptyText(prompt, consoleReader, mask);
}

/**
* @param prompt
* to display to the user
* @param mask
* single character to display instead of what the user is typing, use null if text is not secret
* @param consoleReader
* the reader
* @return the text which was entered
* @throws Exception
* in case of errors
*/
@Nonnull
private String promptForText(@Nonnull String prompt, @Nullable Character mask) throws Exception {
String line;
ConsoleReader consoleReader = new ConsoleReader(in, out);
// Disable expansion of bangs: !
consoleReader.setExpandEvents(false);
// Ensure Reader does not handle user input for ctrl+C behaviour
consoleReader.setHandleUserInterrupt(false);
line = consoleReader.readLine(prompt + ": ", mask);
consoleReader.close();

private String promptForText(@Nonnull String prompt, @Nonnull ConsoleReader consoleReader, @Nullable Character mask) throws Exception {
String line = consoleReader.readLine(prompt + ": ", mask);
if (line == null) {
throw new CommandException("No text could be read, exiting...");
}

return line;
}

private static class ThrowawayOutputStream extends OutputStream {
@Override
public void write( int b )
{
}
}
}
Loading

0 comments on commit 4e94b2e

Please sign in to comment.