Skip to content

Commit

Permalink
Moved user management to community edition
Browse files Browse the repository at this point in the history
  • Loading branch information
fickludd committed Aug 24, 2016
1 parent dbcc0ba commit a224b4c
Show file tree
Hide file tree
Showing 10 changed files with 493 additions and 66 deletions.
6 changes: 6 additions & 0 deletions community/security/pom.xml
Expand Up @@ -96,6 +96,12 @@ the relevant Commercial Agreement.
<type>test-jar</type> <type>test-jar</type>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-cypher</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.neo4j</groupId> <groupId>org.neo4j</groupId>
<artifactId>neo4j-logging</artifactId> <artifactId>neo4j-logging</artifactId>
Expand Down
Expand Up @@ -20,6 +20,10 @@
package org.neo4j.server.security.auth; package org.neo4j.server.security.auth;


import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;


import org.neo4j.kernel.api.security.AuthSubject; import org.neo4j.kernel.api.security.AuthSubject;
import org.neo4j.kernel.api.security.exception.InvalidArgumentsException; import org.neo4j.kernel.api.security.exception.InvalidArgumentsException;
Expand All @@ -34,9 +38,68 @@ public class AuthProcedures
@Context @Context
public AuthSubject authSubject; public AuthSubject authSubject;


@Procedure( name = "dbms.communityAuth.createUser", mode = DBMS )
public void createUser( @Name( "username" ) String username, @Name( "password" ) String password,
@Name( "requirePasswordChange" ) boolean requirePasswordChange )
throws InvalidArgumentsException, IOException
{
BasicAuthSubject subject = BasicAuthSubject.castOrFail( authSubject );
subject.getAuthManager().newUser( username, password, requirePasswordChange );
}

@Procedure( name = "dbms.communityAuth.deleteUser", mode = DBMS )
public void deleteUser( @Name( "username" ) String username ) throws InvalidArgumentsException, IOException
{
BasicAuthSubject subject = BasicAuthSubject.castOrFail( authSubject );
if ( subject.doesUsernameMatch( username ) )
{
throw new InvalidArgumentsException( "Deleting yourself (user '" + username + "') is not allowed." );
}
subject.getAuthManager().deleteUser( username );
}

@Procedure( name = "dbms.changePassword", mode = DBMS ) @Procedure( name = "dbms.changePassword", mode = DBMS )
public void changePassword( @Name( "password" ) String password ) throws InvalidArgumentsException, IOException public void changePassword( @Name( "password" ) String password ) throws InvalidArgumentsException, IOException
{ {
authSubject.setPassword( password ); authSubject.setPassword( password );
} }

@Procedure( name = "dbms.communityAuth.showCurrentUser", mode = DBMS )
public Stream<UserResult> showCurrentUser() throws InvalidArgumentsException, IOException
{
BasicAuthSubject subject = BasicAuthSubject.castOrFail( authSubject );
return Stream.of( new UserResult(
subject.name(),
subject.getAuthManager().getUser( subject.name() ).getFlags()
) );
}

@Procedure( name = "dbms.communityAuth.listUsers", mode = DBMS )
public Stream<UserResult> listUsers() throws InvalidArgumentsException, IOException
{
BasicAuthSubject subject = BasicAuthSubject.castOrFail( authSubject );
Set<String> usernames = subject.getAuthManager().getAllUsernames();
List<UserResult> results = new ArrayList<>();
for ( String username : usernames )
{
results.add( new UserResult(
username,
subject.getAuthManager().getUser( username ).getFlags()
) );
}
return results.stream();
}

public static class UserResult
{
public final String username;
public final List<String> flags;

UserResult( String username, Iterable<String> flags )
{
this.username = username;
this.flags = new ArrayList<>();
for ( String f : flags ) {this.flags.add( f );}
}
}
} }
Expand Up @@ -22,6 +22,7 @@
import java.io.IOException; import java.io.IOException;
import java.time.Clock; import java.time.Clock;
import java.util.Map; import java.util.Map;
import java.util.Set;


