Skip to content

Commit

Permalink
Completion of suspend / activate user.
Browse files Browse the repository at this point in the history
* general purpose flags on User class and it's serialization
* cleaned up User builder
* added UserTest on immutability
* improved AuthProcedureTests on suspend / activate user
  • Loading branch information
fickludd committed Jun 9, 2016
1 parent 1f5445a commit f47efa3
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 155 deletions.
Expand Up @@ -19,6 +19,10 @@
*/ */
package org.neo4j.server.security.auth; package org.neo4j.server.security.auth;


import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/** /**
* Controls authorization and authentication for an individual user. * Controls authorization and authentication for an individual user.
*/ */
Expand All @@ -35,23 +39,16 @@ public class User
/** Authentication credentials used by the built in username/password authentication scheme */ /** Authentication credentials used by the built in username/password authentication scheme */
private final Credential credential; private final Credential credential;


/** Whether a password change is needed */ /** Set of flags, eg. require_password_change */
private final boolean passwordChangeRequired; private final HashSet<String> flags;

/** User is suspended */
private final boolean isSuspended;


public User( String name, Credential credential, boolean passwordChangeRequired ) public static final String PASSWORD_CHANGE_REQUIRED = "password_change_required";
{
this(name, credential, passwordChangeRequired, false);
}


private User( String name, Credential credential, boolean passwordChangeRequired, boolean isSuspended ) private User( String name, Credential credential, HashSet<String> flags )
{ {
this.name = name; this.name = name;
this.credential = credential; this.credential = credential;
this.passwordChangeRequired = passwordChangeRequired; this.flags = flags;
this.isSuspended = isSuspended;
} }


public String name() public String name()
Expand All @@ -64,8 +61,10 @@ public Credential credentials()
return credential; return credential;
} }


public boolean passwordChangeRequired() { return passwordChangeRequired; } public boolean hasFlag(String flag) { return flags.contains(flag); }
public boolean isSuspended() { return isSuspended; } public Iterator<String> getFlags() { return flags.iterator(); }

public boolean passwordChangeRequired() { return flags.contains( PASSWORD_CHANGE_REQUIRED ); }


/** Use this user as a base for a new user object */ /** Use this user as a base for a new user object */
public Builder augment() { return new Builder(this); } public Builder augment() { return new Builder(this); }
Expand All @@ -84,11 +83,7 @@ public boolean equals( Object o )


User user = (User) o; User user = (User) o;


if ( passwordChangeRequired != user.passwordChangeRequired ) if ( !setsAreEquals( flags, user.flags ) )
{
return false;
}
if ( isSuspended != user.isSuspended )
{ {
return false; return false;
} }
Expand All @@ -109,8 +104,7 @@ public int hashCode()
{ {
int result = name != null ? name.hashCode() : 0; int result = name != null ? name.hashCode() : 0;
result = 31 * result + ( credential != null ? credential.hashCode() : 0); result = 31 * result + ( credential != null ? credential.hashCode() : 0);
result = 31 * result + (passwordChangeRequired ? 1 : 0); result = 31 * result + ( flags.hashCode() );
result = 31 * result + (isSuspended ? 1 : 0);
return result; return result;
} }


Expand All @@ -120,36 +114,69 @@ public String toString()
return "User{" + return "User{" +
"name='" + name + '\'' + "name='" + name + '\'' +
", credentials=" + credential + ", credentials=" + credential +
", passwordChangeRequired=" + passwordChangeRequired + ", flags=" + flags.toString() +
", isSuspended=" + isSuspended +
'}'; '}';
} }


