Skip to content

Commit

Permalink
Improved suspend/activate based on PR feedback.
Browse files Browse the repository at this point in the history
  • Loading branch information
fickludd committed Jun 9, 2016
1 parent 2a691fa commit 5d4f747
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 71 deletions.
Expand Up @@ -19,9 +19,8 @@
*/ */
package org.neo4j.server.security.auth; package org.neo4j.server.security.auth;


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


/** /**
* Controls authorization and authentication for an individual user. * Controls authorization and authentication for an individual user.
Expand All @@ -39,12 +38,12 @@ 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;


/** Set of flags, eg. require_password_change */ /** Set of flags, eg. password_change_required */
private final HashSet<String> flags; private final SortedSet<String> flags;


public static final String PASSWORD_CHANGE_REQUIRED = "password_change_required"; public static final String PASSWORD_CHANGE_REQUIRED = "password_change_required";


private User( String name, Credential credential, HashSet<String> flags ) private User( String name, Credential credential, SortedSet<String> flags )
{ {
this.name = name; this.name = name;
this.credential = credential; this.credential = credential;
Expand All @@ -62,7 +61,7 @@ public Credential credentials()
} }


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


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


Expand All @@ -83,7 +82,7 @@ public boolean equals( Object o )


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


if ( !setsAreEquals( flags, user.flags ) ) if ( !flags.equals( user.flags ) )
{ {
return false; return false;
} }
Expand Down Expand Up @@ -122,7 +121,7 @@ public static class Builder
{ {
private String name; private String name;
private Credential credential = Credential.INACCESSIBLE; private Credential credential = Credential.INACCESSIBLE;
private HashSet<String> flags = new HashSet<>(); private TreeSet<String> flags = new TreeSet<>();


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


Expand Down Expand Up @@ -162,21 +161,4 @@ public User build()
return new User(name, credential, flags ); 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,9 +20,7 @@
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,9 +71,10 @@ public List<User> deserializeUsers( byte[] bytes ) throws FormatException


private String serialize( User user ) private String serialize( User user )
{ {
return join( userSeparator, user.name(), return String.join( userSeparator,
user.name(),
serialize( user.credentials() ), serialize( user.credentials() ),
join( ",", user.getFlags() ) String.join( ",", user.getFlags() )
); );
} }


Expand Down Expand Up @@ -108,7 +107,7 @@ private String serialize( Credential cred )
{ {
String encodedSalt = HexString.encodeHexString( cred.salt() ); String encodedSalt = HexString.encodeHexString( cred.salt() );
String encodedPassword = HexString.encodeHexString( cred.passwordHash() ); String encodedPassword = HexString.encodeHexString( cred.passwordHash() );
return join( credentialSeparator, Credential.DIGEST_ALGO, encodedPassword, encodedSalt ); return String.join( credentialSeparator, Credential.DIGEST_ALGO, encodedPassword, encodedSalt );
} }


private Credential deserializeCredentials( String part, int lineNumber ) throws FormatException private Credential deserializeCredentials( String part, int lineNumber ) throws FormatException
Expand All @@ -126,22 +125,4 @@ private Credential deserializeCredentials( String part, int lineNumber ) throws
byte[] decodedSalt = HexString.decodeHexString( split[2] ); byte[] decodedSalt = HexString.decodeHexString( split[2] );
return new Credential( decodedSalt, decodedPassword ); return new Credential( decodedSalt, decodedPassword );
} }

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();
boolean afterFirst = false;
while (segments.hasNext())
{
if ( afterFirst ) { sb.append( separator ); }
else { afterFirst = true; }
sb.append( segments.next() );
}
return sb.toString();
}
} }
Expand Up @@ -90,7 +90,7 @@ public void deleteUser( @Name( "username" ) String username ) throws IllegalCred


