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

Commit 2efa050

Browse files
authored
Merge pull request #177 from henriknyman/4.0-update-java-driver-beta03
Update Java driver to 4.0.0-beta03
2 parents 7fe1bb5 + edb593b commit 2efa050

21 files changed

+366
-108
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ ext {
7070
argparse4jVersion = '0.7.0'
7171
junitVersion = '4.12'
7272
evaluatorVersion = '3.5.4'
73-
neo4jJavaDriverVersion = '4.0.0-beta01'
73+
neo4jJavaDriverVersion = '4.0.0-beta03'
7474
findbugsVersion = '3.0.0'
7575
jansiVersion = '1.13'
7676
jlineVersion = '2.14.6'

cypher-shell/src/integration-test/java/org/neo4j/shell/MainIntegrationTest.java

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.io.FileNotFoundException;
1111
import java.io.InputStream;
1212
import java.io.PrintStream;
13+
import java.nio.ByteBuffer;
1314

1415
import org.neo4j.driver.exceptions.ClientException;
1516
import org.neo4j.driver.exceptions.ServiceUnavailableException;
@@ -26,12 +27,14 @@
2627
import static java.lang.String.format;
2728
import static org.hamcrest.CoreMatchers.isA;
2829
import static org.junit.Assert.assertEquals;
30+
import static org.junit.Assert.assertNull;
2931
import static org.junit.Assert.assertTrue;
3032
import static org.junit.Assert.fail;
3133
import static org.mockito.Matchers.any;
3234
import static org.mockito.Mockito.mock;
3335
import static org.mockito.Mockito.verify;
3436
import static org.mockito.Mockito.verifyNoMoreInteractions;
37+
import static org.neo4j.shell.util.Versions.majorVersion;
3538

3639
public class MainIntegrationTest
3740
{
@@ -53,22 +56,26 @@ private static class ShellAndConnection
5356
private String inputString = String.format( "neo4j%nneo%n" );
5457
private ByteArrayOutputStream baos;
5558
private ConnectionConfig connectionConfig;
59+
private CliArgs cliArgs;
5660
private CypherShell shell;
5761
private Main main;
5862
private PrintStream printStream;
5963
private InputStream inputStream;
64+
private ByteBuffer inputBuffer;
6065

6166
@Before
6267
public void setup() {
6368
// given
64-
inputStream = new ByteArrayInputStream(inputString.getBytes());
69+
inputBuffer = ByteBuffer.allocate(256);
70+
inputBuffer.put(inputString.getBytes());
71+
inputStream = new ByteArrayInputStream(inputBuffer.array());
6572

6673
baos = new ByteArrayOutputStream();
6774
printStream = new PrintStream(baos);
6875

6976
main = new Main(inputStream, printStream);
7077

71-
CliArgs cliArgs = new CliArgs();
78+
cliArgs = new CliArgs();
7279
cliArgs.setUsername("", "");
7380
cliArgs.setPassword("", "");
7481

@@ -77,6 +84,24 @@ public void setup() {
7784
connectionConfig = sac.connectionConfig;
7885
}
7986

87+
private void ensureUser() throws Exception {
88+
if (majorVersion(shell.getServerVersion() ) >= 4) {
89+
shell.execute(":use " + DatabaseManager.SYSTEM_DB_NAME);
90+
shell.execute("CREATE OR REPLACE USER foo SET PASSWORD 'pass';");
91+
shell.execute("GRANT ROLE reader TO foo;");
92+
shell.execute(":use");
93+
} else {
94+
try {
95+
shell.execute("CALL dbms.security.createUser('foo', 'pass', true)");
96+
} catch (ClientException e) {
97+
if (e.code().equalsIgnoreCase("Neo.ClientError.General.InvalidArguments") && e.getMessage().contains("already exists")) {
98+
shell.execute("CALL dbms.security.deleteUser('foo')");
99+
shell.execute("CALL dbms.security.createUser('foo', 'pass', true)");
100+
}
101+
}
102+
}
103+
}
104+
80105
@Test
81106
public void promptsOnWrongAuthenticationIfInteractive() throws Exception {
82107
// when
@@ -94,6 +119,63 @@ public void promptsOnWrongAuthenticationIfInteractive() throws Exception {
94119
assertEquals("neo", connectionConfig.password());
95120
}
96121

122+
@Test
123+
public void promptsOnPasswordChangeRequired() throws Exception {
124+
shell.setCommandHelper(new CommandHelper(mock(Logger.class), Historian.empty, shell));
125+
inputBuffer.put(String.format("foo%npass%nnewpass%n").getBytes());
126+
127+
assertEquals("", connectionConfig.username());
128+
assertEquals("", connectionConfig.password());
129+
130+
// when
131+
main.connectMaybeInteractively(shell, connectionConfig, true, true);
132+
133+
// then
134+
// should be connected
135+
assertTrue(shell.isConnected());
136+
// should have prompted and set the username and password
137+
String expectedLoginOutput = format( "username: neo4j%npassword: ***%n" );
138+
assertEquals(expectedLoginOutput, baos.toString());
139+
assertEquals("neo4j", connectionConfig.username());
140+
assertEquals("neo", connectionConfig.password());
141+
142+
// Create a new user
143+
ensureUser();
144+
shell.disconnect();
145+
146+
connectionConfig = getConnectionConfig(cliArgs);
147+
assertEquals("", connectionConfig.username());
148+
assertEquals("", connectionConfig.password());
149+
150+
// when
151+
main.connectMaybeInteractively(shell, connectionConfig, true, true);
152+
153+
// then
154+
assertTrue(shell.isConnected());
155+
if (majorVersion(shell.getServerVersion() ) >= 4) {
156+
// should have prompted to change the password
157+
String expectedChangePasswordOutput = format( "username: foo%npassword: ****%nPassword change required%nnew password: *******%n" );
158+
assertEquals(expectedLoginOutput + expectedChangePasswordOutput, baos.toString());
159+
assertEquals("foo", connectionConfig.username());
160+
assertEquals("newpass", connectionConfig.password());
161+
assertNull(connectionConfig.newPassword());
162+
163+
// Should be able to execute read query
164+
shell.execute("MATCH (n) RETURN count(n)");
165+
} else {
166+
// in 3.x we do not get credentials expired exception on connection, but when we try to access data
167+
String expectedChangePasswordOutput = format( "username: foo%npassword: ****%n" );
168+
assertEquals(expectedLoginOutput + expectedChangePasswordOutput, baos.toString());
169+
assertEquals("foo", connectionConfig.username());
170+
assertEquals("pass", connectionConfig.password());
171+
172+
// Should get exception with instructions on how to change password using procedure
173+
exception.expect(ClientException.class);
174+
exception.expectMessage("CALL dbms.changePassword");
175+
shell.execute("MATCH (n) RETURN count(n)");
176+
}
177+
}
178+
97179
@Test
98180
public void doesNotPromptToStdOutOnWrongAuthenticationIfOutputRedirected() throws Exception {
99181
// when
@@ -335,16 +417,21 @@ private ShellAndConnection getShell( CliArgs cliArgs )
335417
private ShellAndConnection getShell( CliArgs cliArgs, LinePrinter linePrinter )
336418
{
337419
PrettyConfig prettyConfig = new PrettyConfig( cliArgs );
338-
ConnectionConfig connectionConfig = new ConnectionConfig(
420+
ConnectionConfig connectionConfig = getConnectionConfig( cliArgs );
421+
422+
return new ShellAndConnection( new CypherShell( linePrinter, prettyConfig, true, new ShellParameterMap() ), connectionConfig );
423+
}
424+
425+
private ConnectionConfig getConnectionConfig( CliArgs cliArgs )
426+
{
427+
return new ConnectionConfig(
339428
cliArgs.getScheme(),
340429
cliArgs.getHost(),
341430
cliArgs.getPort(),
342431
cliArgs.getUsername(),
343432
cliArgs.getPassword(),
344433
cliArgs.getEncryption(),
345434
cliArgs.getDatabase() );
346-
347-
return new ShellAndConnection( new CypherShell( linePrinter, prettyConfig, true, new ShellParameterMap() ), connectionConfig );
348435
}
349436

350437
private void exit( CypherShell shell ) throws CommandException

cypher-shell/src/integration-test/java/org/neo4j/shell/commands/CypherShellIntegrationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import org.neo4j.shell.CypherShell;
66
import org.neo4j.shell.exception.CommandException;
77

8-
import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME;
8+
import static org.neo4j.shell.DatabaseManager.ABSENT_DB_NAME;
99

1010
abstract class CypherShellIntegrationTest
1111
{

cypher-shell/src/integration-test/java/org/neo4j/shell/commands/CypherShellMultiDatabaseIntegrationTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020
import static org.hamcrest.MatcherAssert.assertThat;
2121
import static org.junit.Assert.fail;
2222
import static org.junit.Assume.assumeTrue;
23-
import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME;
23+
import static org.neo4j.shell.DatabaseManager.ABSENT_DB_NAME;
2424
import static org.neo4j.shell.DatabaseManager.DEFAULT_DEFAULT_DB_NAME;
2525
import static org.neo4j.shell.DatabaseManager.SYSTEM_DB_NAME;
26-
import static org.neo4j.shell.Versions.majorVersion;
26+
import static org.neo4j.shell.util.Versions.majorVersion;
2727

2828
public class CypherShellMultiDatabaseIntegrationTest
2929
{

cypher-shell/src/integration-test/java/org/neo4j/shell/commands/CypherShellVerboseIntegrationTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
import static org.junit.Assert.assertEquals;
2222
import static org.junit.Assert.assertTrue;
2323
import static org.junit.Assume.assumeTrue;
24-
import static org.neo4j.shell.Versions.majorVersion;
25-
import static org.neo4j.shell.Versions.minorVersion;
24+
import static org.neo4j.shell.util.Versions.majorVersion;
25+
import static org.neo4j.shell.util.Versions.minorVersion;
2626

2727
public class CypherShellVerboseIntegrationTest extends CypherShellIntegrationTest {
2828
@Rule

cypher-shell/src/main/java/org/neo4j/shell/ConnectionConfig.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class ConnectionConfig {
1313
private final boolean encryption;
1414
private String username;
1515
private String password;
16+
private String newPassword;
1617
private String database;
1718

1819
public ConnectionConfig(@Nonnull String scheme,
@@ -67,6 +68,10 @@ public String password() {
6768
return password;
6869
}
6970

71+
public String newPassword() {
72+
return newPassword;
73+
}
74+
7075
@Nonnull
7176
public String driverUrl() {
7277
return String.format("%s%s:%d", scheme(), host(), port());
@@ -89,4 +94,12 @@ public void setUsername(@Nonnull String username) {
8994
public void setPassword(@Nonnull String password) {
9095
this.password = password;
9196
}
97+
98+
public void setNewPassword(@Nonnull String password) {
99+
this.newPassword = password;
100+
}
101+
102+
public boolean passwordChangeRequired() {
103+
return this.newPassword != null;
104+
}
92105
}

cypher-shell/src/main/java/org/neo4j/shell/CypherShell.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,11 @@ public String lastNeo4jErrorCode() {
9191
*/
9292
private void executeCypher(@Nonnull final String cypher) throws CommandException {
9393
try {
94-
final Optional<BoltResult> result = boltStateHandler.runCypher( cypher, parameterMap.allParameterValues() );
95-
result.ifPresent(boltResult -> prettyPrinter.format(boltResult, linePrinter));
94+
final Optional<BoltResult> result = boltStateHandler.runCypher(cypher, parameterMap.allParameterValues());
95+
result.ifPresent(boltResult -> {
96+
prettyPrinter.format(boltResult, linePrinter);
97+
boltStateHandler.updateActualDbName(boltResult.getSummary());
98+
});
9699
lastNeo4jErrorCode = null;
97100
} catch (Neo4jException e) {
98101
lastNeo4jErrorCode = e.code();
@@ -216,4 +219,15 @@ public ParameterMap getParameterMap()
216219
{
217220
return parameterMap;
218221
}
222+
223+
public void changePassword(@Nonnull ConnectionConfig connectionConfig) {
224+
boltStateHandler.changePassword(connectionConfig);
225+
}
226+
227+
/**
228+
* Used for testing purposes
229+
*/
230+
public void disconnect() {
231+
boltStateHandler.disconnect();
232+
}
219233
}

cypher-shell/src/main/java/org/neo4j/shell/Main.java

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import javax.annotation.Nullable;
1010

1111
import org.neo4j.driver.exceptions.AuthenticationException;
12+
import org.neo4j.driver.exceptions.Neo4jException;
1213
import org.neo4j.shell.build.Build;
1314
import org.neo4j.shell.cli.CliArgHelper;
1415
import org.neo4j.shell.cli.CliArgs;
@@ -122,23 +123,40 @@ void connectMaybeInteractively(@Nonnull CypherShell shell,
122123
didPrompt = true;
123124
}
124125

125-
try {
126-
// Try to connect
127-
shell.connect(connectionConfig);
128-
} catch (AuthenticationException e) {
129-
// Fail if we already prompted,
130-
// or do not have interactive input,
131-
// or already tried with both username and password
132-
if (didPrompt || !inputInteractive || (!connectionConfig.username().isEmpty() && !connectionConfig.password().isEmpty())) {
133-
throw e;
126+
while (true) {
127+
try {
128+
// Try to connect
129+
shell.connect(connectionConfig);
130+
131+
// If no exception occurred we are done
132+
break;
133+
} catch (AuthenticationException e) {
134+
// Fail if we already prompted,
135+
// or do not have interactive input,
136+
// or already tried with both username and password
137+
if (didPrompt || !inputInteractive || (!connectionConfig.username().isEmpty() && !connectionConfig.password().isEmpty())) {
138+
throw e;
139+
}
140+
141+
// Otherwise we prompt for username and password, and try to connect again
142+
promptForUsernameAndPassword(connectionConfig, outputInteractive);
143+
didPrompt = true;
144+
} catch (Neo4jException e) {
145+
if (passwordChangeRequiredException(e)) {
146+
promptForPasswordChange(connectionConfig, outputInteractive);
147+
shell.changePassword(connectionConfig);
148+
didPrompt = true;
149+
} else {
150+
throw e;
151+
}
134152
}
135-
136-
// Otherwise we prompt for username and password, and try to connect again
137-
promptForUsernameAndPassword(connectionConfig, outputInteractive);
138-
shell.connect(connectionConfig);
139153
}
140154
}
141155

156+
private boolean passwordChangeRequiredException(Neo4jException e) {
157+
return "Neo.ClientError.Security.CredentialsExpired".equalsIgnoreCase(e.code());
158+
}
159+
142160
private void promptForUsernameAndPassword(ConnectionConfig connectionConfig, boolean outputInteractive) throws Exception {
143161
OutputStream promptOutputStream = getOutputStreamForInteractivePrompt();
144162
ConsoleReader consoleReader = new ConsoleReader(in, promptOutputStream);
@@ -162,6 +180,35 @@ private void promptForUsernameAndPassword(ConnectionConfig connectionConfig, boo
162180
}
163181
}
164182

183+
private void promptForPasswordChange(ConnectionConfig connectionConfig, boolean outputInteractive) throws Exception {
184+
OutputStream promptOutputStream = getOutputStreamForInteractivePrompt();
185+
ConsoleReader consoleReader = new ConsoleReader(in, promptOutputStream);
186+
// Disable expansion of bangs: !
187+
consoleReader.setExpandEvents(false);
188+
// Ensure Reader does not handle user input for ctrl+C behaviour
189+
consoleReader.setHandleUserInterrupt(false);
190+
191+
consoleReader.println("Password change required");
192+
193+
try {
194+
if (connectionConfig.username().isEmpty()) {
195+
String username = outputInteractive ?
196+
promptForNonEmptyText("username", consoleReader, null) :
197+
promptForText("username", consoleReader, null);
198+
connectionConfig.setUsername(username);
199+
}
200+
if (connectionConfig.password().isEmpty()) {
201+
connectionConfig.setPassword(promptForText("password", consoleReader, '*'));
202+
}
203+
String newPassword = outputInteractive ?
204+
promptForNonEmptyText("new password", consoleReader, '*') :
205+
promptForText("new password", consoleReader, '*');
206+
connectionConfig.setNewPassword(newPassword);
207+
} finally {
208+
consoleReader.close();
209+
}
210+
}
211+
165212
/**
166213
* @param prompt
167214
* to display to the user

cypher-shell/src/main/java/org/neo4j/shell/cli/CliArgs.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import org.neo4j.shell.ParameterMap;
88
import org.neo4j.shell.ShellParameterMap;
99

10-
import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME;
10+
import static org.neo4j.shell.DatabaseManager.ABSENT_DB_NAME;
1111

1212
public class CliArgs {
1313
private static final String DEFAULT_SCHEME = "bolt://";

0 commit comments

Comments
 (0)