Skip to content

Commit

Permalink
Ensures UserService logs password changes if given EnterpriseAuthManager
Browse files Browse the repository at this point in the history
  • Loading branch information
fickludd committed Sep 28, 2016
1 parent cf25e37 commit 51dbff9
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 41 deletions.
Expand Up @@ -71,4 +71,4 @@ public String toString()
"username='" + username + '\'' + "username='" + username + '\'' +
'}'; '}';
} }
} }
Expand Up @@ -21,13 +21,10 @@


import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;


import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException;
import java.security.Principal; import java.security.Principal;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
Expand All @@ -47,6 +44,7 @@
import org.neo4j.server.security.auth.PasswordPolicy; import org.neo4j.server.security.auth.PasswordPolicy;
import org.neo4j.server.security.auth.User; import org.neo4j.server.security.auth.User;
import org.neo4j.server.security.auth.UserManager; import org.neo4j.server.security.auth.UserManager;
import org.neo4j.server.security.auth.UserRepository;
import org.neo4j.test.server.EntityOutputFormat; import org.neo4j.test.server.EntityOutputFormat;


import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
Expand All @@ -56,30 +54,30 @@
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.neo4j.test.assertion.Assert.assertException;


public class UserServiceTest 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(); .withRequiredPasswordChange( true ).build();


private final PasswordPolicy passwordPolicy = new BasicPasswordPolicy(); protected final PasswordPolicy passwordPolicy = new BasicPasswordPolicy();
private final InMemoryUserRepository userRepository = new InMemoryUserRepository(); protected final UserRepository userRepository = new InMemoryUserRepository();


private AuthManager authManager; protected AuthManager authManager;
private AuthSubject neo4jSubject; protected UserManager userManager;
private Principal neo4jPrinciple; protected AuthSubject neo4jSubject;
protected Principal neo4jPrinciple;


protected void setupAuthManagerAndSubject() protected void setupAuthManagerAndSubject()
{ {
BasicAuthManager m = new BasicAuthManager( userRepository, passwordPolicy, BasicAuthManager basicAuthManager = new BasicAuthManager( userRepository, passwordPolicy,
mock( AuthenticationStrategy.class) ); mock( AuthenticationStrategy.class) );
authManager = m; authManager = basicAuthManager;
neo4jSubject = new BasicAuthSubject( m, NEO4J_USER, AuthenticationResult.SUCCESS ); userManager = basicAuthManager.getUserManager();
neo4jSubject = new BasicAuthSubject( basicAuthManager, NEO4J_USER, AuthenticationResult.SUCCESS );
} }


@Rule
public ExpectedException exception = ExpectedException.none();

@Before @Before
public void setUp() throws InvalidArgumentsException, IOException public void setUp() throws InvalidArgumentsException, IOException
{ {
Expand All @@ -101,11 +99,6 @@ public void shouldReturnValidUserRepresentation() throws Exception
HttpServletRequest req = mock( HttpServletRequest.class ); HttpServletRequest req = mock( HttpServletRequest.class );
when( req.getUserPrincipal() ).thenReturn( neo4jPrinciple ); 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 ); OutputFormat outputFormat = new EntityOutputFormat( new JsonFormat(), new URI( "http://www.example.com" ), null );
UserService userService = new UserService( authManager, new JsonFormat(), outputFormat ); UserService userService = new UserService( authManager, new JsonFormat(), outputFormat );


Expand All @@ -128,10 +121,6 @@ public void shouldReturn404WhenRequestingUserIfNotAuthenticated() throws Excepti
HttpServletRequest req = mock( HttpServletRequest.class ); HttpServletRequest req = mock( HttpServletRequest.class );
when( req.getUserPrincipal() ).thenReturn( null ); 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 ); OutputFormat outputFormat = new EntityOutputFormat( new JsonFormat(), new URI( "http://www.example.com" ), null );
UserService userService = new UserService( authManager, new JsonFormat(), outputFormat ); UserService userService = new UserService( authManager, new JsonFormat(), outputFormat );


Expand Down Expand Up @@ -166,11 +155,7 @@ public void shouldReturn404WhenRequestingUserIfUnknownUser() throws Exception
HttpServletRequest req = mock( HttpServletRequest.class ); HttpServletRequest req = mock( HttpServletRequest.class );
when( req.getUserPrincipal() ).thenReturn( neo4jPrinciple ); when( req.getUserPrincipal() ).thenReturn( neo4jPrinciple );


BasicAuthManager authManager = mock( BasicAuthManager.class ); userManager.deleteUser( "neo4j" );
UserManager userManager = mock( UserManager.class );
when( authManager.getUserManager() ).thenReturn( userManager );
when( userManager.getUser( "neo4j" ) )
.thenThrow( new InvalidArgumentsException( "User 'neo4j' does not exist!" ) );


OutputFormat outputFormat = new EntityOutputFormat( new JsonFormat(), new URI( "http://www.example.com" ), null ); OutputFormat outputFormat = new EntityOutputFormat( new JsonFormat(), new URI( "http://www.example.com" ), null );
UserService userService = new UserService( authManager, new JsonFormat(), outputFormat ); UserService userService = new UserService( authManager, new JsonFormat(), outputFormat );
Expand All @@ -189,11 +174,6 @@ public void shouldChangePasswordAndReturnSuccess() throws Exception
HttpServletRequest req = mock( HttpServletRequest.class ); HttpServletRequest req = mock( HttpServletRequest.class );
when( req.getUserPrincipal() ).thenReturn( neo4jPrinciple ); 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 ); OutputFormat outputFormat = new EntityOutputFormat( new JsonFormat(), new URI( "http://www.example.com" ), null );
UserService userService = new UserService( authManager, new JsonFormat(), outputFormat ); UserService userService = new UserService( authManager, new JsonFormat(), outputFormat );


Expand Down Expand Up @@ -368,16 +348,14 @@ public void shouldReturn422IfPasswordIdentical() throws Exception
} }


@Test @Test
public void shouldThrowExceptionIfGivenAuthManagerDoesNotImplementUserManager() throws URISyntaxException public void shouldThrowExceptionIfGivenAuthManagerDoesNotImplementUserManager() throws Exception
{ {
// Given // Given
OutputFormat outputFormat = new EntityOutputFormat( new JsonFormat(), new URI( "http://www.example.com" ), null ); 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 // 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" );
} }
} }
@@ -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 <http://www.gnu.org/licenses/>.
*/
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 ) );
}
}
16 changes: 16 additions & 0 deletions enterprise/server-enterprise/pom.xml
Expand Up @@ -158,6 +158,22 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>


<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-security</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-logging</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>

<dependency> <dependency>
<groupId>org.neo4j.app</groupId> <groupId>org.neo4j.app</groupId>
<artifactId>neo4j-server</artifactId> <artifactId>neo4j-server</artifactId>
Expand Down
@@ -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 <http://www.gnu.org/licenses/>.
*/
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." );
}
}

0 comments on commit 51dbff9

Please sign in to comment.