public static class Builder public static class Builder
{ {
private String name; private String name;
private Credential credential = Credential.INACCESSIBLE; private Credential credential = Credential.INACCESSIBLE;
private boolean pwdChangeRequired; private HashSet<String> flags = new HashSet<>();
private boolean isSuspended;


public Builder() { } public Builder() { }


public Builder( String name, Credential credential )
{
this.name = name;
this.credential = credential;
}

public Builder( User base ) public Builder( User base )
{ {
name = base.name; name = base.name;
credential = base.credential; credential = base.credential;
pwdChangeRequired = base.passwordChangeRequired; flags.addAll( base.flags );
isSuspended = base.isSuspended;
} }


public Builder withName( String name ) { this.name = name; return this; } public Builder withName( String name ) { this.name = name; return this; }
public Builder withCredentials( Credential creds ) { this.credential = creds; return this; } public Builder withCredentials( Credential creds ) { this.credential = creds; return this; }
public Builder withRequiredPasswordChange( boolean change ) { this.pwdChangeRequired = change; return this; } public Builder withFlag( String flag ) { this.flags.add(flag); return this; }
public Builder withIsSuspended( boolean suspended ) { this.isSuspended = suspended; return this; } public Builder withoutFlag( String flag ) { this.flags.remove(flag); return this; }

public Builder withRequiredPasswordChange( boolean change )
{
if ( change )
{
withFlag( PASSWORD_CHANGE_REQUIRED );
}
else
{
withoutFlag( PASSWORD_CHANGE_REQUIRED );
}
return this;
}


public User build() public User build()
{ {
return new User(name, credential, pwdChangeRequired, isSuspended ); return new User(name, credential, flags );
}
}

private static boolean setsAreEquals( Set<String> set1, Set<String> set2 )
{
if ( set1.size() != set2.size() )
{
return false;
}

for ( String element : set1 )
{
if ( !set2.contains( element ) )
{
return false;
}
} }
return true;
} }
} }
Expand Up @@ -20,7 +20,9 @@
package org.neo4j.server.security.auth; package org.neo4j.server.security.auth;


import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.List; import java.util.List;


import org.neo4j.string.HexString; import org.neo4j.string.HexString;
Expand Down Expand Up @@ -73,36 +75,33 @@ private String serialize( User user )
{ {
return join( userSeparator, user.name(), return join( userSeparator, user.name(),
serialize( user.credentials() ), serialize( user.credentials() ),
user.passwordChangeRequired() ? "password_change_required" : "", join( ",", user.getFlags() )
user.isSuspended() ? "is_suspended" : "" ); );
} }


private User deserializeUser( String line, int lineNumber ) throws FormatException private User deserializeUser( String line, int lineNumber ) throws FormatException
{ {
String[] parts = line.split( userSeparator, -1 ); String[] parts = line.split( userSeparator, -1 );
boolean isSuspended; if ( parts.length != 3 )
if ( parts.length == 3 ) // Before suspension impl. assume non-suspended user
{ {
isSuspended = false; throw new FormatException( format( "wrong number of line fields, expected 3, got %d [line %d]",
}
else if ( parts.length == 4 )
{
isSuspended = parts[3].equals( "is_suspended" );
}
else
{
throw new FormatException( format( "wrong number of line fields, expected 3 or 4, got %d [line %d]",
parts.length, parts.length,
lineNumber lineNumber
) ); ) );
} }


return new User.Builder() User.Builder b = new User.Builder()
.withName( parts[0] ) .withName( parts[0] )
.withCredentials( deserializeCredentials( parts[1], lineNumber ) ) .withCredentials( deserializeCredentials( parts[1], lineNumber ) );
.withRequiredPasswordChange( parts[2].equals( "password_change_required" ) )
.withIsSuspended( isSuspended ) for ( String flag : parts[2].split( ",", -1 ))
.build(); {
String trimmed = flag.trim();
if (!trimmed.isEmpty())
b = b.withFlag( trimmed );
}

return b.build();
} }


private String serialize( Credential cred ) private String serialize( Credential cred )
Expand All @@ -129,12 +128,19 @@ private Credential deserializeCredentials( String part, int lineNumber ) throws
} }


