- * Through the BasicAuthManager you can create, update and delete users, and authenticate using credentials.
+ * Through the BasicAuthManager you can create, update and delete userRepository, and authenticate using credentials.
*
>
* NOTE: AuthManager will manage the lifecycle of the given UserRepository,
* so the given UserRepository should not be added to another LifeSupport.
@@ -44,33 +45,33 @@
public class BasicAuthManager implements AuthManager, UserManager, UserManagerSupplier
{
protected final AuthenticationStrategy authStrategy;
- protected final UserRepository users;
+ protected final UserRepository userRepository;
protected final PasswordPolicy passwordPolicy;
- public BasicAuthManager( UserRepository users, PasswordPolicy passwordPolicy, AuthenticationStrategy authStrategy )
+ public BasicAuthManager( UserRepository userRepository, PasswordPolicy passwordPolicy, AuthenticationStrategy authStrategy )
{
- this.users = users;
+ this.userRepository = userRepository;
this.passwordPolicy = passwordPolicy;
this.authStrategy = authStrategy;
}
- public BasicAuthManager( UserRepository users, PasswordPolicy passwordPolicy, Clock clock )
+ public BasicAuthManager( UserRepository userRepository, PasswordPolicy passwordPolicy, Clock clock )
{
- this( users, passwordPolicy, new RateLimitedAuthenticationStrategy( clock, 3 ) );
+ this( userRepository, passwordPolicy, new RateLimitedAuthenticationStrategy( clock, 3 ) );
}
@Override
public void init() throws Throwable
{
- users.init();
+ userRepository.init();
}
@Override
public void start() throws Throwable
{
- users.start();
+ userRepository.start();
- if ( users.numberOfUsers() == 0 )
+ if ( userRepository.numberOfUsers() == 0 )
{
newUser( "neo4j", "neo4j", true );
}
@@ -79,22 +80,22 @@ public void start() throws Throwable
@Override
public void stop() throws Throwable
{
- users.stop();
+ userRepository.stop();
}
@Override
public void shutdown() throws Throwable
{
- users.shutdown();
+ userRepository.shutdown();
}
@Override
- public AuthSubject login( Map authToken ) throws InvalidAuthTokenException
+ public BasicAuthSubject login( Map authToken ) throws InvalidAuthTokenException
{
String username = AuthToken.safeCast( AuthToken.PRINCIPAL, authToken );
String password = AuthToken.safeCast( AuthToken.CREDENTIALS, authToken );
- User user = users.getUserByName( username );
+ User user = userRepository.getUserByName( username );
AuthenticationResult result = AuthenticationResult.FAILURE;
if ( user != null )
{
@@ -111,30 +112,34 @@ public AuthSubject login( Map authToken ) throws InvalidAuthToken
public User newUser( String username, String initialPassword, boolean requirePasswordChange ) throws IOException,
InvalidArgumentsException
{
- assertValidName( username );
+ assertValidUsername( username );
+
+ passwordPolicy.validatePassword( initialPassword );
+
User user = new User.Builder()
.withName( username )
.withCredentials( Credential.forPassword( initialPassword ) )
.withRequiredPasswordChange( requirePasswordChange )
.build();
- users.create( user );
+ userRepository.create( user );
+
return user;
}
@Override
- public boolean deleteUser( String username ) throws IOException
+ public boolean deleteUser( String username ) throws IOException, InvalidArgumentsException
{
- User user = users.getUserByName( username );
- return user != null && users.delete( user );
+ User user = getUser( username );
+ return user != null && userRepository.delete( user );
}
@Override
public User getUser( String username ) throws InvalidArgumentsException
{
- User user = users.getUserByName( username );
+ User user = userRepository.getUserByName( username );
if ( user == null )
{
- throw new InvalidArgumentsException( "User '" + username + "' does not exist!" );
+ throw new InvalidArgumentsException( "User '" + username + "' does not exist." );
}
return user;
}
@@ -156,11 +161,7 @@ public void setPassword( AuthSubject authSubject, String username, String passwo
public void setUserPassword( String username, String password ) throws IOException,
InvalidArgumentsException
{
- User existingUser = users.getUserByName( username );
- if ( existingUser == null )
- {
- throw new InvalidArgumentsException( "User '" + username + "' does not exist" );
- }
+ User existingUser = getUser( username );
passwordPolicy.validatePassword( password );
@@ -175,7 +176,7 @@ public void setUserPassword( String username, String password ) throws IOExcepti
.withCredentials( Credential.forPassword( password ) )
.withRequiredPasswordChange( false )
.build();
- users.update( existingUser, updatedUser );
+ userRepository.update( existingUser, updatedUser );
} catch ( ConcurrentModificationException e )
{
// try again
@@ -183,11 +184,23 @@ public void setUserPassword( String username, String password ) throws IOExcepti
}
}
- private void assertValidName( String name ) throws InvalidArgumentsException
+ @Override
+ public Set getAllUsernames()
{
- if ( !users.isValidUsername( name ) )
+ return userRepository.getAllUsernames();
+ }
+
+ private void assertValidUsername( String name ) throws InvalidArgumentsException
+ {
+ if ( name.isEmpty() )
+ {
+ throw new InvalidArgumentsException( "The provided user name is empty." );
+ }
+ if ( !userRepository.isValidUsername( name ) )
{
- throw new InvalidArgumentsException( "User name contains illegal characters. Please use simple ascii characters and numbers." );
+ throw new InvalidArgumentsException(
+ "User name '" + name +
+ "' contains illegal characters. Use simple ascii characters and numbers." );
}
}
diff --git a/community/security/src/main/java/org/neo4j/server/security/auth/BasicAuthSubject.java b/community/security/src/main/java/org/neo4j/server/security/auth/BasicAuthSubject.java
index a50b9be063f87..d873ab7bfde1e 100644
--- a/community/security/src/main/java/org/neo4j/server/security/auth/BasicAuthSubject.java
+++ b/community/security/src/main/java/org/neo4j/server/security/auth/BasicAuthSubject.java
@@ -97,6 +97,11 @@ public void setPassword( String password ) throws IOException, InvalidArgumentsE
}
}
+ public BasicAuthManager getAuthManager()
+ {
+ return authManager;
+ }
+
public boolean doesUsernameMatch( String username )
{
return user.name().equals( username );
@@ -135,6 +140,6 @@ public AuthorizationViolationException onViolation( String msg )
@Override
public String name()
{
- return accessMode.name();
+ return user.name();
}
}
diff --git a/community/security/src/main/java/org/neo4j/server/security/auth/UserManager.java b/community/security/src/main/java/org/neo4j/server/security/auth/UserManager.java
index 9ebe2849bdc0c..95dcc5f6af76a 100644
--- a/community/security/src/main/java/org/neo4j/server/security/auth/UserManager.java
+++ b/community/security/src/main/java/org/neo4j/server/security/auth/UserManager.java
@@ -20,6 +20,7 @@
package org.neo4j.server.security.auth;
import java.io.IOException;
+import java.util.Set;
import org.neo4j.kernel.api.security.exception.InvalidArgumentsException;
@@ -33,4 +34,6 @@ User newUser( String username, String initialPassword, boolean requirePasswordCh
User getUser( String username ) throws InvalidArgumentsException;
void setUserPassword( String username, String password ) throws IOException, InvalidArgumentsException;
+
+ Set getAllUsernames();
}
diff --git a/community/security/src/test/java/org/neo4j/server/security/auth/AuthProceduresIT.java b/community/security/src/test/java/org/neo4j/server/security/auth/AuthProceduresIT.java
new file mode 100644
index 0000000000000..a2d7c88f6a060
--- /dev/null
+++ b/community/security/src/test/java/org/neo4j/server/security/auth/AuthProceduresIT.java
@@ -0,0 +1,323 @@
+/*
+ * 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.server.security.auth;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.neo4j.graphdb.GraphDatabaseService;
+import org.neo4j.graphdb.ResourceIterator;
+import org.neo4j.graphdb.Transaction;
+import org.neo4j.graphdb.config.Setting;
+import org.neo4j.graphdb.factory.GraphDatabaseSettings;
+import org.neo4j.graphdb.mockfs.EphemeralFileSystemAbstraction;
+import org.neo4j.kernel.api.KernelTransaction;
+import org.neo4j.kernel.api.security.exception.InvalidArgumentsException;
+import org.neo4j.kernel.api.security.exception.InvalidAuthTokenException;
+import org.neo4j.kernel.internal.GraphDatabaseAPI;
+import org.neo4j.test.TestGraphDatabaseBuilder;
+import org.neo4j.test.TestGraphDatabaseFactory;
+
+import static junit.framework.TestCase.fail;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.neo4j.helpers.collection.MapUtil.map;
+import static org.neo4j.kernel.api.security.AuthenticationResult.PASSWORD_CHANGE_REQUIRED;
+
+public class AuthProceduresIT
+{
+ static final String PWD_CHANGE = PASSWORD_CHANGE_REQUIRED.name().toLowerCase();
+
+ protected GraphDatabaseAPI db;
+ private EphemeralFileSystemAbstraction fs;
+ private BasicAuthManager authManager;
+ private BasicAuthSubject admin;
+
+ //---------- change password -----------
+
+ @Test
+ public void shouldChangePassword() throws Throwable
+ {
+
+ // Given
+ assertEmpty( admin, "CALL dbms.changePassword('abc')" );
+
+ assert( authManager.getUser( "neo4j" ).credentials().matchesPassword( "abc" ) );
+ }
+
+ @Test
+ public void shouldNotChangeOwnPasswordIfNewPasswordInvalid() throws Exception
+ {
+ assertFail( admin, "CALL dbms.changePassword( '' )", "Password cannot be empty." );
+ assertFail( admin, "CALL dbms.changePassword( 'neo4j' )", "Old password and new password cannot be the same." );
+ }
+
+ //---------- create user -----------
+
+ @Test
+ public void shouldCreateUser() throws Exception
+ {
+ assertEmpty( admin, "CALL dbms.communityAuth.createUser('andres', '123', true)" );
+ try
+ {
+ authManager.getUser( "andres" );
+ }
+ catch ( Throwable t )
+ {
+ fail( "Expected no exception!" );
+ }
+ }
+
+ @Test
+ public void shouldNotCreateUserIfInvalidUsername() throws Exception
+ {
+ assertFail( admin, "CALL dbms.communityAuth.createUser('', '1234', true)", "The provided user name is empty." );
+ assertFail( admin, "CALL dbms.communityAuth.createUser('&%ss!', '1234', true)",
+ "User name '&%ss!' contains illegal characters." );
+ assertFail( admin, "CALL dbms.communityAuth.createUser('&%ss!', '', true)", "User name '&%ss!' contains illegal characters." );
+ }
+
+ @Test
+ public void shouldNotCreateUserIfInvalidPassword() throws Exception
+ {
+ assertFail( admin, "CALL dbms.communityAuth.createUser('andres', '', true)", "Password cannot be empty." );
+ }
+
+ @Test
+ public void shouldNotCreateExistingUser() throws Exception
+ {
+ assertFail( admin, "CALL dbms.communityAuth.createUser('neo4j', '1234', true)",
+ "The specified user already exists" );
+ assertFail( admin, "CALL dbms.communityAuth.createUser('neo4j', '', true)", "Password cannot be empty." );
+ }
+
+ //---------- delete user -----------
+
+ @Test
+ public void shouldDeleteUser() throws Exception
+ {
+ authManager.newUser( "andres", "123", false );
+ assertEmpty( admin, "CALL dbms.communityAuth.deleteUser('andres')" );
+ try
+ {
+ authManager.getUser( "andres" );
+ fail("Andres should no longer exist, expected exception.");
+ }
+ catch ( InvalidArgumentsException e )
+ {
+ assertThat( e.getMessage(), containsString( "User 'andres' does not exist." ) );
+ }
+ catch ( Throwable t )
+ {
+ assertThat( t.getClass(), equalTo( InvalidArgumentsException.class ) );
+ }
+ }
+
+ @Test
+ public void shouldNotDeleteNonExistentUser() throws Exception
+ {
+ assertFail( admin, "CALL dbms.communityAuth.deleteUser('nonExistentUser')", "User 'nonExistentUser' does not exist" );
+ }
+
+ //---------- list users -----------
+
+ @Test
+ public void shouldListUsers() throws Exception
+ {
+ authManager.newUser( "andres", "123", false );
+ assertSuccess( admin, "CALL dbms.communityAuth.listUsers() YIELD username",
+ r -> assertKeyIs( r, "username", "neo4j", "andres" ) );
+ }
+
+ @Test
+ public void shouldReturnUsersWithFlags() throws Exception
+ {
+ authManager.newUser( "andres", "123", false );
+ Map expected = map(
+ "neo4j", listOf( PWD_CHANGE ),
+ "andres", listOf()
+ );
+ assertSuccess( admin, "CALL dbms.communityAuth.listUsers()",
+ r -> assertKeyIsMap( r, "username", "flags", expected ) );
+ }
+
+ @Test
+ public void shouldShowCurrentUser() throws Exception
+ {
+ assertSuccess( admin, "CALL dbms.communityAuth.showCurrentUser()",
+ r -> assertKeyIsMap( r, "username", "flags", map( "neo4j", listOf( PWD_CHANGE ) ) ) );
+
+ authManager.newUser( "andres", "123", false );
+ BasicAuthSubject andres = login( "andres", "123" );
+ assertSuccess( andres, "CALL dbms.communityAuth.showCurrentUser()",
+ r -> assertKeyIsMap( r, "username", "flags", map( "andres", listOf() ) ) );
+ }
+
+ //---------- utility -----------
+
+ @Before
+ public void setup() throws InvalidAuthTokenException
+ {
+ fs = new EphemeralFileSystemAbstraction();
+ db = (GraphDatabaseAPI) createGraphDatabase( fs );
+ authManager = db.getDependencyResolver().resolveDependency( BasicAuthManager.class );
+ admin = login( "neo4j", "neo4j" );
+ }
+
+ @After
+ public void cleanup() throws Exception
+ {
+ db.shutdown();
+ fs.shutdown();
+ }
+
+ protected GraphDatabaseService createGraphDatabase( EphemeralFileSystemAbstraction fs )
+ {
+
+ Map, String> settings = new HashMap<>();
+ settings.put( GraphDatabaseSettings.auth_enabled, "true" );
+ settings.put( GraphDatabaseSettings.auth_manager, "basic-auth-manager" );
+
+ TestGraphDatabaseBuilder graphDatabaseFactory = (TestGraphDatabaseBuilder) new TestGraphDatabaseFactory()
+ .setFileSystem( fs )
+ .newImpermanentDatabaseBuilder()
+ .setConfig( GraphDatabaseSettings.auth_enabled, "true" )
+ .setConfig( GraphDatabaseSettings.auth_manager, "basic-auth-manager" );
+
+ return graphDatabaseFactory.newGraphDatabase();
+ }
+
+ protected BasicAuthSubject login( String username, String password ) throws InvalidAuthTokenException
+ {
+ return authManager.login( SecurityTestUtils.authToken( username, password ) );
+ }
+
+ private void assertEmpty( BasicAuthSubject subject, String query )
+ {
+ assertThat(
+ execute( subject, query, r -> { assert(!r.hasNext() ); } ),
+ equalTo( "" ) );
+ }
+
+ private void assertFail( BasicAuthSubject subject, String query, String partOfErrorMsg )
+ {
+ assertThat(
+ execute( subject, query, r -> { assert(!r.hasNext() ); } ),
+ containsString( partOfErrorMsg ) );
+ }
+
+ void assertSuccess( BasicAuthSubject subject, String query,
+ Consumer>> resultConsumer )
+ {
+ assertThat(
+ execute( subject, query, resultConsumer ),
+ equalTo( "" ) );
+ }
+
+ private String execute( BasicAuthSubject subject, String query,
+ Consumer>> resultConsumer )
+ {
+ try ( Transaction tx = db.beginTransaction( KernelTransaction.Type.implicit, subject ) )
+ {
+ resultConsumer.accept( db.execute( query ) );
+ tx.success();
+ return "";
+ }
+ catch ( Exception e )
+ {
+ return e.getMessage();
+ }
+ }
+
+ List