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 #216 from sherfert/4.0-new-protocols
Browse files Browse the repository at this point in the history
Support +ssc and +s protocols
  • Loading branch information
sherfert authored Apr 27, 2020
2 parents e78b0ab + ae4b9db commit a1fb500
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package org.neo4j.shell.commands;

import org.neo4j.driver.exceptions.ServiceUnavailableException;
import org.neo4j.shell.ConnectionConfig;
import org.neo4j.shell.CypherShell;
import org.neo4j.shell.cli.Encryption;
import org.neo4j.shell.exception.CommandException;

import static org.neo4j.shell.DatabaseManager.ABSENT_DB_NAME;
Expand All @@ -12,15 +12,6 @@ abstract class CypherShellIntegrationTest
CypherShell shell;

void connect(String password) throws CommandException {
// Try with encryption off first, which is the default for 4.X
try
{
shell.connect( new ConnectionConfig( "bolt://", "localhost", 7687, "neo4j", password, false, ABSENT_DB_NAME ) );
}
catch ( ServiceUnavailableException e )
{
// This means we are probably in 3.X, let's retry with encryption on
shell.connect( new ConnectionConfig( "bolt://", "localhost", 7687, "neo4j", password, true, ABSENT_DB_NAME ) );
}
shell.connect( new ConnectionConfig( "bolt://", "localhost", 7687, "neo4j", password, Encryption.DEFAULT, ABSENT_DB_NAME ) );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.neo4j.shell.CypherShell;
import org.neo4j.shell.ShellParameterMap;
import org.neo4j.shell.StringLinePrinter;
import org.neo4j.shell.cli.Encryption;
import org.neo4j.shell.cli.Format;
import org.neo4j.shell.exception.CommandException;
import org.neo4j.shell.prettyprint.PrettyConfig;
Expand Down Expand Up @@ -45,7 +46,7 @@ public void setUp() throws Exception
beginCommand = new Begin( shell );
rollbackCommand = new Rollback( shell );

shell.connect( new ConnectionConfig( "bolt://", "localhost", 7687, "neo4j", "neo", false, ABSENT_DB_NAME ) );
shell.connect( new ConnectionConfig( "bolt://", "localhost", 7687, "neo4j", "neo", Encryption.DEFAULT, ABSENT_DB_NAME ) );

// Multiple databases are only available from 4.0
assumeTrue( majorVersion( shell.getServerVersion() ) >= 4 );
Expand Down Expand Up @@ -137,7 +138,7 @@ public void switchingToNonExistingDatabaseShouldGiveErrorResponseFromServerInter
{
shell = new CypherShell( linePrinter, new PrettyConfig( Format.PLAIN, true, 1000 ), true, new ShellParameterMap() );
useCommand = new Use( shell );
shell.connect( new ConnectionConfig( "bolt://", "localhost", 7687, "neo4j", "neo", false, ABSENT_DB_NAME ) );
shell.connect( new ConnectionConfig( "bolt://", "localhost", 7687, "neo4j", "neo", Encryption.DEFAULT, ABSENT_DB_NAME ) );

useCommand.execute( SYSTEM_DB_NAME );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,59 @@
import org.neo4j.shell.CypherShell;
import org.neo4j.shell.ShellParameterMap;
import org.neo4j.shell.StringLinePrinter;
import org.neo4j.shell.cli.Encryption;
import org.neo4j.shell.cli.Format;
import org.neo4j.shell.prettyprint.PrettyConfig;
import static org.junit.Assert.assertTrue;

import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.neo4j.shell.DatabaseManager.ABSENT_DB_NAME;
import static org.neo4j.shell.util.Versions.majorVersion;
import static org.neo4j.shell.util.Versions.minorVersion;

public class CypherShellProtocolIntegrationTest{

@Test
public void shouldConnectWithBoltProtocol() throws Exception {
CypherShell shell = new CypherShell( new StringLinePrinter(), new PrettyConfig( Format.PLAIN, true, 1000), false, new ShellParameterMap());
shell.connect( new ConnectionConfig( "bolt://", "localhost", 7687, "neo4j", "neo", Encryption.DEFAULT, ABSENT_DB_NAME ) );
assertTrue(shell.isConnected());
}

@Test
public void shouldConnectWithNeo4jProtocol() throws Exception {
CypherShell shell = new CypherShell( new StringLinePrinter(), new PrettyConfig( Format.PLAIN, true, 1000), false, new ShellParameterMap());
// This should work even on older databases without the neo4j protocol, by falling back to bolt
shell.connect( new ConnectionConfig( "neo4j://", "localhost", 7687, "neo4j", "neo", false, ABSENT_DB_NAME ) );
shell.connect( new ConnectionConfig( "neo4j://", "localhost", 7687, "neo4j", "neo", Encryption.DEFAULT, ABSENT_DB_NAME ) );
assertTrue(shell.isConnected());
}

@Test
public void shouldConnectWithBoltSSCProtocol() throws Exception {
CypherShell shell = new CypherShell( new StringLinePrinter(), new PrettyConfig( Format.PLAIN, true, 1000), false, new ShellParameterMap());
// Given 3.X series where X > 1, where SSC are the default. Hard to test in 4.0 sadly.
onlyIn3_2to3_6( shell);
shell.connect( new ConnectionConfig( "bolt+ssc://", "localhost", 7687, "neo4j", "neo", Encryption.DEFAULT, ABSENT_DB_NAME ) );
assertTrue(shell.isConnected());
}

@Test
public void shouldConnectWithNeo4jSSCProtocol() throws Exception {
CypherShell shell = new CypherShell( new StringLinePrinter(), new PrettyConfig( Format.PLAIN, true, 1000), false, new ShellParameterMap());
// Given 3.X series where X > 1, where SSC are the default. Hard to test in 4.0 sadly.
onlyIn3_2to3_6( shell);
// This should work by falling back to bolt+ssc
shell.connect( new ConnectionConfig( "neo4j+ssc://", "localhost", 7687, "neo4j", "neo", Encryption.DEFAULT, ABSENT_DB_NAME ) );
assertTrue(shell.isConnected());
}

// Here should be tests for "neo4j+s" and "bolt+s", but we don't have the infrastructure for those.

private void onlyIn3_2to3_6( CypherShell shell) throws Exception {
// Default connection settings
shell.connect( new ConnectionConfig( "bolt://", "localhost", 7687, "neo4j", "neo", Encryption.DEFAULT, ABSENT_DB_NAME ) );
assumeTrue( majorVersion( shell.getServerVersion() ) == 3 );
assumeTrue( minorVersion( shell.getServerVersion() ) > 1 );
shell.disconnect();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import javax.annotation.Nonnull;

import org.neo4j.shell.cli.Encryption;

public class ConnectionConfig {
public static final String USERNAME_ENV_VAR = "NEO4J_USERNAME";
public static final String PASSWORD_ENV_VAR = "NEO4J_PASSWORD";
Expand All @@ -10,7 +12,7 @@ public class ConnectionConfig {
private final String scheme;
private final String host;
private final int port;
private final boolean encryption;
private final Encryption encryption;
private String username;
private String password;
private String newPassword;
Expand All @@ -21,7 +23,7 @@ public ConnectionConfig(@Nonnull String scheme,
int port,
@Nonnull String username,
@Nonnull String password,
boolean encryption,
Encryption encryption,
@Nonnull String database) {
this.host = host;
this.port = port;
Expand Down Expand Up @@ -78,7 +80,7 @@ public String driverUrl() {
}

@Nonnull
public boolean encryption() {
public Encryption encryption() {
return encryption;
}

Expand Down
15 changes: 10 additions & 5 deletions cypher-shell/src/main/java/org/neo4j/shell/cli/CliArgHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private static CliArgs getCliArgs( CliArgs cliArgs, ArgumentParser parser, Names
if (!pass.isEmpty()) {
cliArgs.setPassword(pass, cliArgs.getPassword());
}
cliArgs.setEncryption(ns.getBoolean("encryption"));
cliArgs.setEncryption(Encryption.parse(ns.get("encryption")));
cliArgs.setDatabase(ns.getString("database"));
cliArgs.setInputFilename(ns.getString( "file" ) );

Expand Down Expand Up @@ -161,10 +161,15 @@ private static ArgumentParser setupParser(ParameterMap parameterMap)
.setDefault("")
.help("password to connect with. Can also be specified using environment variable " + ConnectionConfig.PASSWORD_ENV_VAR);
connGroup.addArgument("--encryption")
.help("whether the connection to Neo4j should be encrypted; must be consistent with Neo4j's " +
"configuration")
.type(new BooleanArgumentType())
.setDefault(false);
.help("whether the connection to Neo4j should be encrypted. This must be consistent with Neo4j's " +
"configuration. If choosing '" + Encryption.DEFAULT.name().toLowerCase() +
"' the encryption setting is deduced from the specified address. " +
"For example the 'neo4j+ssc' protocol would use encryption.")
.choices(new CollectionArgumentChoice<>(
Encryption.TRUE.name().toLowerCase(),
Encryption.FALSE.name().toLowerCase(),
Encryption.DEFAULT.name().toLowerCase()))
.setDefault(Encryption.DEFAULT.name().toLowerCase());
connGroup.addArgument("-d", "--database")
.help("database to connect to. Can also be specified using environment variable " + ConnectionConfig.DATABASE_ENV_VAR)
.setDefault("");
Expand Down
6 changes: 3 additions & 3 deletions cypher-shell/src/main/java/org/neo4j/shell/cli/CliArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class CliArgs {
private Format format = Format.AUTO;
@SuppressWarnings( "OptionalUsedAsFieldOrParameterType" )
private Optional<String> cypher = Optional.empty();
private boolean encryption;
private Encryption encryption = Encryption.DEFAULT;
private boolean debugMode;
private boolean nonInteractive = false;
private boolean version = false;
Expand Down Expand Up @@ -101,7 +101,7 @@ public void setCypher(@Nullable String cypher) {
/**
* Set whether the connection should be encrypted
*/
public void setEncryption(boolean encryption) {
public void setEncryption(Encryption encryption) {
this.encryption = encryption;
}

Expand Down Expand Up @@ -171,7 +171,7 @@ public Format getFormat() {
return format;
}

public boolean getEncryption() {
public Encryption getEncryption() {
return encryption;
}

Expand Down
20 changes: 20 additions & 0 deletions cypher-shell/src/main/java/org/neo4j/shell/cli/Encryption.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.neo4j.shell.cli;

import javax.annotation.Nonnull;

public enum Encryption
{
TRUE,
FALSE,
DEFAULT;

public static Encryption parse( @Nonnull String format) {
if (format.equalsIgnoreCase(TRUE.name())) {
return TRUE;
} else if (format.equalsIgnoreCase( FALSE.name() )) {
return FALSE;
} else {
return DEFAULT;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,24 @@ public void connect( @Nonnull ConnectionConfig connectionConfig, ThrowingAction<
driver = getDriver(connectionConfig, authToken);
reconnect(command);
} catch (org.neo4j.driver.exceptions.ServiceUnavailableException e) {
if (!connectionConfig.scheme().equals( Scheme.NEO4J_URI_SCHEME + "://")) {
String scheme = connectionConfig.scheme();
String fallbackScheme;
switch ( scheme )
{
case Scheme.NEO4J_URI_SCHEME + "://":
fallbackScheme = Scheme.BOLT_URI_SCHEME;
break;
case Scheme.NEO4J_LOW_TRUST_URI_SCHEME + "://":
fallbackScheme = Scheme.BOLT_LOW_TRUST_URI_SCHEME;
break;
case Scheme.NEO4J_HIGH_TRUST_URI_SCHEME + "://":
fallbackScheme = Scheme.BOLT_HIGH_TRUST_URI_SCHEME;
break;
default:
throw e;
}
connectionConfig = new ConnectionConfig(
Scheme.BOLT_URI_SCHEME + "://",
fallbackScheme + "://",
connectionConfig.host(),
connectionConfig.port(),
connectionConfig.username(),
Expand Down Expand Up @@ -438,10 +451,14 @@ private void clearTransactionStatements() {

private Driver getDriver(@Nonnull ConnectionConfig connectionConfig, @Nullable AuthToken authToken) {
Config.ConfigBuilder configBuilder = Config.builder().withLogging(NullLogging.NULL_LOGGING);
if (connectionConfig.encryption()) {
switch(connectionConfig.encryption())
{
case TRUE:
configBuilder = configBuilder.withEncryption();
} else {
break;
case FALSE:
configBuilder = configBuilder.withoutEncryption();
break;
}
return driverProvider.apply(connectionConfig.driverUrl(), authToken, configBuilder.build());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.EnvironmentVariables;

import org.neo4j.shell.cli.Encryption;
import org.neo4j.shell.log.Logger;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.neo4j.shell.DatabaseManager.ABSENT_DB_NAME;

import org.junit.contrib.java.lang.system.EnvironmentVariables;

public class ConnectionConfigTest {

@Rule
Expand All @@ -20,7 +19,7 @@ public class ConnectionConfigTest {

private Logger logger = mock(Logger.class);
private ConnectionConfig config = new ConnectionConfig("bolt://", "localhost", 1, "bob",
"pass", false, "db");
"pass", Encryption.DEFAULT, "db");


@Test
Expand All @@ -47,7 +46,7 @@ public void username() {
public void usernameDefaultsToEnvironmentVar() {
environmentVariables.set(ConnectionConfig.USERNAME_ENV_VAR, "alice");
ConnectionConfig configWithEmptyParams = new ConnectionConfig("bolt://", "localhost", 1, "",
"", false, ABSENT_DB_NAME);
"", Encryption.DEFAULT, ABSENT_DB_NAME);
assertEquals("alice", configWithEmptyParams.username());
}

Expand All @@ -60,7 +59,7 @@ public void password() {
public void passwordDefaultsToEnvironmentVar() {
environmentVariables.set(ConnectionConfig.PASSWORD_ENV_VAR, "ssap");
ConnectionConfig configWithEmptyParams = new ConnectionConfig("bolt://", "localhost", 1, "",
"", false, ABSENT_DB_NAME);
"", Encryption.DEFAULT, ABSENT_DB_NAME);
assertEquals("ssap", configWithEmptyParams.password());
}

Expand All @@ -73,7 +72,7 @@ public void database() {
public void databaseDefaultsToEnvironmentVar() {
environmentVariables.set(ConnectionConfig.DATABASE_ENV_VAR, "funnyDB");
ConnectionConfig configWithEmptyParams = new ConnectionConfig("bolt://", "localhost", 1, "",
"", false, ABSENT_DB_NAME);
"", Encryption.DEFAULT, ABSENT_DB_NAME);
assertEquals("funnyDB", configWithEmptyParams.database());
}
@Test
Expand All @@ -83,7 +82,8 @@ public void driverUrlDefaultScheme() {

@Test
public void encryption() {
assertTrue(new ConnectionConfig("bolt://", "", -1, "", "", true, ABSENT_DB_NAME).encryption());
assertFalse(new ConnectionConfig("bolt://", "", -1, "", "", false, ABSENT_DB_NAME).encryption());
assertEquals(Encryption.DEFAULT, new ConnectionConfig("bolt://", "", -1, "", "", Encryption.DEFAULT, ABSENT_DB_NAME).encryption());
assertEquals(Encryption.TRUE, new ConnectionConfig("bolt://", "", -1, "", "", Encryption.TRUE, ABSENT_DB_NAME).encryption());
assertEquals(Encryption.FALSE, new ConnectionConfig("bolt://", "", -1, "", "", Encryption.FALSE, ABSENT_DB_NAME).encryption());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.neo4j.driver.Session;
import org.neo4j.driver.Value;
import org.neo4j.driver.summary.ResultSummary;
import org.neo4j.shell.cli.Encryption;
import org.neo4j.shell.commands.CommandExecutable;
import org.neo4j.shell.commands.CommandHelper;
import org.neo4j.shell.exception.CommandException;
Expand Down Expand Up @@ -62,7 +63,7 @@ public void setup() {

@Test
public void verifyDelegationOfConnectionMethods() throws CommandException {
ConnectionConfig cc = new ConnectionConfig("bolt://", "", 1, "", "", false, ABSENT_DB_NAME);
ConnectionConfig cc = new ConnectionConfig("bolt://", "", 1, "", "", Encryption.DEFAULT, ABSENT_DB_NAME);
CypherShell shell = new CypherShell(logger, mockedBoltStateHandler, mockedPrettyPrinter, new ShellParameterMap());

shell.connect(cc);
Expand Down
5 changes: 3 additions & 2 deletions cypher-shell/src/test/java/org/neo4j/shell/MainTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.neo4j.driver.exceptions.Neo4jException;
import org.neo4j.driver.exceptions.SecurityException;
import org.neo4j.shell.cli.CliArgs;
import org.neo4j.shell.cli.Encryption;
import org.neo4j.shell.system.Utils;

import static org.junit.Assert.assertEquals;
Expand Down Expand Up @@ -275,7 +276,7 @@ public void promptsSilentlyForPassIfUserExistsIfOutputRedirected() throws Except
@Test
public void promptsForNewPasswordIfPasswordChangeRequired() throws Exception {
// Use a real ConnectionConfig instead of the mock in this test
ConnectionConfig connectionConfig = new ConnectionConfig("", "", 0, "", "", false, "");
ConnectionConfig connectionConfig = new ConnectionConfig("", "", 0, "", "", Encryption.DEFAULT, "");
doThrow(authException).doThrow(passwordChangeRequiredException).doNothing().when(shell).connect(connectionConfig, null);

String inputString = "bob\nsecret\nnewsecret\n";
Expand All @@ -300,7 +301,7 @@ public void promptsForNewPasswordIfPasswordChangeRequired() throws Exception {
@Test
public void promptsForNewPasswordIfPasswordChangeRequiredCannotBeEmpty() throws Exception {
// Use a real ConnectionConfig instead of the mock in this test
ConnectionConfig connectionConfig = new ConnectionConfig("", "", 0, "", "", false, "");
ConnectionConfig connectionConfig = new ConnectionConfig("", "", 0, "", "", Encryption.DEFAULT, "");
doThrow(authException).doThrow(passwordChangeRequiredException).doNothing().when(shell).connect(connectionConfig, null);

String inputString = "bob\nsecret\n\nnewsecret\n";
Expand Down
Loading

0 comments on commit a1fb500

Please sign in to comment.