@PerformsDBMS @PerformsDBMS
@Procedure( "dbms.suspendUser" ) @Procedure( "dbms.suspendUser" )
public void suspendUser( @Name( "username" ) String username ) throws IOException, ConcurrentModificationException public void suspendUser( @Name( "username" ) String username ) throws IOException
{ {
ShiroAuthSubject shiroSubject = ShiroAuthSubject.castOrFail( authSubject ); ShiroAuthSubject shiroSubject = ShiroAuthSubject.castOrFail( authSubject );
if ( !shiroSubject.isAdmin() ) if ( !shiroSubject.isAdmin() )
Expand All @@ -102,7 +102,7 @@ public void suspendUser( @Name( "username" ) String username ) throws IOExceptio


@PerformsDBMS @PerformsDBMS
@Procedure( "dbms.activateUser" ) @Procedure( "dbms.activateUser" )
public void activateUser( @Name( "username" ) String username ) throws IOException, ConcurrentModificationException public void activateUser( @Name( "username" ) String username ) throws IOException
{ {
ShiroAuthSubject shiroSubject = ShiroAuthSubject.castOrFail( authSubject ); ShiroAuthSubject shiroSubject = ShiroAuthSubject.castOrFail( authSubject );
if ( !shiroSubject.isAdmin() ) if ( !shiroSubject.isAdmin() )
Expand Down
Expand Up @@ -57,6 +57,10 @@ public class FileUserRealm extends AuthorizingRealm
private final UserRepository userRepository; private final UserRepository userRepository;
private final RoleRepository roleRepository; private final RoleRepository roleRepository;


/**
* This flag is used in the same way as User.PASSWORD_CHANGE_REQUIRED, but it's
* placed here because of user suspension not being a part of community edition
*/
public static final String IS_SUSPENDED = "is_suspended"; public static final String IS_SUSPENDED = "is_suspended";


private final CredentialsMatcher credentialsMatcher = private final CredentialsMatcher credentialsMatcher =
Expand Down Expand Up @@ -279,23 +283,55 @@ boolean deleteUser( String username ) throws IOException
return result; return result;
} }


void suspendUser( String username ) throws IOException, ConcurrentModificationException void suspendUser( String username ) throws IOException
{ {
User user = userRepository.findByName( username ); synchronized ( this )
if ( user != null && !user.hasFlag( IS_SUSPENDED ) )
{ {
User suspendedUser = user.augment().withFlag( IS_SUSPENDED ).build(); User user = userRepository.findByName( username );
userRepository.update( user, suspendedUser ); if ( user == null )
{
throw new IllegalArgumentException( "User " + username + " does not exist." );
}
if ( !user.hasFlag( IS_SUSPENDED ) )
{
User suspendedUser = user.augment().withFlag( IS_SUSPENDED ).build();
try
{
userRepository.update( user, suspendedUser );
}
catch ( ConcurrentModificationException e )
{
// Due to the synchronized (this) we should never get this exception unless the user repository
// is modified outside this neo instance. In that case we do not know how to proceed.
throw new RuntimeException( "Unexpected failure", e );
}
}
} }
} }


void activateUser( String username ) throws IOException, ConcurrentModificationException void activateUser( String username ) throws IOException
{ {
User user = userRepository.findByName( username ); synchronized ( this )
if ( user != null && user.hasFlag( IS_SUSPENDED ) )
{ {
User activatedUser = user.augment().withoutFlag( IS_SUSPENDED ).build(); User user = userRepository.findByName( username );
userRepository.update( user, activatedUser ); if ( user == null )
{
throw new IllegalArgumentException( "User " + username + " does not exist." );
}
if ( user.hasFlag( IS_SUSPENDED ) )
{
User activatedUser = user.augment().withoutFlag( IS_SUSPENDED ).build();
try
{
userRepository.update( user, activatedUser );
}
catch ( ConcurrentModificationException e )
{
// Due to the synchronized (this) we should never get this exception unless the user repository
// is modified outside this neo instance. In that case we do not know how to proceed.
throw new RuntimeException( "Unexpected failure", e );
}
}
} }
} }


Expand Down
Expand Up @@ -213,13 +213,13 @@ public boolean deleteUser( String username ) throws IOException
return realm.deleteUser( username ); return realm.deleteUser( username );
} }


void suspendUser( String username ) throws IOException, ConcurrentModificationException void suspendUser( String username ) throws IOException
{ {
assertAuthEnabled(); assertAuthEnabled();
realm.suspendUser( username ); realm.suspendUser( username );
} }


void activateUser( String username ) throws IOException, ConcurrentModificationException void activateUser( String username ) throws IOException
{ {
assertAuthEnabled(); assertAuthEnabled();
realm.activateUser( username ); realm.activateUser( username );
Expand Down
Expand Up @@ -569,7 +569,9 @@ public void userSuspension1() throws Exception


/* /*
Admin creates user Henrik with password bar Admin creates user Henrik with password bar
Admin adds user Henrik to role Reader
Henrik logs in with correct password → ok Henrik logs in with correct password → ok
Henrik starts and completes transaction with read query → ok
Admin suspends user Henrik Admin suspends user Henrik
Henrik’s session is terminated Henrik’s session is terminated
Henrik logs in with correct password → fail Henrik logs in with correct password → fail
Expand All @@ -584,7 +586,7 @@ public void userSuspension2() throws Exception
testSuccessfulReadAction( subject, 3L ); testSuccessfulReadAction( subject, 3L );
testCallEmpty( db, adminSubject, "CALL dbms.suspendUser('Henrik')" ); testCallEmpty( db, adminSubject, "CALL dbms.suspendUser('Henrik')" );
testFailReadAction( subject, 3L ); testFailReadAction( subject, 3L );
// TODO: Check that user session is terminated // TODO: Check that user session is terminated instead of checking failed read
subject = manager.login( "Henrik", "bar" ); subject = manager.login( "Henrik", "bar" );
assertEquals( AuthenticationResult.FAILURE, subject.getAuthenticationResult() ); assertEquals( AuthenticationResult.FAILURE, subject.getAuthenticationResult() );
} }
Expand Down

0 comments on commit 5d4f747

Please sign in to comment.