From 51dbff985ba209378b1dfdb3b51826496daa07c5 Mon Sep 17 00:00:00 2001 From: fickludd Date: Wed, 28 Sep 2016 16:49:52 +0200 Subject: [PATCH] Ensures UserService logs password changes if given EnterpriseAuthManager --- .../server/rest/dbms/DelegatingPrincipal.java | 2 +- .../server/rest/dbms/UserServiceTest.java | 58 ++----- .../auth/MultiRealmAuthManagerRule.java | 159 ++++++++++++++++++ enterprise/server-enterprise/pom.xml | 16 ++ .../security/EnterpriseUserServiceTest.java | 68 ++++++++ 5 files changed, 262 insertions(+), 41 deletions(-) create mode 100644 enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerRule.java create mode 100644 enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/security/EnterpriseUserServiceTest.java diff --git a/community/server/src/main/java/org/neo4j/server/rest/dbms/DelegatingPrincipal.java b/community/server/src/main/java/org/neo4j/server/rest/dbms/DelegatingPrincipal.java index d302ac2dfae4..812e345e2999 100644 --- a/community/server/src/main/java/org/neo4j/server/rest/dbms/DelegatingPrincipal.java +++ b/community/server/src/main/java/org/neo4j/server/rest/dbms/DelegatingPrincipal.java @@ -71,4 +71,4 @@ public String toString() "username='" + username + '\'' + '}'; } -} \ No newline at end of file +} 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 7b016ade3bd8..a59921fd9de3 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 @@ -21,13 +21,10 @@ import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import java.io.IOException; import java.net.URI; -import java.net.URISyntaxException; import java.security.Principal; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.Response; @@ -47,6 +44,7 @@ import org.neo4j.server.security.auth.PasswordPolicy; import org.neo4j.server.security.auth.User; import org.neo4j.server.security.auth.UserManager; +import org.neo4j.server.security.auth.UserRepository; import org.neo4j.test.server.EntityOutputFormat; import static org.hamcrest.Matchers.containsString; @@ -56,30 +54,30 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import static org.neo4j.test.assertion.Assert.assertException; public class UserServiceTest { - private static final User NEO4J_USER = new User.Builder( "neo4j", Credential.forPassword( "neo4j" )) + protected static final User NEO4J_USER = new User.Builder( "neo4j", Credential.forPassword( "neo4j" )) .withRequiredPasswordChange( true ).build(); - private final PasswordPolicy passwordPolicy = new BasicPasswordPolicy(); - private final InMemoryUserRepository userRepository = new InMemoryUserRepository(); + protected final PasswordPolicy passwordPolicy = new BasicPasswordPolicy(); + protected final UserRepository userRepository = new InMemoryUserRepository(); - private AuthManager authManager; - private AuthSubject neo4jSubject; - private Principal neo4jPrinciple; + protected AuthManager authManager; + protected UserManager userManager; + protected AuthSubject neo4jSubject; + protected Principal neo4jPrinciple; protected void setupAuthManagerAndSubject() { - BasicAuthManager m = new BasicAuthManager( userRepository, passwordPolicy, + BasicAuthManager basicAuthManager = new BasicAuthManager( userRepository, passwordPolicy, mock( AuthenticationStrategy.class) ); - authManager = m; - neo4jSubject = new BasicAuthSubject( m, NEO4J_USER, AuthenticationResult.SUCCESS ); + authManager = basicAuthManager; + userManager = basicAuthManager.getUserManager(); + neo4jSubject = new BasicAuthSubject( basicAuthManager, NEO4J_USER, AuthenticationResult.SUCCESS ); } - @Rule - public ExpectedException exception = ExpectedException.none(); - @Before public void setUp() throws InvalidArgumentsException, IOException { @@ -101,11 +99,6 @@ public void shouldReturnValidUserRepresentation() throws Exception HttpServletRequest req = mock( HttpServletRequest.class ); when( req.getUserPrincipal() ).thenReturn( neo4jPrinciple ); - BasicAuthManager authManager = mock( BasicAuthManager.class ); - UserManager userManager = mock( UserManager.class ); - when( authManager.getUserManager() ).thenReturn( userManager ); - when( userManager.getUser( "neo4j" ) ).thenReturn( NEO4J_USER ); - OutputFormat outputFormat = new EntityOutputFormat( new JsonFormat(), new URI( "http://www.example.com" ), null ); UserService userService = new UserService( authManager, new JsonFormat(), outputFormat ); @@ -128,10 +121,6 @@ public void shouldReturn404WhenRequestingUserIfNotAuthenticated() throws Excepti HttpServletRequest req = mock( HttpServletRequest.class ); when( req.getUserPrincipal() ).thenReturn( null ); - BasicAuthManager authManager = mock( BasicAuthManager.class ); - UserManager userManager = mock( UserManager.class ); - when( authManager.getUserManager() ).thenReturn( userManager ); - OutputFormat outputFormat = new EntityOutputFormat( new JsonFormat(), new URI( "http://www.example.com" ), null ); UserService userService = new UserService( authManager, new JsonFormat(), outputFormat ); @@ -166,11 +155,7 @@ public void shouldReturn404WhenRequestingUserIfUnknownUser() throws Exception HttpServletRequest req = mock( HttpServletRequest.class ); when( req.getUserPrincipal() ).thenReturn( neo4jPrinciple ); - BasicAuthManager authManager = mock( BasicAuthManager.class ); - UserManager userManager = mock( UserManager.class ); - when( authManager.getUserManager() ).thenReturn( userManager ); - when( userManager.getUser( "neo4j" ) ) - .thenThrow( new InvalidArgumentsException( "User 'neo4j' does not exist!" ) ); + userManager.deleteUser( "neo4j" ); OutputFormat outputFormat = new EntityOutputFormat( new JsonFormat(), new URI( "http://www.example.com" ), null ); UserService userService = new UserService( authManager, new JsonFormat(), outputFormat ); @@ -189,11 +174,6 @@ public void shouldChangePasswordAndReturnSuccess() throws Exception HttpServletRequest req = mock( HttpServletRequest.class ); when( req.getUserPrincipal() ).thenReturn( neo4jPrinciple ); - BasicAuthManager authManager = mock( BasicAuthManager.class ); - UserManager userManager = mock( UserManager.class ); - when( authManager.getUserManager() ).thenReturn( userManager ); - when( userManager.getUser( "neo4j" ) ).thenReturn( NEO4J_USER ); - OutputFormat outputFormat = new EntityOutputFormat( new JsonFormat(), new URI( "http://www.example.com" ), null ); UserService userService = new UserService( authManager, new JsonFormat(), outputFormat ); @@ -368,16 +348,14 @@ public void shouldReturn422IfPasswordIdentical() throws Exception } @Test - public void shouldThrowExceptionIfGivenAuthManagerDoesNotImplementUserManager() throws URISyntaxException + public void shouldThrowExceptionIfGivenAuthManagerDoesNotImplementUserManager() throws Exception { // Given OutputFormat outputFormat = new EntityOutputFormat( new JsonFormat(), new URI( "http://www.example.com" ), null ); - // Expect - exception.expect( IllegalArgumentException.class ); - exception.expectMessage( "The provided auth manager is not capable of user management" ); - // When - new UserService( mock( AuthManager.class ), new JsonFormat(), outputFormat ); + assertException( () -> + new UserService( mock( AuthManager.class ), new JsonFormat(), outputFormat ), + IllegalArgumentException.class, "The provided auth manager is not capable of user management" ); } } 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 new file mode 100644 index 000000000000..9c3e58ecc392 --- /dev/null +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerRule.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.security.enterprise.auth; + +import org.apache.shiro.cache.MemoryConstrainedCacheManager; +import org.junit.After; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.Collections; + +import org.neo4j.kernel.api.security.AuthSubject; +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.server.security.auth.AuthenticationStrategy; +import org.neo4j.server.security.auth.BasicPasswordPolicy; +import org.neo4j.server.security.auth.UserRepository; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.neo4j.logging.AssertableLogProvider.inLog; + +public class MultiRealmAuthManagerRule implements TestRule +{ + private UserRepository users; + private AuthenticationStrategy authStrategy; + private MultiRealmAuthManager manager; + private EnterpriseUserManager userManager; + private AssertableLogProvider logProvider; + private SecurityLog securityLog; + + public MultiRealmAuthManagerRule( + UserRepository users, + AuthenticationStrategy authStrategy + ) + { + this.users = users; + this.authStrategy = authStrategy; + + logProvider = new AssertableLogProvider(); + } + + private void setupAuthManager( AuthenticationStrategy authStrategy ) throws Throwable + { + Log log = logProvider.getLog( this.getClass() ); + securityLog = new SecurityLog( log ); + InternalFlatFileRealm internalFlatFileRealm = + new InternalFlatFileRealm( + users, + new InMemoryRoleRepository(), + new BasicPasswordPolicy(), + authStrategy, + mock( JobScheduler.class ) + ); + + manager = new MultiRealmAuthManager( internalFlatFileRealm, Collections.singleton( internalFlatFileRealm ), + new MemoryConstrainedCacheManager(), securityLog, true ); + manager.init(); + + userManager = manager.getUserManager(); + } + + public EnterpriseAuthAndUserManager getManager() + { + return manager; + } + + public AuthSubject makeSubject( ShiroSubject shiroSubject ) + { + return new StandardEnterpriseAuthSubject( manager, shiroSubject, securityLog ); + } + + @Override + public Statement apply( final Statement base, final Description description ) + { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try + { + setupAuthManager( authStrategy ); + base.evaluate(); + } + catch ( Throwable t ) + { + fail( "Got unexpected exception " + t ); + } + finally + { + try + { + tearDown(); + } + catch ( Throwable t ) + { + throw new RuntimeException( "Failed to shut down MultiRealmAuthManager", t ); + } + } + } + }; + } + + @After + public void tearDown() throws Throwable + { + manager.stop(); + manager.shutdown(); + } + + public void assertExactlyInfoInLog( String message ) + { + logProvider.assertExactly( inLog( this.getClass() ).info( message ) ); + } + + public void assertExactlyInfoInLog( String message, Object... values ) + { + logProvider.assertExactly( inLog( this.getClass() ).info( message, values ) ); + } + + public void assertExactlyWarnInLog( String message ) + { + logProvider.assertExactly( inLog( this.getClass() ).warn( message ) ); + } + + public void assertExactlyWarnInLog( String message, Object... values ) + { + logProvider.assertExactly( inLog( this.getClass() ).warn( message, values ) ); + } + + public void assertExactlyErrorInLog( String message ) + { + logProvider.assertExactly( inLog( this.getClass() ).error( message ) ); + } + + public void assertExactlyErrorInLog( String message, Object... values ) + { + logProvider.assertExactly( inLog( this.getClass() ).error( message, values ) ); + } +} diff --git a/enterprise/server-enterprise/pom.xml b/enterprise/server-enterprise/pom.xml index 5e913db27d9c..5d9b26d4fd21 100644 --- a/enterprise/server-enterprise/pom.xml +++ b/enterprise/server-enterprise/pom.xml @@ -158,6 +158,22 @@ test + + org.neo4j + neo4j-security + ${project.version} + test-jar + test + + + + org.neo4j + neo4j-logging + ${project.version} + test-jar + test + + org.neo4j.app neo4j-server diff --git a/enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/security/EnterpriseUserServiceTest.java b/enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/security/EnterpriseUserServiceTest.java new file mode 100644 index 000000000000..b8322fe93b16 --- /dev/null +++ b/enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/security/EnterpriseUserServiceTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest.security; + +import org.junit.Rule; +import org.junit.Test; + +import org.neo4j.server.rest.dbms.UserServiceTest; +import org.neo4j.server.security.auth.AuthenticationStrategy; +import org.neo4j.server.security.enterprise.auth.MultiRealmAuthManagerRule; +import org.neo4j.server.security.enterprise.auth.ShiroSubject; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class EnterpriseUserServiceTest extends UserServiceTest +{ + @Rule + public MultiRealmAuthManagerRule authManagerRule = new MultiRealmAuthManagerRule( + userRepository, + mock( AuthenticationStrategy.class ) + ); + + @Override + protected void setupAuthManagerAndSubject() + { + authManager = authManagerRule.getManager(); + userManager = authManagerRule.getManager().getUserManager(); + + ShiroSubject shiroSubject = mock( ShiroSubject.class ); + when( shiroSubject.getPrincipal() ).thenReturn( "neo4j" ); + neo4jSubject = authManagerRule.makeSubject( shiroSubject ); + } + + @Test + public void shouldLogPasswordChange() throws Exception + { + shouldChangePasswordAndReturnSuccess(); + + authManagerRule.assertExactlyInfoInLog( "[neo4j]: changed password%s", "" ); + } + + @Test + public void shouldLogFailedPasswordChange() throws Exception + { + shouldReturn422IfPasswordIdentical(); + + authManagerRule.assertExactlyErrorInLog( "[neo4j]: tried to change password: %s", + "Old password and new password cannot be the same." ); + } +}