Skip to content

Commit

Permalink
Fixed bug on removing user assigned a role and set-password
Browse files Browse the repository at this point in the history
* Deleting a user now also removes it from any role it is assigned.
* Fixed --requires-password-change being ignored in community,
  with true being default as is the case with the procedure.
  • Loading branch information
OliviaYtterbrink committed Sep 28, 2016
1 parent 01e68cf commit bb0568c
Show file tree
Hide file tree
Showing 15 changed files with 359 additions and 244 deletions.
Expand Up @@ -19,12 +19,16 @@
*/
package org.neo4j.commandline.admin.security;

import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.stream.StreamSupport;

import org.neo4j.commandline.admin.AdminCommand;
import org.neo4j.commandline.admin.CommandFailed;
Expand All @@ -33,17 +37,15 @@
import org.neo4j.dbms.DatabaseManagementSystemSettings;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.helpers.Args;
import org.neo4j.io.fs.DefaultFileSystemAbstraction;
import org.neo4j.io.fs.DelegateFileSystemAbstraction;
import org.neo4j.helpers.Service;
import org.neo4j.kernel.api.security.AuthManager;
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.impl.util.JobScheduler;
import org.neo4j.logging.NullLog;
import org.neo4j.logging.NullLogProvider;
import org.neo4j.server.configuration.ConfigLoader;
import org.neo4j.server.security.auth.BasicAuthManager;
import org.neo4j.server.security.auth.BasicAuthManagerFactory;
import org.neo4j.server.security.auth.BasicPasswordPolicy;
import org.neo4j.server.security.auth.FileUserRepository;
import org.neo4j.server.security.auth.PasswordPolicy;
import org.neo4j.time.Clocks;
import org.neo4j.server.security.auth.UserManager;
import org.neo4j.server.security.auth.UserManagerSupplier;

public class UsersCommand implements AdminCommand
{
Expand Down Expand Up @@ -77,9 +79,9 @@ public AdminCommand create( Path homeDir, Path configDir, OutsideWorld outsideWo
}
}

private final Path homeDir;
private final Path configDir;
private OutsideWorld outsideWorld;
final Path homeDir;
final Path configDir;
OutsideWorld outsideWorld;

