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.
Original file line number Original file line Diff line number Diff line change
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;
} }
} }
Original file line number Original file line Diff line number Diff line change
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
Original file line number Original file line Diff line number Diff line change
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
Original file line number Original file line Diff line number Diff line change
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
Loading

0 comments on commit f47efa3

Please sign in to comment.