diff --git a/community/bolt/src/test/java/org/neo4j/bolt/security/auth/BasicAuthenticationTest.java b/community/bolt/src/test/java/org/neo4j/bolt/security/auth/BasicAuthenticationTest.java index d9b2fb0fac68..aa2c6e8a8931 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/security/auth/BasicAuthenticationTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/security/auth/BasicAuthenticationTest.java @@ -213,7 +213,7 @@ public void shouldFailOnMalformedToken() throws Exception { // Given BasicAuthManager manager = new BasicAuthManager( mock( UserRepository.class), mock( PasswordPolicy.class ), - FakeClock.systemUTC() ); + FakeClock.systemUTC(), mock( UserRepository.class ) ); BasicAuthSubject authSubject = mock( BasicAuthSubject.class ); BasicAuthentication authentication = new BasicAuthentication( manager ); when( authSubject.getAuthenticationResult() ).thenReturn( AuthenticationResult.SUCCESS ); diff --git a/community/security/src/main/java/org/neo4j/server/security/auth/BasicAuthManager.java b/community/security/src/main/java/org/neo4j/server/security/auth/BasicAuthManager.java index 3af804460c28..cabd5618c4cf 100644 --- a/community/security/src/main/java/org/neo4j/server/security/auth/BasicAuthManager.java +++ b/community/security/src/main/java/org/neo4j/server/security/auth/BasicAuthManager.java @@ -47,33 +47,55 @@ public class BasicAuthManager implements AuthManager, UserManager, UserManagerSu protected final AuthenticationStrategy authStrategy; protected final UserRepository userRepository; protected final PasswordPolicy passwordPolicy; + private final UserRepository initialUserRepository; - public BasicAuthManager( UserRepository userRepository, PasswordPolicy passwordPolicy, AuthenticationStrategy authStrategy ) + public BasicAuthManager( UserRepository userRepository, PasswordPolicy passwordPolicy, + AuthenticationStrategy authStrategy, UserRepository initialUserRepository ) { this.userRepository = userRepository; this.passwordPolicy = passwordPolicy; this.authStrategy = authStrategy; + this.initialUserRepository = initialUserRepository; } - public BasicAuthManager( UserRepository userRepository, PasswordPolicy passwordPolicy, Clock clock ) + public BasicAuthManager( UserRepository userRepository, PasswordPolicy passwordPolicy, Clock clock, + UserRepository initialUserRepository ) { - this( userRepository, passwordPolicy, new RateLimitedAuthenticationStrategy( clock, 3 ) ); + this( userRepository, passwordPolicy, new RateLimitedAuthenticationStrategy( clock, 3 ), initialUserRepository ); } @Override public void init() throws Throwable { userRepository.init(); + initialUserRepository.init(); } @Override public void start() throws Throwable { userRepository.start(); + initialUserRepository.start(); if ( userRepository.numberOfUsers() == 0 ) { - newUser( "neo4j", "neo4j", true ); + if ( initialUserRepository.numberOfUsers() == 0 ) + { + newUser( "neo4j", "neo4j", true ); + } + } + for ( String username : initialUserRepository.getAllUsernames() ) + { + User oldUser = userRepository.getUserByName( username ); + User newUser = initialUserRepository.getUserByName( username ); + if ( oldUser == null ) + { + userRepository.create( newUser ); + } + else + { + userRepository.update( oldUser, newUser ); + } } } @@ -81,12 +103,14 @@ public void start() throws Throwable public void stop() throws Throwable { userRepository.stop(); + initialUserRepository.stop(); } @Override public void shutdown() throws Throwable { userRepository.shutdown(); + initialUserRepository.shutdown(); } @Override diff --git a/community/security/src/main/java/org/neo4j/server/security/auth/BasicAuthManagerFactory.java b/community/security/src/main/java/org/neo4j/server/security/auth/BasicAuthManagerFactory.java index 4a69384c06ae..fc6887b8b090 100644 --- a/community/security/src/main/java/org/neo4j/server/security/auth/BasicAuthManagerFactory.java +++ b/community/security/src/main/java/org/neo4j/server/security/auth/BasicAuthManagerFactory.java @@ -48,6 +48,12 @@ public static FileUserRepository getUserRepository( Config config, LogProvider l return new FileUserRepository( fileSystem, getUserRepositoryFile( config ), logProvider ); } + public static FileUserRepository getInitialUserRepository( Config config, LogProvider logProvider, + FileSystemAbstraction fileSystem ) + { + return new FileUserRepository( fileSystem, getInitialUserRepositoryFile( config ), logProvider ); + } + public static File getUserRepositoryFile( Config config ) { return getUserRepositoryFile( config, USER_STORE_FILENAME ); @@ -95,9 +101,10 @@ public AuthManager newInstance( Config config, LogProvider logProvider, Log igno } final UserRepository userRepository = getUserRepository( config, logProvider, fileSystem ); + final UserRepository initialUserRepository = getInitialUserRepository( config, logProvider, fileSystem ); final PasswordPolicy passwordPolicy = new BasicPasswordPolicy(); - return new BasicAuthManager( userRepository, passwordPolicy, Clocks.systemClock() ); + return new BasicAuthManager( userRepository, passwordPolicy, Clocks.systemClock(), initialUserRepository ); } } diff --git a/community/security/src/test/java/org/neo4j/server/security/auth/BasicAuthManagerTest.java b/community/security/src/test/java/org/neo4j/server/security/auth/BasicAuthManagerTest.java index 3162ebe9aa7e..51bbad6ab6a7 100644 --- a/community/security/src/test/java/org/neo4j/server/security/auth/BasicAuthManagerTest.java +++ b/community/security/src/test/java/org/neo4j/server/security/auth/BasicAuthManagerTest.java @@ -23,6 +23,7 @@ import org.hamcrest.core.IsEqual; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import java.io.IOException; @@ -31,9 +32,13 @@ 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.kernel.configuration.Config; +import org.neo4j.logging.NullLogProvider; +import org.neo4j.test.rule.fs.EphemeralFileSystemRule; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; @@ -46,16 +51,23 @@ public class BasicAuthManagerTest { - private InMemoryUserRepository users; + @Rule + public EphemeralFileSystemRule fsRule = new EphemeralFileSystemRule(); + + private Config config; + private UserRepository users; private BasicAuthManager manager; - private AuthenticationStrategy authStrategy = mock( AuthenticationStrategy.class );; + private AuthenticationStrategy authStrategy = mock( AuthenticationStrategy.class ); @Before public void setup() throws Throwable { - users = new InMemoryUserRepository(); - manager = new BasicAuthManager( users, mock( PasswordPolicy.class ), authStrategy ); - manager.start(); + config = Config.defaults(); + users = BasicAuthManagerFactory.getUserRepository( config, NullLogProvider.getInstance(), fsRule.get() ); + UserRepository initUserRepository = + BasicAuthManagerFactory.getInitialUserRepository( config, NullLogProvider.getInstance(), fsRule.get() ); + manager = new BasicAuthManager( users, mock( PasswordPolicy.class ), authStrategy, initUserRepository ); + manager.init(); } @After @@ -65,21 +77,106 @@ public void teardown() throws Throwable } @Test - public void shouldCreateDefaultUserIfNoneExist() + public void shouldCreateDefaultUserIfNoneExist() throws Throwable { // When - final User user = users.getUserByName( "neo4j" ); + manager.start(); // Then + final User user = users.getUserByName( "neo4j" ); assertNotNull( user ); assertTrue( user.credentials().matchesPassword( "neo4j" ) ); assertTrue( user.passwordChangeRequired() ); } + @Test + public void shouldLoadInitialUserIfNoneExist() throws Throwable + { + // Given + FileUserRepository initialUserRepository = + BasicAuthManagerFactory.getInitialUserRepository( config, NullLogProvider.getInstance(), fsRule.get() ); + initialUserRepository.start(); + initialUserRepository.create( + new User.Builder( "initUser", Credential.forPassword( "123" )) + .withRequiredPasswordChange( false ) + .build() + ); + initialUserRepository.shutdown(); + + // When + manager.start(); + + // Then + final User user = users.getUserByName( "initUser" ); + assertNotNull( user ); + assertTrue( user.credentials().matchesPassword( "123" ) ); + assertFalse( user.passwordChangeRequired() ); + } + + @Test + public void shouldAddInitialUserIfUsersExist() throws Throwable + { + // Given + FileUserRepository initialUserRepository = + BasicAuthManagerFactory.getInitialUserRepository( config, NullLogProvider.getInstance(), fsRule.get() ); + initialUserRepository.start(); + initialUserRepository.create( + new User.Builder( "initUser", Credential.forPassword( "123" )) + .withRequiredPasswordChange( false ) + .build() + ); + initialUserRepository.shutdown(); + users.start(); + createUser( "oldUser", "321", false ); + users.shutdown(); + + // When + manager.start(); + + // Then + final User initUser = users.getUserByName( "initUser" ); + assertNotNull( initUser ); + assertTrue( initUser.credentials().matchesPassword( "123" ) ); + assertFalse( initUser.passwordChangeRequired() ); + + final User oldUser = users.getUserByName( "oldUser" ); + assertNotNull( oldUser ); + assertTrue( oldUser.credentials().matchesPassword( "321" ) ); + assertFalse( oldUser.passwordChangeRequired() ); + } + + @Test + public void shouldUpdateUserIfInitialUserExist() throws Throwable + { + // Given + FileUserRepository initialUserRepository = + BasicAuthManagerFactory.getInitialUserRepository( config, NullLogProvider.getInstance(), fsRule.get() ); + initialUserRepository.start(); + initialUserRepository.create( + new User.Builder( "oldUser", Credential.forPassword( "newPassword" )) + .withRequiredPasswordChange( false ) + .build() + ); + initialUserRepository.shutdown(); + users.start(); + createUser( "oldUser", "oldPassword", true ); + users.shutdown(); + + // When + manager.start(); + + // Then + final User oldUser = users.getUserByName( "oldUser" ); + assertNotNull( oldUser ); + assertTrue( oldUser.credentials().matchesPassword( "newPassword" ) ); + assertFalse( oldUser.passwordChangeRequired() ); + } + @Test public void shouldFindAndAuthenticateUserSuccessfully() throws Throwable { // Given + manager.start(); final User user = createUser( "jake", "abc123", false ); // When @@ -93,6 +190,7 @@ public void shouldFindAndAuthenticateUserSuccessfully() throws Throwable public void shouldFindAndAuthenticateUserAndReturnAuthStrategyResult() throws Throwable { // Given + manager.start(); final User user = createUser( "jake", "abc123", true ); // When @@ -106,6 +204,7 @@ public void shouldFindAndAuthenticateUserAndReturnAuthStrategyResult() throws Th public void shouldFindAndAuthenticateUserAndReturnPasswordChangeIfRequired() throws Throwable { // Given + manager.start(); final User user = createUser( "jake", "abc123", true ); // When @@ -119,6 +218,7 @@ public void shouldFindAndAuthenticateUserAndReturnPasswordChangeIfRequired() thr public void shouldFailAuthenticationIfUserIsNotFound() throws Throwable { // Given + manager.start(); createUser( "jake", "abc123", true ); // Then @@ -128,6 +228,9 @@ public void shouldFailAuthenticationIfUserIsNotFound() throws Throwable @Test public void shouldCreateUser() throws Throwable { + // Given + manager.start(); + // When manager.newUser( "foo", "bar", true ); @@ -142,6 +245,7 @@ public void shouldCreateUser() throws Throwable public void shouldDeleteUser() throws Throwable { // Given + manager.start(); manager.newUser( "jake", "abc123", true ); // When @@ -155,6 +259,7 @@ public void shouldDeleteUser() throws Throwable public void shouldFailToDeleteUnknownUser() throws Throwable { // Given + manager.start(); manager.newUser( "jake", "abc123", true ); try @@ -180,6 +285,7 @@ public void shouldFailToDeleteUnknownUser() throws Throwable public void shouldSetPassword() throws Throwable { // Given + manager.start(); manager.newUser( "jake", "abc123", true ); // When @@ -194,6 +300,9 @@ public void shouldSetPassword() throws Throwable @Test public void shouldReturnNullWhenSettingPasswordForUnknownUser() throws Throwable { + // Given + manager.start(); + // When try { diff --git a/community/server/src/test/java/org/neo4j/server/rest/dbms/UserServiceTest.java b/community/server/src/test/java/org/neo4j/server/rest/dbms/UserServiceTest.java index a59921fd9de3..8848b2458106 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/dbms/UserServiceTest.java +++ b/community/server/src/test/java/org/neo4j/server/rest/dbms/UserServiceTest.java @@ -72,7 +72,7 @@ public class UserServiceTest protected void setupAuthManagerAndSubject() { BasicAuthManager basicAuthManager = new BasicAuthManager( userRepository, passwordPolicy, - mock( AuthenticationStrategy.class) ); + mock( AuthenticationStrategy.class), new InMemoryUserRepository() ); authManager = basicAuthManager; userManager = basicAuthManager.getUserManager(); neo4jSubject = new BasicAuthSubject( basicAuthManager, NEO4J_USER, AuthenticationResult.SUCCESS ); diff --git a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/EnterpriseAuthManagerFactory.java b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/EnterpriseAuthManagerFactory.java index 59c07f35e843..056eb2a0dae3 100644 --- a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/EnterpriseAuthManagerFactory.java +++ b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/EnterpriseAuthManagerFactory.java @@ -39,6 +39,7 @@ import org.neo4j.kernel.impl.util.JobScheduler; import org.neo4j.logging.Log; import org.neo4j.logging.LogProvider; +import org.neo4j.server.security.auth.BasicAuthManagerFactory; import org.neo4j.server.security.auth.BasicPasswordPolicy; import org.neo4j.server.security.auth.RateLimitedAuthenticationStrategy; import org.neo4j.server.security.enterprise.auth.plugin.PluginRealm; @@ -130,9 +131,13 @@ public static InternalFlatFileRealm createInternalRealm( Config config, LogProvi return new InternalFlatFileRealm( getUserRepository( config, logProvider, fileSystem ), getRoleRepository( config, logProvider, fileSystem ), - new BasicPasswordPolicy(), new RateLimitedAuthenticationStrategy( Clocks.systemClock(), 3 ), + new BasicPasswordPolicy(), + new RateLimitedAuthenticationStrategy( Clocks.systemClock(), 3 ), config.get( SecuritySettings.native_authentication_enabled ), - config.get( SecuritySettings.native_authorization_enabled ), jobScheduler ); + config.get( SecuritySettings.native_authorization_enabled ), + jobScheduler, + BasicAuthManagerFactory.getInitialUserRepository( config, logProvider, fileSystem ) + ); } private SecurityLog getSecurityLog( Log allegedSecurityLog ) diff --git a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealm.java b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealm.java index 746231b0bfbe..7251b9e59a69 100644 --- a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealm.java +++ b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealm.java @@ -36,6 +36,7 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedList; import java.util.Set; import java.util.SortedSet; @@ -75,6 +76,7 @@ public class InternalFlatFileRealm extends AuthorizingRealm implements RealmLife private final UserRepository userRepository; private final RoleRepository roleRepository; + private final UserRepository initialUserRepository; private final PasswordPolicy passwordPolicy; private final AuthenticationStrategy authenticationStrategy; private final boolean authenticationEnabled; @@ -84,21 +86,23 @@ public class InternalFlatFileRealm extends AuthorizingRealm implements RealmLife public InternalFlatFileRealm( UserRepository userRepository, RoleRepository roleRepository, PasswordPolicy passwordPolicy, AuthenticationStrategy authenticationStrategy, - JobScheduler jobScheduler ) + JobScheduler jobScheduler, UserRepository initialUserRepository ) { this( userRepository, roleRepository, passwordPolicy, authenticationStrategy, true, true, - jobScheduler ); + jobScheduler, initialUserRepository ); } InternalFlatFileRealm( UserRepository userRepository, RoleRepository roleRepository, PasswordPolicy passwordPolicy, AuthenticationStrategy authenticationStrategy, - boolean authenticationEnabled, boolean authorizationEnabled, JobScheduler jobScheduler ) + boolean authenticationEnabled, boolean authorizationEnabled, JobScheduler jobScheduler, + UserRepository initialUserRepository ) { super(); setName( SecuritySettings.NATIVE_REALM_NAME ); this.userRepository = userRepository; this.roleRepository = roleRepository; + this.initialUserRepository = initialUserRepository; this.passwordPolicy = passwordPolicy; this.authenticationStrategy = authenticationStrategy; this.authenticationEnabled = authenticationEnabled; @@ -115,6 +119,7 @@ public InternalFlatFileRealm( UserRepository userRepository, RoleRepository role @Override public void initialize( RealmOperations ignore ) throws Throwable { + initialUserRepository.init(); userRepository.init(); roleRepository.init(); } @@ -122,11 +127,12 @@ public void initialize( RealmOperations ignore ) throws Throwable @Override public void start() throws Throwable { + initialUserRepository.start(); userRepository.start(); roleRepository.start(); - ensureDefaultUsers(); - ensureDefaultRoles(); + Set addedDefaultUsers = ensureDefaultUsers(); + ensureDefaultRoles( addedDefaultUsers ); scheduleNextFileReload(); } @@ -198,19 +204,38 @@ private void readFilesFromDisk( int attemptLeft, java.util.List failures } /* Adds neo4j user if no users exist */ - private void ensureDefaultUsers() throws IOException, InvalidArgumentsException + private Set ensureDefaultUsers() throws Throwable { if ( authenticationEnabled || authorizationEnabled ) { - if ( numberOfUsers() == 0 ) + if ( userRepository.numberOfUsers() == 0 ) { - newUser( "neo4j", "neo4j", true ); + if ( initialUserRepository.numberOfUsers() == 0 ) + { + newUser( "neo4j", "neo4j", true ); + return Collections.singleton( "neo4j" ); + } + } + for ( String username : initialUserRepository.getAllUsernames() ) + { + User oldUser = userRepository.getUserByName( username ); + User newUser = initialUserRepository.getUserByName( username ); + if ( oldUser == null ) + { + userRepository.create( newUser ); + } + else + { + userRepository.update( oldUser, newUser ); + } } + return initialUserRepository.getAllUsernames(); } + return Collections.emptySet(); } /* Builds all predefined roles if no roles exist. Adds 'neo4j' to admin role if no admin is assigned */ - private void ensureDefaultRoles() throws IOException, InvalidArgumentsException + private void ensureDefaultRoles( Set addedDefaultUsers ) throws IOException, InvalidArgumentsException { if ( authenticationEnabled || authorizationEnabled ) { @@ -221,12 +246,9 @@ private void ensureDefaultRoles() throws IOException, InvalidArgumentsException newRole( role ); } } - if ( this.getUsernamesForRole( PredefinedRoles.ADMIN ).size() == 0 ) + for ( String username : addedDefaultUsers ) { - if ( getAllUsernames().contains( "neo4j" ) ) - { - addRoleToUser( PredefinedRoles.ADMIN, "neo4j" ); - } + addRoleToUser( PredefinedRoles.ADMIN, username ); } } } @@ -234,6 +256,7 @@ private void ensureDefaultRoles() throws IOException, InvalidArgumentsException @Override public void stop() throws Throwable { + initialUserRepository.stop(); userRepository.stop(); roleRepository.stop(); @@ -247,6 +270,7 @@ public void stop() throws Throwable @Override public void shutdown() throws Throwable { + initialUserRepository.shutdown(); userRepository.shutdown(); roleRepository.shutdown(); setCacheManager( null ); diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/AuthProceduresLoggingTest.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/AuthProceduresLoggingTest.java index 25323a1ebcad..bddf0af4b4dc 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/AuthProceduresLoggingTest.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/AuthProceduresLoggingTest.java @@ -73,7 +73,9 @@ private EnterpriseUserManager getUserManager() throws Throwable new InMemoryRoleRepository(), new BasicPasswordPolicy(), mock( AuthenticationStrategy.class ), - mock( JobScheduler.class ) ); + mock( JobScheduler.class ), + new InMemoryUserRepository() + ); realm.start(); // creates default user and roles return realm; } diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealmIT.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealmIT.java index 184307f8be50..9d1a06f200c0 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealmIT.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealmIT.java @@ -35,10 +35,12 @@ import org.neo4j.graphdb.mockfs.DelegatingFileSystemAbstraction; import org.neo4j.io.fs.FileSystemAbstraction; +import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.impl.util.Neo4jJobScheduler; import org.neo4j.logging.LogProvider; import org.neo4j.logging.NullLogProvider; import org.neo4j.server.security.auth.AuthenticationStrategy; +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; @@ -75,11 +77,13 @@ public void setup() throws Throwable roleStoreFile = new File( "dbms", "roles" ); final UserRepository userRepository = new FileUserRepository( fs, userStoreFile, logProvider ); final RoleRepository roleRepository = new FileRoleRepository( fs, roleStoreFile, logProvider ); + final UserRepository initialUserRepository = BasicAuthManagerFactory.getInitialUserRepository( Config + .defaults(), logProvider, fs ); final PasswordPolicy passwordPolicy = new BasicPasswordPolicy(); AuthenticationStrategy authenticationStrategy = new RateLimitedAuthenticationStrategy( Clocks.systemClock(), 3 ); realm = new InternalFlatFileRealm( userRepository, roleRepository, passwordPolicy, authenticationStrategy, - true, true, jobScheduler ); + true, true, jobScheduler, initialUserRepository ); realm.init(); realm.start(); } diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealmTest.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealmTest.java index 2f3dd471438e..6c5ab50e46bc 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealmTest.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealmTest.java @@ -44,6 +44,7 @@ import org.neo4j.server.security.auth.ListSnapshot; import org.neo4j.server.security.auth.PasswordPolicy; import org.neo4j.server.security.auth.RateLimitedAuthenticationStrategy; +import org.neo4j.server.security.auth.User; import org.neo4j.server.security.auth.UserRepository; import org.neo4j.time.Clocks; @@ -72,7 +73,9 @@ public void setup() throws Throwable new InMemoryRoleRepository(), new BasicPasswordPolicy(), new RateLimitedAuthenticationStrategy( Clock.systemUTC(), 3 ), - mock( JobScheduler.class ) ); + mock( JobScheduler.class ), + new InMemoryUserRepository() + ); List realms = listOf( testRealm ); @@ -132,16 +135,23 @@ private void assertSetUsersAndRolesNTimes( boolean usersChanged, boolean rolesCh { final UserRepository userRepository = mock( UserRepository.class ); final RoleRepository roleRepository = mock( RoleRepository.class ); + final UserRepository initialUserRepository = mock( UserRepository.class ); final PasswordPolicy passwordPolicy = new BasicPasswordPolicy(); AuthenticationStrategy authenticationStrategy = new RateLimitedAuthenticationStrategy( Clocks.systemClock(), 3 ); InternalFlatFileRealmIT.TestJobScheduler jobScheduler = new InternalFlatFileRealmIT.TestJobScheduler(); - InternalFlatFileRealm realm = new InternalFlatFileRealm( userRepository, roleRepository, - passwordPolicy, - authenticationStrategy, - true, true, jobScheduler ); + InternalFlatFileRealm realm = + new InternalFlatFileRealm( + userRepository, + roleRepository, + passwordPolicy, + authenticationStrategy, + jobScheduler, + initialUserRepository + ); when( userRepository.getPersistedSnapshot() ).thenReturn( new ListSnapshot<>( 10L, Collections.emptyList(), usersChanged ) ); + when( userRepository.getUserByName( any() ) ).thenReturn( new User.Builder( ).build() ); when( roleRepository.getPersistedSnapshot() ).thenReturn( new ListSnapshot<>( 10L, Collections.emptyList(), rolesChanged ) ); when( roleRepository.getRoleByName( anyString() ) ).thenReturn( new RoleRecord( "" ) ); @@ -161,9 +171,10 @@ private class TestRealm extends InternalFlatFileRealm private boolean authorizationFlag = false; public TestRealm( UserRepository userRepository, RoleRepository roleRepository, PasswordPolicy passwordPolicy, - AuthenticationStrategy authenticationStrategy, JobScheduler jobScheduler ) + AuthenticationStrategy authenticationStrategy, JobScheduler jobScheduler, + UserRepository initialUserRepository ) { - super( userRepository, roleRepository, passwordPolicy, authenticationStrategy, jobScheduler ); + super( userRepository, roleRepository, passwordPolicy, authenticationStrategy, jobScheduler, initialUserRepository ); } boolean takeAuthenticationFlag() diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/LdapCachingTest.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/LdapCachingTest.java index ee36848e327f..8466524bef89 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/LdapCachingTest.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/LdapCachingTest.java @@ -70,7 +70,8 @@ public void setup() throws Throwable new InMemoryRoleRepository(), new BasicPasswordPolicy(), new RateLimitedAuthenticationStrategy( Clock.systemUTC(), 3 ), - mock( JobScheduler.class ) + mock( JobScheduler.class ), + new InMemoryUserRepository() ); testRealm = new TestRealm( getLdapConfig(), securityLog ); diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerRule.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerRule.java index 9c3e58ecc392..402503e03a68 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerRule.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerRule.java @@ -34,6 +34,7 @@ import org.neo4j.logging.Log; import org.neo4j.server.security.auth.AuthenticationStrategy; import org.neo4j.server.security.auth.BasicPasswordPolicy; +import org.neo4j.server.security.auth.InMemoryUserRepository; import org.neo4j.server.security.auth.UserRepository; import static org.junit.Assert.fail; @@ -70,7 +71,8 @@ private void setupAuthManager( AuthenticationStrategy authStrategy ) throws Thro new InMemoryRoleRepository(), new BasicPasswordPolicy(), authStrategy, - mock( JobScheduler.class ) + mock( JobScheduler.class ), + new InMemoryUserRepository() ); manager = new MultiRealmAuthManager( internalFlatFileRealm, Collections.singleton( internalFlatFileRealm ), diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerTest.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerTest.java index 28d8221ab365..6e9f605ab736 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerTest.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerTest.java @@ -22,6 +22,7 @@ import org.apache.shiro.cache.MemoryConstrainedCacheManager; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import java.util.Collections; @@ -31,15 +32,20 @@ import org.neo4j.kernel.api.security.AuthToken; import org.neo4j.kernel.api.security.AuthenticationResult; import org.neo4j.kernel.api.security.exception.InvalidAuthTokenException; +import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.impl.enterprise.SecurityLog; import org.neo4j.kernel.impl.util.JobScheduler; import org.neo4j.logging.AssertableLogProvider; import org.neo4j.logging.Log; +import org.neo4j.logging.NullLogProvider; import org.neo4j.server.security.auth.AuthenticationStrategy; +import org.neo4j.server.security.auth.BasicAuthManagerFactory; import org.neo4j.server.security.auth.Credential; -import org.neo4j.server.security.auth.InMemoryUserRepository; +import org.neo4j.server.security.auth.FileUserRepository; import org.neo4j.server.security.auth.PasswordPolicy; import org.neo4j.server.security.auth.User; +import org.neo4j.server.security.auth.UserRepository; +import org.neo4j.test.rule.fs.EphemeralFileSystemRule; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; @@ -59,7 +65,11 @@ public class MultiRealmAuthManagerTest { - private InMemoryUserRepository users; + @Rule + public EphemeralFileSystemRule fsRule = new EphemeralFileSystemRule(); + + private Config config; + private UserRepository users; private AuthenticationStrategy authStrategy; private MultiRealmAuthManager manager; private EnterpriseUserManager userManager; @@ -68,7 +78,8 @@ public class MultiRealmAuthManagerTest @Before public void setUp() throws Throwable { - users = new InMemoryUserRepository(); + config = Config.defaults(); + users = BasicAuthManagerFactory.getUserRepository( config, NullLogProvider.getInstance(), fsRule.get() ); authStrategy = mock( AuthenticationStrategy.class ); logProvider = new AssertableLogProvider(); @@ -81,8 +92,15 @@ private MultiRealmAuthManager createAuthManager( boolean logSuccessfulAuthentica Log log = logProvider.getLog( this.getClass() ); InternalFlatFileRealm internalFlatFileRealm = - new InternalFlatFileRealm( users, new InMemoryRoleRepository(), mock( PasswordPolicy.class ), - authStrategy, mock( JobScheduler.class ) ); + new InternalFlatFileRealm( + users, + new InMemoryRoleRepository(), + mock( PasswordPolicy.class ), + authStrategy, + mock( JobScheduler.class ), + BasicAuthManagerFactory.getInitialUserRepository( + config, NullLogProvider.getInstance(), fsRule.get() ) + ); manager = new MultiRealmAuthManager( internalFlatFileRealm, Collections.singleton( internalFlatFileRealm ), new MemoryConstrainedCacheManager(), new SecurityLog( log ), logSuccessfulAuthentications ); @@ -101,8 +119,6 @@ public void tearDown() throws Throwable @Test public void shouldCreateDefaultUserIfNoneExist() throws Throwable { - // Given - // When manager.start(); @@ -113,6 +129,81 @@ public void shouldCreateDefaultUserIfNoneExist() throws Throwable assertTrue( user.passwordChangeRequired() ); } + @Test + public void shouldLoadInitialUserIfNoneExist() throws Throwable + { + // Given + FileUserRepository initialUserRepository = + BasicAuthManagerFactory.getInitialUserRepository( config, NullLogProvider.getInstance(), fsRule.get() ); + initialUserRepository.start(); + initialUserRepository.create( + new User.Builder( "initUser", Credential.forPassword( "123" )) + .withRequiredPasswordChange( false ) + .build() + ); + initialUserRepository.shutdown(); + + // When + manager.start(); + + // Then + final User user = users.getUserByName( "initUser" ); + assertNotNull( user ); + assertTrue( user.credentials().matchesPassword( "123" ) ); + assertFalse( user.passwordChangeRequired() ); + } + + @Test + public void shouldAddInitialUserIfUsersExist() throws Throwable + { + // Given + FileUserRepository initialUserRepository = + BasicAuthManagerFactory.getInitialUserRepository( config, NullLogProvider.getInstance(), fsRule.get() ); + initialUserRepository.start(); + initialUserRepository.create( newUser( "initUser", "123", false ) ); + initialUserRepository.shutdown(); + users.start(); + users.create( newUser( "oldUser", "321", false ) ); + users.shutdown(); + + // When + manager.start(); + + // Then + final User initUser = users.getUserByName( "initUser" ); + assertNotNull( initUser ); + assertTrue( initUser.credentials().matchesPassword( "123" ) ); + assertFalse( initUser.passwordChangeRequired() ); + + final User oldUser = users.getUserByName( "oldUser" ); + assertNotNull( oldUser ); + assertTrue( oldUser.credentials().matchesPassword( "321" ) ); + assertFalse( oldUser.passwordChangeRequired() ); + } + + @Test + public void shouldUpdateUserIfInitialUserExist() throws Throwable + { + // Given + FileUserRepository initialUserRepository = + BasicAuthManagerFactory.getInitialUserRepository( config, NullLogProvider.getInstance(), fsRule.get() ); + initialUserRepository.start(); + initialUserRepository.create( newUser( "oldUser", "newPassword", false ) ); + initialUserRepository.shutdown(); + users.start(); + users.create( newUser( "oldUser", "oldPassword", true ) ); + users.shutdown(); + + // When + manager.start(); + + // Then + final User oldUser = users.getUserByName( "oldUser" ); + assertNotNull( oldUser ); + assertTrue( oldUser.credentials().matchesPassword( "newPassword" ) ); + assertFalse( oldUser.passwordChangeRequired() ); + } + @Test public void shouldFindAndAuthenticateUserSuccessfully() throws Throwable {