Skip to content

Commit

Permalink
Reload auth and roles files on change
Browse files Browse the repository at this point in the history
  • Loading branch information
OliviaYtterbrink authored and fickludd committed Sep 11, 2016
1 parent a92a136 commit 7b18926
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 14 deletions.
Expand Up @@ -42,6 +42,13 @@ public abstract class AbstractUserRepository extends LifecycleAdapter implements

private final Pattern usernamePattern = Pattern.compile( "^[a-zA-Z0-9_]+$" );

@Override
public void clear()
{
users.clear();
usersByName.clear();
}

@Override
public User getUserByName( String username )
{
Expand Down Expand Up @@ -82,6 +89,13 @@ public void create( User user ) throws InvalidArgumentsException, IOException
*/
protected abstract void saveUsers() throws IOException;

/**
* Override this in the implementing class to persist users
*
* @throws IOException
*/
abstract void loadUsers() throws IOException;

@Override
public void update( User existingUser, User updatedUser )
throws ConcurrentModificationException, IOException, InvalidArgumentsException
Expand Down
Expand Up @@ -54,8 +54,12 @@ public FileUserRepository( FileSystemAbstraction fileSystem, File file, LogProvi
@Override
public void start() throws Throwable
{
users.clear();
usersByName.clear();
clear();
loadUsers();
}

void loadUsers() throws IOException
{
if ( fileSystem.fileExists( authFile ) )
{
List<User> loadedUsers;
Expand All @@ -70,6 +74,7 @@ public void start() throws Throwable
throw new IllegalStateException( "Failed to read authentication file: " + authFile );
}

clear();
users = loadedUsers;
for ( User user : users )
{
Expand All @@ -83,4 +88,13 @@ protected void saveUsers() throws IOException
{
serialization.saveRecordsToFile( fileSystem, authFile, users );
}

@Override
public void reloadIfNeeded() throws IOException
{
if ( lastLoaded < fileSystem.lastModifiedTime( authFile ) )
{
loadUsers();
}
}
}
Expand Up @@ -31,6 +31,16 @@
*/
public interface UserRepository extends Lifecycle
{
/**
* Clears all cached user data.
*/
void clear();

/**
* Return the user associated with the given username.
* @param username the username
* @return the associated user, or null if no user exists
*/
User getUserByName( String username );

/**
Expand Down Expand Up @@ -66,4 +76,6 @@ void update( User existingUser, User updatedUser )
boolean isValidUsername( String username );

Set<String> getAllUsernames();

void reloadIfNeeded() throws IOException;
}
Expand Up @@ -19,6 +19,7 @@
*/
package org.neo4j.server.security.auth;

import org.neo4j.io.fs.FileSystemAbstraction;
import org.neo4j.server.security.auth.exception.FormatException;
import org.neo4j.string.HexString;

Expand Down
Expand Up @@ -29,4 +29,16 @@ protected void saveUsers() throws IOException
{
// Nothing to do
}

@Override
void loadUsers() throws IOException
{
// Nothing to do
}

@Override
public void reloadIfNeeded()
{
// Nothing to do
}
}
Expand Up @@ -32,6 +32,7 @@
import java.util.stream.Collectors;

import org.neo4j.kernel.lifecycle.LifecycleAdapter;
import org.neo4j.server.security.auth.UserRepository;
import org.neo4j.server.security.auth.exception.ConcurrentModificationException;

public abstract class AbstractRoleRepository extends LifecycleAdapter implements RoleRepository
Expand All @@ -47,6 +48,14 @@ public abstract class AbstractRoleRepository extends LifecycleAdapter implements

private final Pattern roleNamePattern = Pattern.compile( "^[a-zA-Z0-9_]+$" );

@Override
public void clear()
{
roles.clear();
rolesByName.clear();
rolesByUsername.clear();
}

@Override
public RoleRecord getRoleByName( String roleName )
{
Expand Down Expand Up @@ -172,6 +181,8 @@ public synchronized boolean delete( RoleRecord role ) throws IOException
*/
protected abstract void saveRoles() throws IOException;

protected abstract void loadRoles() throws IOException;

@Override
public synchronized int numberOfRoles()
{
Expand Down Expand Up @@ -208,6 +219,13 @@ public synchronized Set<String> getAllRoleNames()
return roles.stream().map( RoleRecord::name ).collect( Collectors.toSet() );
}

@Override
public boolean validateAgainst( UserRepository userRepository )
{
return rolesByUsername.keySet().stream()
.allMatch( username -> userRepository.getUserByName( username ) != null );
}

protected void populateUserMap( RoleRecord role )
{
for ( String username : role.users() )
Expand Down
Expand Up @@ -39,6 +39,8 @@ public class FileRoleRepository extends AbstractRoleRepository
private final RoleSerialization serialization = new RoleSerialization();
private final FileSystemAbstraction fileSystem;

private long lastLoaded;

public FileRoleRepository( FileSystemAbstraction fileSystem, File file, LogProvider logProvider )
{
this.roleFile = file;
Expand All @@ -48,12 +50,20 @@ public FileRoleRepository( FileSystemAbstraction fileSystem, File file, LogProvi

@Override
public void start() throws Throwable
{
clear();
loadRoles();
}

@Override
protected void loadRoles() throws IOException
{
if ( fileSystem.fileExists( roleFile ) )
{
List<RoleRecord> loadedRoles;
try
{
lastLoaded = fileSystem.lastModifiedTime( roleFile );
loadedRoles = serialization.loadRecordsFromFile( fileSystem, roleFile );
}
catch ( FormatException e )
Expand All @@ -62,6 +72,7 @@ public void start() throws Throwable
throw new IllegalStateException( "Failed to read role file '" + roleFile + "'." );
}

clear();
roles = loadedRoles;
for ( RoleRecord role : roles )
{
Expand All @@ -77,4 +88,13 @@ protected void saveRoles() throws IOException
{
serialization.saveRecordsToFile( fileSystem, roleFile, roles );
}

@Override
public void reloadIfNeeded() throws IOException
{
if ( lastLoaded < fileSystem.lastModifiedTime( roleFile ) )
{
loadRoles();
}
}
}
Expand Up @@ -45,6 +45,9 @@
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.neo4j.kernel.api.security.AuthToken;
import org.neo4j.kernel.api.security.AuthenticationResult;
Expand All @@ -70,6 +73,8 @@ public class InternalFlatFileRealm extends AuthorizingRealm implements RealmLife
*/
public static final String IS_SUSPENDED = "is_suspended";

private int RELOAD_ATTEMPTS = 10;

private final RolePermissionResolver rolePermissionResolver = new RolePermissionResolver()
{
@Override
Expand All @@ -96,8 +101,8 @@ public Collection<Permission> resolvePermissionsInRole( String roleString )
private final Map<String,SimpleRole> roles;

public InternalFlatFileRealm( UserRepository userRepository, RoleRepository roleRepository,
PasswordPolicy passwordPolicy, AuthenticationStrategy authenticationStrategy,
boolean authenticationEnabled, boolean authorizationEnabled )
PasswordPolicy passwordPolicy, AuthenticationStrategy authenticationStrategy, boolean authenticationEnabled,
boolean authorizationEnabled, ScheduledExecutorService executorService )
{
super();

Expand All @@ -113,6 +118,38 @@ public InternalFlatFileRealm( UserRepository userRepository, RoleRepository role
setRolePermissionResolver( rolePermissionResolver );

roles = new PredefinedRolesBuilder().buildRoles();

executorService.scheduleAtFixedRate( () -> tryReload( RELOAD_ATTEMPTS ), 5, 5, TimeUnit.SECONDS );
}

public InternalFlatFileRealm( UserRepository userRepository, RoleRepository roleRepository,
PasswordPolicy passwordPolicy, AuthenticationStrategy authenticationStrategy, boolean authenticationEnabled,
boolean authorizationEnabled )
{
this( userRepository, roleRepository, passwordPolicy, authenticationStrategy, authenticationEnabled,
authorizationEnabled, new ScheduledThreadPoolExecutor( 1 ) );
}

private void tryReload( int attemptLeft )
{
if ( attemptLeft < 0 )
{
throw new RuntimeException( "Unable to load valid flat file repositories!" );
}

try
{
userRepository.reloadIfNeeded();
roleRepository.reloadIfNeeded();
if ( !roleRepository.validateAgainst( userRepository ) )
{
tryReload( attemptLeft - 1 );
}
}
catch ( IOException ioe )
{
tryReload( attemptLeft - 1 );
}
}

public InternalFlatFileRealm( UserRepository userRepository, RoleRepository roleRepository,
Expand Down Expand Up @@ -200,7 +237,7 @@ public boolean supports( AuthenticationToken token )
}
return false;
}
catch( InvalidAuthTokenException e )
catch ( InvalidAuthTokenException e )
{
return false;
}
Expand Down Expand Up @@ -473,7 +510,8 @@ public void setUserPassword( String username, String password, boolean requirePa
.withRequiredPasswordChange( requirePasswordChange )
.build();
userRepository.update( existingUser, updatedUser );
} catch ( ConcurrentModificationException e )
}
catch ( ConcurrentModificationException e )
{
// try again
setUserPassword( username, password, requirePasswordChange );
Expand Down Expand Up @@ -578,8 +616,7 @@ private void assertValidUsername( String name ) throws InvalidArgumentsException
if ( !userRepository.isValidUsername( name ) )
{
throw new InvalidArgumentsException(
"User name '" + name +
"' contains illegal characters. Use simple ascii characters and numbers." );
"User name '" + name + "' contains illegal characters. Use simple ascii characters and numbers." );
}
}

Expand All @@ -592,8 +629,7 @@ private void assertValidRoleName( String name ) throws InvalidArgumentsException
if ( !roleRepository.isValidRoleName( name ) )
{
throw new InvalidArgumentsException(
"Role name '" + name +
"' contains illegal characters. Use simple ascii characters and numbers." );
"Role name '" + name + "' contains illegal characters. Use simple ascii characters and numbers." );
}
}

Expand Down
Expand Up @@ -22,8 +22,8 @@
import java.io.IOException;
import java.util.Set;

import org.neo4j.kernel.api.security.exception.InvalidArgumentsException;
import org.neo4j.kernel.lifecycle.Lifecycle;
import org.neo4j.server.security.auth.UserRepository;
import org.neo4j.server.security.auth.exception.ConcurrentModificationException;

/**
Expand All @@ -35,6 +35,11 @@ public interface RoleRepository extends Lifecycle

Set<String> getRoleNamesByUsername( String username );

/**
* Clears all cached role data.
*/
void clear();

/**
* Create a role, given that the roles token is unique.
*
Expand Down Expand Up @@ -70,4 +75,8 @@ void removeUserFromAllRoles( String username )
throws ConcurrentModificationException, IOException;

Set<String> getAllRoleNames();

void reloadIfNeeded() throws IOException;

boolean validateAgainst( UserRepository userRepository );
}
Expand Up @@ -29,4 +29,16 @@ protected void saveRoles() throws IOException
{
// Nothing to do
}

@Override
protected void loadRoles() throws IOException
{
// Nothing to do
}

@Override
public void reloadIfNeeded()
{
// Nothing to do
}
}

0 comments on commit 7b18926

Please sign in to comment.