diff --git a/community/security/src/main/java/org/neo4j/commandline/admin/security/SetPasswordCommand.java b/community/security/src/main/java/org/neo4j/commandline/admin/security/SetPasswordCommand.java index 0aa5248c984a..b47f86cf4c63 100644 --- a/community/security/src/main/java/org/neo4j/commandline/admin/security/SetPasswordCommand.java +++ b/community/security/src/main/java/org/neo4j/commandline/admin/security/SetPasswordCommand.java @@ -31,6 +31,8 @@ import org.neo4j.commandline.admin.OutsideWorld; import org.neo4j.dbms.DatabaseManagementSystemSettings; import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.helpers.Args; +import org.neo4j.kernel.api.security.exception.InvalidArgumentsException; import org.neo4j.kernel.configuration.Config; import org.neo4j.logging.NullLogProvider; import org.neo4j.server.configuration.ConfigLoader; @@ -55,7 +57,7 @@ public Provider() @Override public Optional arguments() { - return Optional.of( " " ); + return Optional.of( "--create " ); } @Override @@ -83,12 +85,15 @@ public SetPasswordCommand( Path homeDir, Path configDir ) @Override public void execute( String[] args ) throws IncorrectUsage, CommandFailed { - if ( args.length < 2 ) + Args parsedArgs = Args.parse( args ); + if ( parsedArgs.orphans().size() < 2 ) { throw new IncorrectUsage( "Missing arguments: expected username and password" ); } - String username = args[0]; - String password = args[1]; + + String username = parsedArgs.orphans().get( 0 ); + String password = parsedArgs.orphans().get( 1 ); + boolean shouldCreate = parsedArgs.asMap().containsKey( "create" ); try { Config config = loadNeo4jConfig( homeDir, configDir ); @@ -98,7 +103,19 @@ public void execute( String[] args ) throws IncorrectUsage, CommandFailed userRepository.start(); PasswordPolicy passwordPolicy = new BasicPasswordPolicy(); BasicAuthManager authManager = new BasicAuthManager( userRepository, passwordPolicy, systemUTC() ); - authManager.setUserPassword( username, password ); + try + { + authManager.setUserPassword( username, password ); + } + catch ( InvalidArgumentsException e ) + { + if ( shouldCreate ) + { + authManager.getUserManager().newUser( username, password, false ); + } else { + throw e; + } + } } catch ( Exception e ) { @@ -106,7 +123,8 @@ public void execute( String[] args ) throws IncorrectUsage, CommandFailed } catch ( Throwable t ) { - throw new CommandFailed( "Failed to set password for '" + username + "': " + t.getMessage(), new RuntimeException(t.getMessage()) ); + throw new CommandFailed( "Failed to set password for '" + username + "': " + t.getMessage(), + new RuntimeException( t.getMessage() ) ); } } diff --git a/community/security/src/test/java/org/neo4j/commandline/admin/security/SetPasswordCommandTest.java b/community/security/src/test/java/org/neo4j/commandline/admin/security/SetPasswordCommandTest.java index 1f8b9d1f838d..c0636be31f91 100644 --- a/community/security/src/test/java/org/neo4j/commandline/admin/security/SetPasswordCommandTest.java +++ b/community/security/src/test/java/org/neo4j/commandline/admin/security/SetPasswordCommandTest.java @@ -112,7 +112,7 @@ public void shouldFailWitNonExistingUser() throws Exception } @Test - public void shouldRunSetPasswordCommand() throws Throwable + public void shouldRunSetPasswordCommandWithExistinguser() throws Throwable { // Given - new user that requires password change File graphDir = testDir.graphDbDir(); @@ -128,6 +128,41 @@ public void shouldRunSetPasswordCommand() throws Throwable assertUserDoesNotRequirePasswordChange( "neo4j" ); } + @Test + public void shouldRunSetPasswordCommandWithoutExistingUser() throws Throwable + { + // Given - no user + File graphDir = testDir.graphDbDir(); + + // When - the admin command sets the password + File confDir = new File( graphDir, "conf" ); + SetPasswordCommand setPasswordCommand = new SetPasswordCommand( graphDir.toPath(), confDir.toPath() ); + setPasswordCommand.execute( new String[]{"neo4j", "abc", "--create"} ); + + // Then - the new user no longer requires a password change + assertUserDoesNotRequirePasswordChange( "neo4j" ); + } + + @Test + public void shouldFailToRunSetPasswordCommandWithoutExistingUser() throws Throwable + { + // Given - no user + File graphDir = testDir.graphDbDir(); + + // When - the admin command sets the password + try + { + File confDir = new File( graphDir, "conf" ); + SetPasswordCommand setPasswordCommand = new SetPasswordCommand( graphDir.toPath(), confDir.toPath() ); + setPasswordCommand.execute( new String[]{"neo4j", "abc"} ); + } + catch ( CommandFailed e ) + { + // Then we get an error + assertThat( e.getMessage(), containsString( "does not exist" ) ); + } + } + @Test public void shouldRunAdminToolWithSetPasswordCommandAndNoArgs() throws Throwable { @@ -144,7 +179,7 @@ public void shouldRunAdminToolWithSetPasswordCommandAndNoArgs() throws Throwable // Then we get error output and user still requires password change verify( out, times( 0 ) ).stdOutLine( anyString() ); - verify( out ).stdErrLine( "neo4j-admin set-password " ); + verify( out ).stdErrLine( "neo4j-admin set-password --create " ); verify( out ).stdErrLine( " Sets the password for the specified user and removes the password change " ); verify( out ).stdErrLine( " requirement" ); verify( out ).stdErrLine( "Missing arguments: expected username and password" ); @@ -152,7 +187,24 @@ public void shouldRunAdminToolWithSetPasswordCommandAndNoArgs() throws Throwable } @Test - public void shouldRunAdminToolWithSetPasswordCommandAndCorrectArgs() throws Throwable + public void shouldRunAdminToolWithSetPasswordCommandAndArgsButNoUser() throws Throwable + { + // Given no existing user + + // When running the neo4j-admin tool without --create parameter + Path homeDir = testDir.graphDbDir().toPath(); + Path configDir = testDir.directory( "conf" ).toPath(); + OutsideWorld out = mock( OutsideWorld.class ); + AdminTool tool = new AdminTool( CommandLocator.fromServiceLocator(), out, true ); + tool.execute( homeDir, configDir, "set-password", "neo4j", "abc" ); + + // Then we get error output and user still requires password change + verify( out, times( 0 ) ).stdOutLine( anyString() ); + verify( out ).stdErrLine( "command failed: Failed to set password for 'neo4j': User 'neo4j' does not exist" ); + } + + @Test + public void shouldRunAdminToolWithSetPasswordCommandAndExistingUser() throws Throwable { // Given a user that requires password change createTestUser("neo4j", "neo4j"); @@ -172,6 +224,26 @@ public void shouldRunAdminToolWithSetPasswordCommandAndCorrectArgs() throws Thro assertUserDoesNotRequirePasswordChange( "neo4j" ); } + @Test + public void shouldRunAdminToolWithSetPasswordCommandAndNoExistingUser() throws Throwable + { + // Given no previously existing user + + // When running the neo4j-admin tool with correct parameters + Path homeDir = testDir.graphDbDir().toPath(); + Path configDir = testDir.directory( "conf" ).toPath(); + OutsideWorld out = mock( OutsideWorld.class ); + AdminTool tool = new AdminTool( CommandLocator.fromServiceLocator(), out, true ); + tool.execute( homeDir, configDir, "set-password", "--create=true", "neo4j", "abc" ); + + // Then we get no error output and the user no longer requires password change + verify( out, times( 0 ) ).stdOutLine( anyString() ); + verify( out, times( 0 ) ).stdOutLine( anyString() ); + verify( out, times( 0 ) ).stdErrLine( anyString() ); + verify( out ).exit(0); + assertUserDoesNotRequirePasswordChange( "neo4j" ); + } + private File authFile() { return new File( new File( new File( testDir.graphDbDir(), "data" ), "dbms" ), "auth.db" );