private String join( String separator, String... segments ) private String join( String separator, String... segments )
{
return join(separator, Arrays.asList(segments).iterator() );
}

private String join( String separator, Iterator<String> segments )
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for ( int i = 0; i < segments.length; i++ ) boolean afterFirst = false;
while (segments.hasNext())
{ {
if(i > 0) { sb.append( separator ); } if ( afterFirst ) { sb.append( separator ); }
sb.append( segments[i] == null ? "" : segments[i] ); else { afterFirst = true; }
sb.append( segments.next() );
} }
return sb.toString(); return sb.toString();
} }
Expand Down
Expand Up @@ -59,7 +59,7 @@ public void shouldFindAndAuthenticateUserSuccessfully() throws Throwable
{ {
// Given // Given
final InMemoryUserRepository users = new InMemoryUserRepository(); final InMemoryUserRepository users = new InMemoryUserRepository();
final User user = new User( "jake", Credential.forPassword( "abc123" ), false ); final User user = new User.Builder( "jake", Credential.forPassword( "abc123" )).build();
users.create( user ); users.create( user );
final AuthenticationStrategy authStrategy = mock( AuthenticationStrategy.class ); final AuthenticationStrategy authStrategy = mock( AuthenticationStrategy.class );
final BasicAuthManager manager = new BasicAuthManager( users, mock( PasswordPolicy.class ), authStrategy ); final BasicAuthManager manager = new BasicAuthManager( users, mock( PasswordPolicy.class ), authStrategy );
Expand All @@ -79,7 +79,7 @@ public void shouldFindAndAuthenticateUserAndReturnAuthStrategyResult() throws Th
{ {
// Given // Given
final InMemoryUserRepository users = new InMemoryUserRepository(); final InMemoryUserRepository users = new InMemoryUserRepository();
final User user = new User( "jake", Credential.forPassword( "abc123" ), true ); final User user = new User.Builder( "jake", Credential.forPassword( "abc123" )).withRequiredPasswordChange( true ).build();
users.create( user ); users.create( user );
final AuthenticationStrategy authStrategy = mock( AuthenticationStrategy.class ); final AuthenticationStrategy authStrategy = mock( AuthenticationStrategy.class );
final BasicAuthManager manager = new BasicAuthManager( users, mock( PasswordPolicy.class ), authStrategy ); final BasicAuthManager manager = new BasicAuthManager( users, mock( PasswordPolicy.class ), authStrategy );
Expand All @@ -99,7 +99,7 @@ public void shouldFindAndAuthenticateUserAndReturnPasswordChangeIfRequired() thr
{ {
// Given // Given
final InMemoryUserRepository users = new InMemoryUserRepository(); final InMemoryUserRepository users = new InMemoryUserRepository();
final User user = new User( "jake", Credential.forPassword( "abc123" ), true ); final User user = new User.Builder( "jake", Credential.forPassword( "abc123" )).withRequiredPasswordChange( true ).build();
users.create( user ); users.create( user );
final AuthenticationStrategy authStrategy = mock( AuthenticationStrategy.class ); final AuthenticationStrategy authStrategy = mock( AuthenticationStrategy.class );
final BasicAuthManager manager = new BasicAuthManager( users, mock( PasswordPolicy.class ), authStrategy ); final BasicAuthManager manager = new BasicAuthManager( users, mock( PasswordPolicy.class ), authStrategy );
Expand All @@ -119,7 +119,7 @@ public void shouldFailAuthenticationIfUserIsNotFound() throws Throwable
{ {
// Given // Given
final InMemoryUserRepository users = new InMemoryUserRepository(); final InMemoryUserRepository users = new InMemoryUserRepository();
final User user = new User( "jake", Credential.forPassword( "abc123" ), true ); final User user = new User.Builder( "jake", Credential.forPassword( "abc123" )).withRequiredPasswordChange( true ).build();
users.create( user ); users.create( user );
final AuthenticationStrategy authStrategy = mock( AuthenticationStrategy.class ); final AuthenticationStrategy authStrategy = mock( AuthenticationStrategy.class );
final BasicAuthManager manager = new BasicAuthManager( users, mock( PasswordPolicy.class ), authStrategy ); final BasicAuthManager manager = new BasicAuthManager( users, mock( PasswordPolicy.class ), authStrategy );
Expand Down Expand Up @@ -157,7 +157,7 @@ public void shouldDeleteUser() throws Throwable
{ {
// Given // Given
final InMemoryUserRepository users = new InMemoryUserRepository(); final InMemoryUserRepository users = new InMemoryUserRepository();
final User user = new User( "jake", Credential.forPassword( "abc123" ), true ); final User user = new User.Builder( "jake", Credential.forPassword( "abc123" )).withRequiredPasswordChange( true ).build();
users.create( user ); users.create( user );
final BasicAuthManager manager = final BasicAuthManager manager =
new BasicAuthManager( users, mock( PasswordPolicy.class ), mock( AuthenticationStrategy.class ) ); new BasicAuthManager( users, mock( PasswordPolicy.class ), mock( AuthenticationStrategy.class ) );
Expand All @@ -175,7 +175,7 @@ public void shouldDeleteUnknownUser() throws Throwable
{ {
// Given // Given
final InMemoryUserRepository users = new InMemoryUserRepository(); final InMemoryUserRepository users = new InMemoryUserRepository();
final User user = new User( "jake", Credential.forPassword( "abc123" ), true ); final User user = new User.Builder( "jake", Credential.forPassword( "abc123" )).withRequiredPasswordChange( true ).build();
users.create( user ); users.create( user );
final BasicAuthManager manager = final BasicAuthManager manager =
new BasicAuthManager( users, mock( PasswordPolicy.class ), mock( AuthenticationStrategy.class ) ); new BasicAuthManager( users, mock( PasswordPolicy.class ), mock( AuthenticationStrategy.class ) );
Expand All @@ -193,7 +193,7 @@ public void shouldSetPassword() throws Throwable
{ {
// Given // Given
final InMemoryUserRepository users = new InMemoryUserRepository(); final InMemoryUserRepository users = new InMemoryUserRepository();
users.create( new User( "jake", Credential.forPassword( "abc123" ), true ) ); users.create( new User.Builder( "jake", Credential.forPassword( "abc123" )).withRequiredPasswordChange( true ).build() );
final BasicAuthManager manager = final BasicAuthManager manager =
new BasicAuthManager( users, mock( PasswordPolicy.class ), mock( AuthenticationStrategy.class ) ); new BasicAuthManager( users, mock( PasswordPolicy.class ), mock( AuthenticationStrategy.class ) );
manager.start(); manager.start();
Expand Down
Expand Up @@ -83,7 +83,7 @@ public void shouldStoreAndRetriveUsersByName() throws Exception
{ {
// Given // Given
FileUserRepository users = new FileUserRepository( authFile, NullLogProvider.getInstance() ); FileUserRepository users = new FileUserRepository( authFile, NullLogProvider.getInstance() );
User user = new User( "jake", Credential.INACCESSIBLE, true ); User user = new User.Builder( "jake", Credential.INACCESSIBLE ).withRequiredPasswordChange( true ).build();
users.create( user ); users.create( user );


// When // When
Expand All @@ -98,7 +98,7 @@ public void shouldPersistUsers() throws Throwable
{ {
// Given // Given
FileUserRepository users = new FileUserRepository( authFile, NullLogProvider.getInstance() ); FileUserRepository users = new FileUserRepository( authFile, NullLogProvider.getInstance() );
User user = new User( "jake", Credential.INACCESSIBLE, true ); User user = new User.Builder( "jake", Credential.INACCESSIBLE ).withRequiredPasswordChange( true ).build();
users.create( user ); users.create( user );


users = new FileUserRepository( authFile, NullLogProvider.getInstance() ); users = new FileUserRepository( authFile, NullLogProvider.getInstance() );
Expand All @@ -116,7 +116,7 @@ public void shouldNotFindUserAfterDelete() throws Throwable
{ {
// Given // Given
FileUserRepository users = new FileUserRepository( authFile, NullLogProvider.getInstance() ); FileUserRepository users = new FileUserRepository( authFile, NullLogProvider.getInstance() );
User user = new User( "jake", Credential.INACCESSIBLE, true ); User user = new User.Builder( "jake", Credential.INACCESSIBLE ).withRequiredPasswordChange( true ).build();
users.create( user ); users.create( user );


// When // When
Expand Down Expand Up @@ -172,7 +172,7 @@ public void move( Path source, Path target, CopyOption... options ) throws IOExc


FileUserRepository users = new FileUserRepository( authFile, NullLogProvider.getInstance() ); FileUserRepository users = new FileUserRepository( authFile, NullLogProvider.getInstance() );
users.start(); users.start();
User user = new User( "jake", Credential.INACCESSIBLE, true ); User user = new User.Builder( "jake", Credential.INACCESSIBLE ).withRequiredPasswordChange( true ).build();


// When // When
try try
Expand All @@ -194,11 +194,12 @@ public void shouldThrowIfUpdateChangesName() throws Throwable
{ {
// Given // Given
FileUserRepository users = new FileUserRepository( authFile, NullLogProvider.getInstance() ); FileUserRepository users = new FileUserRepository( authFile, NullLogProvider.getInstance() );
User user = new User( "jake", Credential.INACCESSIBLE, true ); User user = new User.Builder( "jake", Credential.INACCESSIBLE ).withRequiredPasswordChange( true ).build();
users.create( user ); users.create( user );


// When // When
User updatedUser = new User( "john", Credential.INACCESSIBLE, true ); User updatedUser = new User.Builder( "john", Credential.INACCESSIBLE ).withRequiredPasswordChange( true )
.build();
try try
{ {
users.update( user, updatedUser ); users.update( user, updatedUser );
Expand All @@ -216,12 +217,12 @@ public void shouldThrowIfExistingUserDoesNotMatch() throws Throwable
{ {
// Given // Given
FileUserRepository users = new FileUserRepository( authFile, NullLogProvider.getInstance() ); FileUserRepository users = new FileUserRepository( authFile, NullLogProvider.getInstance() );
User user = new User( "jake", Credential.INACCESSIBLE, true ); User user = new User.Builder( "jake", Credential.INACCESSIBLE ).withRequiredPasswordChange( true ).build();
users.create( user ); users.create( user );
User modifiedUser = new User( "jake", Credential.forPassword( "foo" ), false ); User modifiedUser = user.augment().withCredentials( Credential.forPassword( "foo" ) ).build();


// When // When
User updatedUser = new User( "jake", Credential.forPassword( "bar" ), false ); User updatedUser = user.augment().withCredentials( Credential.forPassword( "bar" ) ).build();
try try
{ {
users.update( modifiedUser, updatedUser ); users.update( modifiedUser, updatedUser );
Expand Down Expand Up @@ -261,7 +262,7 @@ public void shouldFailOnReadingInvalidEntries() throws Throwable
logProvider.assertExactly( logProvider.assertExactly(
AssertableLogProvider.inLog( FileUserRepository.class ).error( AssertableLogProvider.inLog( FileUserRepository.class ).error(
"Failed to read authentication file \"%s\" (%s)", authFile.toAbsolutePath(), "Failed to read authentication file \"%s\" (%s)", authFile.toAbsolutePath(),
"wrong number of line fields [line 2]" "wrong number of line fields, expected 3, got 4 [line 2]"
) )
); );
throw e; throw e;
Expand Down

0 comments on commit f47efa3

Please sign in to comment.