import org.neo4j.graphdb.security.AuthorizationViolationException; import org.neo4j.graphdb.security.AuthorizationViolationException;
import org.neo4j.kernel.api.security.AuthManager; import org.neo4j.kernel.api.security.AuthManager;
Expand All @@ -35,7 +36,7 @@
/** /**
* Manages server authentication and authorization. * Manages server authentication and authorization.
* <p> * <p>
* 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.
* <p>> * <p>>
* NOTE: AuthManager will manage the lifecycle of the given UserRepository, * NOTE: AuthManager will manage the lifecycle of the given UserRepository,
* so the given UserRepository should not be added to another LifeSupport. * so the given UserRepository should not be added to another LifeSupport.
Expand All @@ -44,33 +45,33 @@
public class BasicAuthManager implements AuthManager, UserManager, UserManagerSupplier public class BasicAuthManager implements AuthManager, UserManager, UserManagerSupplier
{ {
protected final AuthenticationStrategy authStrategy; protected final AuthenticationStrategy authStrategy;
protected final UserRepository users; protected final UserRepository userRepository;
protected final PasswordPolicy passwordPolicy; 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.passwordPolicy = passwordPolicy;
this.authStrategy = authStrategy; 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 @Override
public void init() throws Throwable public void init() throws Throwable
{ {
users.init(); userRepository.init();
} }


@Override @Override
public void start() throws Throwable public void start() throws Throwable
{ {
users.start(); userRepository.start();


if ( users.numberOfUsers() == 0 ) if ( userRepository.numberOfUsers() == 0 )
{ {
newUser( "neo4j", "neo4j", true ); newUser( "neo4j", "neo4j", true );
} }
Expand All @@ -79,22 +80,22 @@ public void start() throws Throwable
@Override @Override
public void stop() throws Throwable public void stop() throws Throwable
{ {
users.stop(); userRepository.stop();
} }


@Override @Override
public void shutdown() throws Throwable public void shutdown() throws Throwable
{ {
users.shutdown(); userRepository.shutdown();
} }


@Override @Override
public AuthSubject login( Map<String,Object> authToken ) throws InvalidAuthTokenException public BasicAuthSubject login( Map<String,Object> authToken ) throws InvalidAuthTokenException
{ {
String username = AuthToken.safeCast( AuthToken.PRINCIPAL, authToken ); String username = AuthToken.safeCast( AuthToken.PRINCIPAL, authToken );
String password = AuthToken.safeCast( AuthToken.CREDENTIALS, authToken ); String password = AuthToken.safeCast( AuthToken.CREDENTIALS, authToken );


User user = users.getUserByName( username ); User user = userRepository.getUserByName( username );
AuthenticationResult result = AuthenticationResult.FAILURE; AuthenticationResult result = AuthenticationResult.FAILURE;
if ( user != null ) if ( user != null )
{ {
Expand All @@ -111,30 +112,34 @@ public AuthSubject login( Map<String,Object> authToken ) throws InvalidAuthToken
public User newUser( String username, String initialPassword, boolean requirePasswordChange ) throws IOException, public User newUser( String username, String initialPassword, boolean requirePasswordChange ) throws IOException,
InvalidArgumentsException InvalidArgumentsException
{ {
assertValidName( username ); assertValidUsername( username );

passwordPolicy.validatePassword( initialPassword );

User user = new User.Builder() User user = new User.Builder()
.withName( username ) .withName( username )
.withCredentials( Credential.forPassword( initialPassword ) ) .withCredentials( Credential.forPassword( initialPassword ) )
.withRequiredPasswordChange( requirePasswordChange ) .withRequiredPasswordChange( requirePasswordChange )
.build(); .build();
users.create( user ); userRepository.create( user );

return user; return user;
} }


@Override @Override
public boolean deleteUser( String username ) throws IOException public boolean deleteUser( String username ) throws IOException, InvalidArgumentsException
{ {
User user = users.getUserByName( username ); User user = getUser( username );
return user != null && users.delete( user ); return user != null && userRepository.delete( user );
} }


@Override @Override
public User getUser( String username ) throws InvalidArgumentsException public User getUser( String username ) throws InvalidArgumentsException
{ {
User user = users.getUserByName( username ); User user = userRepository.getUserByName( username );
if ( user == null ) if ( user == null )
{ {
throw new InvalidArgumentsException( "User '" + username + "' does not exist!" ); throw new InvalidArgumentsException( "User '" + username + "' does not exist." );
} }
return user; return user;
} }
Expand All @@ -156,11 +161,7 @@ public void setPassword( AuthSubject authSubject, String username, String passwo
public void setUserPassword( String username, String password ) throws IOException, public void setUserPassword( String username, String password ) throws IOException,
InvalidArgumentsException InvalidArgumentsException
{ {
User existingUser = users.getUserByName( username ); User existingUser = getUser( username );
if ( existingUser == null )
{
throw new InvalidArgumentsException( "User '" + username + "' does not exist" );
}


passwordPolicy.validatePassword( password ); passwordPolicy.validatePassword( password );


Expand All @@ -175,19 +176,31 @@ public void setUserPassword( String username, String password ) throws IOExcepti
.withCredentials( Credential.forPassword( password ) ) .withCredentials( Credential.forPassword( password ) )
.withRequiredPasswordChange( false ) .withRequiredPasswordChange( false )
.build(); .build();
users.update( existingUser, updatedUser ); userRepository.update( existingUser, updatedUser );
} catch ( ConcurrentModificationException e ) } catch ( ConcurrentModificationException e )
{ {
// try again // try again
setUserPassword( username, password ); setUserPassword( username, password );
} }
} }


private void assertValidName( String name ) throws InvalidArgumentsException @Override
public Set<String> 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." );
} }
} }


Expand Down
Expand Up @@ -97,6 +97,11 @@ public void setPassword( String password ) throws IOException, InvalidArgumentsE
} }
} }


public BasicAuthManager getAuthManager()
{
return authManager;
}

public boolean doesUsernameMatch( String username ) public boolean doesUsernameMatch( String username )
{ {
return user.name().equals( username ); return user.name().equals( username );
Expand Down Expand Up @@ -135,6 +140,6 @@ public AuthorizationViolationException onViolation( String msg )
@Override @Override
public String name() public String name()
{ {
return accessMode.name(); return user.name();
} }
} }
Expand Up @@ -20,6 +20,7 @@
package org.neo4j.server.security.auth; package org.neo4j.server.security.auth;


import java.io.IOException; import java.io.IOException;
import java.util.Set;


import org.neo4j.kernel.api.security.exception.InvalidArgumentsException; import org.neo4j.kernel.api.security.exception.InvalidArgumentsException;


Expand All @@ -33,4 +34,6 @@ User newUser( String username, String initialPassword, boolean requirePasswordCh
User getUser( String username ) throws InvalidArgumentsException; User getUser( String username ) throws InvalidArgumentsException;


void setUserPassword( String username, String password ) throws IOException, InvalidArgumentsException; void setUserPassword( String username, String password ) throws IOException, InvalidArgumentsException;

Set<String> getAllUsernames();
} }

0 comments on commit a224b4c

Please sign in to comment.