public UsersCommand( Path homeDir, Path configDir, OutsideWorld outsideWorld )
{
Expand Down Expand Up @@ -161,9 +163,8 @@ private boolean hasFlagWithValue( Args parsedArgs, String key, String expectedVa

private void listUsers( String contains ) throws Throwable
{
getAuthManager(); // ensure defaults are created
FileUserRepository userRepository = getUserRepository();
for ( String username : userRepository.getAllUsernames() )
UserManager userManager = getUserManager();
for ( String username : userManager.getAllUsernames() )
{
if ( contains == null || username.toLowerCase().contains( contains.toLowerCase() ) )
{
Expand All @@ -174,20 +175,20 @@ private void listUsers( String contains ) throws Throwable

private void createUser( String username, String password, boolean requiresPasswordChange ) throws Throwable
{
BasicAuthManager authManager = getAuthManager();
authManager.newUser( username, password, requiresPasswordChange );
UserManager userManager = getUserManager();
userManager.newUser( username, password, requiresPasswordChange );
outsideWorld.stdOutLine( "Created new user '" + username + "'" );
}

private void deleteUser( String username ) throws Throwable
{
BasicAuthManager authManager = getAuthManager();
authManager.getUser( username ); // Will throw error on missing user
if ( authManager.getAllUsernames().size() == 1 )
UserManager userManager = getUserManager();
userManager.getUser( username ); // Will throw error on missing user
if ( userManager.getAllUsernames().size() == 1 )
{
throw new IllegalArgumentException( "Deleting the only remaining user '" + username + "' is not allowed" );
}
if ( authManager.deleteUser( username ) )
if ( userManager.deleteUser( username ) )
{
outsideWorld.stdOutLine( "Deleted user '" + username + "'" );
}
Expand All @@ -199,8 +200,8 @@ private void deleteUser( String username ) throws Throwable

private void setPassword( String username, String password, boolean requirePasswordChange ) throws Throwable
{
BasicAuthManager authManager = getAuthManager();
authManager.setUserPassword( username, password, requirePasswordChange );
UserManager userManager = getUserManager();
userManager.setUserPassword( username, password, requirePasswordChange );
outsideWorld.stdOutLine( "Changed password for user '" + username + "'" );
}

Expand All @@ -220,22 +221,154 @@ private static List<Class<?>> settings()
return settings;
}

private FileUserRepository getUserRepository() throws Throwable
private UserManager getUserManager() throws Throwable
{
Config config = loadNeo4jConfig( homeDir, configDir );
FileUserRepository userRepository =
BasicAuthManagerFactory.getUserRepository( config,
NullLogProvider.getInstance(), outsideWorld.fileSystem() );
userRepository.start();
return userRepository;
String configuredKey = config.get( GraphDatabaseSettings.auth_manager );
List<AuthManager.Factory> wantedAuthManagerFactories = new ArrayList<>();
List<AuthManager.Factory> backupAuthManagerFactories = new ArrayList<>();

for ( AuthManager.Factory candidate : Service.load( AuthManager.Factory.class ) )
{
if ( StreamSupport.stream( candidate.getKeys().spliterator(), false ).anyMatch( configuredKey::equals ) )
{
wantedAuthManagerFactories.add( candidate );
}
else
{
backupAuthManagerFactories.add( candidate );
}
}

AuthManager authManager = tryMakeInOrder( config, wantedAuthManagerFactories );

if ( authManager == null )
{
authManager = tryMakeInOrder( config, backupAuthManagerFactories );
}

if ( authManager == null )
{
outsideWorld.stdErrLine( "No auth manager implementation specified and no default could be loaded. " +
"It is an illegal product configuration to have auth enabled and not provide an auth manager service." );
throw new IllegalArgumentException(
"Auth enabled but no auth manager found. This is an illegal product configuration." );
}
if ( authManager instanceof UserManagerSupplier )
{
authManager.start();
return ((UserManagerSupplier) authManager).getUserManager();
}
else
{
outsideWorld.stdErrLine( "The configured auth manager doesn't handle user management." );
throw new IllegalArgumentException( "The configured auth manager doesn't handle user management." );
}
}

private AuthManager tryMakeInOrder( Config config, List<AuthManager.Factory> authManagerFactories )
{
JobScheduler jobScheduler = new NoOpJobScheduler();
for ( AuthManager.Factory x : authManagerFactories )
{
try
{
return x.newInstance( config, NullLogProvider.getInstance(), NullLog.getInstance(),
outsideWorld.fileSystem(), jobScheduler );
}
catch ( Exception e )
{
outsideWorld.stdOutLine(
String.format( "Attempted to load configured auth manager with keys '%s', but failed",
String.join( ", ", x.getKeys() ) ) );
}
}
return null;
}

private BasicAuthManager getAuthManager() throws Throwable
public static class NoOpJobScheduler implements JobScheduler
{
FileUserRepository userRepository = getUserRepository();
PasswordPolicy passwordPolicy = new BasicPasswordPolicy();
BasicAuthManager authManager = new BasicAuthManager( userRepository, passwordPolicy, Clocks.systemClock() );
authManager.start(); // required to setup default users
return authManager;

@Override
public void init() throws Throwable
{

}

@Override
public void start() throws Throwable
{

}

@Override
public void stop() throws Throwable
{

}

@Override
public void shutdown() throws Throwable
{

}

@Override
public Executor executor( Group group )
{
return null;
}

@Override
public ThreadFactory threadFactory( Group group )
{
return null;
}

@Override
public JobHandle schedule( Group group, Runnable job )
{
return new NoOpJobHandle();
}

@Override
public JobHandle schedule( Group group, Runnable job, Map<String,String> metadata )
{
return new NoOpJobHandle();
}

@Override
public JobHandle schedule( Group group, Runnable runnable, long initialDelay, TimeUnit timeUnit )
{
return new NoOpJobHandle();
}

@Override
public JobHandle scheduleRecurring( Group group, Runnable runnable, long period, TimeUnit timeUnit )
{
return new NoOpJobHandle();
}

@Override
public JobHandle scheduleRecurring( Group group, Runnable runnable, long initialDelay, long period, TimeUnit timeUnit )
{
return new NoOpJobHandle();
}

public static class NoOpJobHandle implements JobHandle
{

@Override
public void cancel( boolean mayInterruptIfRunning )
{

}

@Override
public void waitTermination() throws InterruptedException, ExecutionException
{

}
}
}
}
Expand Up @@ -25,19 +25,14 @@
import java.util.Set;

import org.neo4j.graphdb.security.AuthorizationViolationException;
import org.neo4j.kernel.api.exceptions.InvalidArgumentsException;
import org.neo4j.kernel.api.security.AuthManager;
import org.neo4j.kernel.api.security.AuthSubject;
import org.neo4j.kernel.api.security.AuthToken;
import org.neo4j.kernel.api.security.AuthenticationResult;
import org.neo4j.kernel.api.exceptions.InvalidArgumentsException;
import org.neo4j.kernel.api.security.exception.InvalidAuthTokenException;
import org.neo4j.server.security.auth.exception.ConcurrentModificationException;

import static org.neo4j.kernel.api.security.AuthToken.BASIC_SCHEME;
import static org.neo4j.kernel.api.security.AuthToken.NATIVE_REALM;
import static org.neo4j.kernel.api.security.AuthToken.REALM_KEY;
import static org.neo4j.kernel.api.security.AuthToken.SCHEME_KEY;

/**
* Manages server authentication and authorization.
* <p>
Expand Down Expand Up @@ -185,7 +180,7 @@ public void setUserPassword( String username, String password, boolean requirePa
{
User updatedUser = existingUser.augment()
.withCredentials( Credential.forPassword( password ) )
.withRequiredPasswordChange( false )
.withRequiredPasswordChange( requirePasswordChange )
.build();
userRepository.update( existingUser, updatedUser );
} catch ( ConcurrentModificationException e )
Expand Down
Expand Up @@ -22,18 +22,16 @@
import org.junit.Before;

import java.io.File;
import java.nio.file.FileSystems;

import org.neo4j.commandline.admin.AdminTool;
import org.neo4j.commandline.admin.CommandLocator;
import org.neo4j.commandline.admin.OutsideWorld;
import org.neo4j.io.fs.DelegateFileSystemAbstraction;
import org.neo4j.graphdb.mockfs.EphemeralFileSystemAbstraction;
import org.neo4j.io.fs.FileSystemAbstraction;
import org.neo4j.logging.NullLogProvider;
import org.neo4j.server.security.auth.Credential;
import org.neo4j.server.security.auth.FileUserRepository;
import org.neo4j.server.security.auth.User;
import org.neo4j.test.rule.TestDirectory;

import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.contains;
Expand All @@ -45,8 +43,7 @@

abstract class CommandTestBase
{
protected TestDirectory testDir = TestDirectory.testDirectory();
protected FileSystemAbstraction fileSystem = new DelegateFileSystemAbstraction( FileSystems.getDefault() );
protected FileSystemAbstraction fileSystem = new EphemeralFileSystemAbstraction( );

File graphDir;
File confDir;
Expand All @@ -57,24 +54,14 @@ abstract class CommandTestBase
@Before
public void setup()
{
graphDir = testDir.graphDbDir();
confDir = ensureDir( "conf" );
homeDir = ensureDir( "home" );
graphDir = new File( "graph-db" );
confDir = new File( graphDir, "conf" );
homeDir = new File( graphDir, "home" );
out = mock( OutsideWorld.class );
resetOutsideWorldMock();
tool = new AdminTool( CommandLocator.fromServiceLocator(), out, true );
}

protected File ensureDir( String name )
{
File dir = new File( graphDir, name );
if ( !dir.exists() )
{
dir.mkdirs();
}
return dir;
}

protected void resetOutsideWorldMock()
{
reset(out);
Expand All @@ -83,7 +70,7 @@ protected void resetOutsideWorldMock()

private File authFile()
{
return new File( new File( new File( testDir.graphDbDir(), "data" ), "dbms" ), "auth" );
return new File( new File( new File( graphDir, "data" ), "dbms" ), "auth" );
}

User createTestUser( String username, String password ) throws Throwable
Expand Down
Expand Up @@ -19,9 +19,7 @@
*/
package org.neo4j.commandline.admin.security;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;

import org.neo4j.commandline.admin.CommandFailed;

Expand All @@ -34,9 +32,6 @@

public class CreateCommandTest extends UsersCommandTestBase
{
@Rule
public RuleChain ruleChain = RuleChain.outerRule( testDir );

@Test
public void shouldFailToCreateExistingUser() throws Throwable
{
Expand Down

0 comments on commit bb0568c

Please sign in to comment.