Skip to content

Commit

Permalink
Merge pull request #7421 from craigtaverner/3.1-predefined-roles-ente…
Browse files Browse the repository at this point in the history
…rprise-auth-manager

Fixed predefined roles for enterprise-auth-manager
  • Loading branch information
craigtaverner committed Jun 22, 2016
2 parents 6602c5a + ed24761 commit 3a78f5f
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ private static String defaultPageCacheMemory()
pathSetting( "unsupported.dbms.security.auth_store.location", NO_DEFAULT );

@Internal
public static final Setting<String> auth_manager = setting( "unsupported.dbms.security.auth_manager", STRING, "basic-auth-manager" );
public static final Setting<String> auth_manager = setting( "unsupported.dbms.security.auth_manager", STRING, "" );

// Bolt Settings

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ public void startServer( boolean authEnabled ) throws IOException
server.start();
}

private String challengeResponse( String username, String password )
protected String challengeResponse( String username, String password )
{
return "Basic " + base64( username + ":" + password );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
package org.neo4j.server.security.enterprise.auth;

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

Expand Down Expand Up @@ -133,28 +135,42 @@ public void activateUser( @Name( "username" ) String username ) throws IOExcepti
shiroSubject.getUserManager().activateUser( username );
}

@PerformsDBMS
@Procedure( "dbms.showCurrentUser" )
public Stream<UserResult> showCurrentUser( )
throws IllegalCredentialsException, IOException
{
ShiroAuthSubject shiroSubject = ShiroAuthSubject.castOrFail( authSubject );
RoleManager roleManager = shiroSubject.getRoleManager();
return Stream.of( new UserResult( shiroSubject.name(), roleManager.getRoleNamesForUser( shiroSubject.name() ) ) );
}

@PerformsDBMS
@Procedure( "dbms.listUsers" )
public Stream<StringResult> listUsers() throws IllegalCredentialsException, IOException
public Stream<UserResult> listUsers() throws IllegalCredentialsException, IOException
{
ShiroAuthSubject shiroSubject = ShiroAuthSubject.castOrFail( authSubject );
if ( !shiroSubject.isAdmin() )
{
throw new AuthorizationViolationException( PERMISSION_DENIED );
}
return shiroSubject.getUserManager().getAllUsernames().stream().map( StringResult::new );
RoleManager roleManager = shiroSubject.getRoleManager();
return shiroSubject.getUserManager().getAllUsernames().stream()
.map( u -> new UserResult( u, roleManager.getRoleNamesForUser( u ) ) );
}

@PerformsDBMS
@Procedure( "dbms.listRoles" )
public Stream<StringResult> listRoles() throws IllegalCredentialsException, IOException
public Stream<RoleResult> listRoles() throws IllegalCredentialsException, IOException
{
ShiroAuthSubject shiroSubject = ShiroAuthSubject.castOrFail( authSubject );
if ( !shiroSubject.isAdmin() )
{
throw new AuthorizationViolationException( PERMISSION_DENIED );
}
return shiroSubject.getRoleManager().getAllRoleNames().stream().map( StringResult::new );
RoleManager roleManager = shiroSubject.getRoleManager();
return roleManager.getAllRoleNames().stream()
.map( r -> new RoleResult( r, roleManager.getUsernamesForRole( r ) ) );
}

@PerformsDBMS
Expand Down Expand Up @@ -190,4 +206,26 @@ public StringResult(String value) {
this.value = value;
}
}

public class UserResult {
public final String username;
public final List<String> roles;

public UserResult(String username, Set<String> roles) {
this.username = username;
this.roles = new ArrayList<>();
this.roles.addAll(roles);
}
}

public class RoleResult {
public final String role;
public final List<String> users;

public RoleResult(String role, Set<String> users) {
this.role = role;
this.users = new ArrayList<>();
this.users.addAll(users);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,20 @@ public void start() throws Throwable
users.start();
roleRepository.start();

if ( authEnabled && realm.numberOfUsers() == 0 )
if ( authEnabled )
{
realm.newUser( "neo4j", "neo4j", true );

if ( realm.numberOfRoles() == 0 )
{
for ( String role : new PredefinedRolesBuilder().buildRoles().keySet() )
{
realm.newRole( role );
}
}
if ( realm.numberOfUsers() == 0 )
{
realm.newUser( "neo4j", "neo4j", true );
// Make the default user admin for now
realm.newRole( PredefinedRolesBuilder.ADMIN, "neo4j" );
realm.addUserToRole( "neo4j", PredefinedRolesBuilder.ADMIN );
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.neo4j.graphdb.QueryExecutionException;
import org.neo4j.graphdb.Result;
Expand All @@ -54,6 +55,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.neo4j.helpers.collection.MapUtil.map;
import static org.neo4j.server.security.auth.SecurityTestUtils.authToken;
import static org.neo4j.server.security.enterprise.auth.PredefinedRolesBuilder.ADMIN;
import static org.neo4j.server.security.enterprise.auth.PredefinedRolesBuilder.ARCHITECT;
Expand Down Expand Up @@ -84,11 +86,11 @@ public void setUp() throws Throwable
manager.newUser( "schemaSubject", "abc", false );
manager.newUser( "readWriteSubject", "abc", false );
manager.newUser( "readSubject", "123", false );
// Currently admin role is created by default
// Currently admin, architect, publisher and reader roles are created by default
manager.addUserToRole( "adminSubject", ADMIN );
manager.newRole( ARCHITECT, "schemaSubject" );
manager.newRole( PUBLISHER, "readWriteSubject" );
manager.newRole( READER, "readSubject" );
manager.addUserToRole( "schemaSubject", ARCHITECT );
manager.addUserToRole( "readWriteSubject", PUBLISHER );
manager.addUserToRole( "readSubject", READER );
manager.newRole( "empty" );
noneSubject = manager.login( authToken( "noneSubject", "abc" ) );
readSubject = manager.login( authToken( "readSubject", "123" ) );
Expand Down Expand Up @@ -692,11 +694,44 @@ public void userActivation1() throws Exception
@Test
public void shouldReturnUsers() throws Exception
{
testResult( db, adminSubject, "CALL dbms.listUsers() YIELD value AS users RETURN users",
r -> resultContainsInAnyOrder( r, "users", "adminSubject", "readSubject", "schemaSubject",
testResult( db, adminSubject, "CALL dbms.listUsers() YIELD username",
r -> resultContainsInAnyOrder( r, "username", "adminSubject", "readSubject", "schemaSubject",
"readWriteSubject", "noneSubject", "neo4j" ) );
}

@Test
public void shouldReturnUsersWithRoles() throws Exception
{
Map<String, Object> expected = map(
"adminSubject", listOf( ADMIN ),
"readSubject", listOf( READER ),
"schemaSubject", listOf( ARCHITECT ),
"readWriteSubject", listOf( READER, PUBLISHER ),
"noneSubject", listOf( ),
"neo4j", listOf( ADMIN )
);
manager.addUserToRole( "readWriteSubject", READER );
testResult( db, adminSubject, "CALL dbms.listUsers()",
r -> resultContainsMap( r, "username", "roles", expected ) );
}

@Test
public void shouldShowCurrentUser() throws Exception
{
manager.addUserToRole( "readWriteSubject", READER );
testResult( db, adminSubject, "CALL dbms.showCurrentUser()",
r -> resultContainsMap( r, "username", "roles", map( "adminSubject", listOf( ADMIN ) ) ) );
testResult( db, readSubject, "CALL dbms.showCurrentUser()",
r -> resultContainsMap( r, "username", "roles", map( "readSubject", listOf( READER ) ) ) );
testResult( db, schemaSubject, "CALL dbms.showCurrentUser()",
r -> resultContainsMap( r, "username", "roles", map( "schemaSubject", listOf( ARCHITECT ) ) ) );
testResult( db, writeSubject, "CALL dbms.showCurrentUser()",
r -> resultContainsMap( r, "username", "roles",
map( "readWriteSubject", listOf( READER, PUBLISHER ) ) ) );
testResult( db, noneSubject, "CALL dbms.showCurrentUser()",
r -> resultContainsMap( r, "username", "roles", map( "noneSubject", listOf() ) ) );
}

@Test
public void shouldNotAllowNonAdminListUsers() throws Exception
{
Expand All @@ -718,29 +753,43 @@ public void shouldNotAllowNonAdminListUsers() throws Exception
@Test
public void userListing() throws Exception
{
testResult( db, adminSubject, "CALL dbms.listUsers() YIELD value AS users RETURN users",
testResult( db, adminSubject, "CALL dbms.listUsers() YIELD username AS users RETURN users",
r -> resultContainsInAnyOrder( r, "users", "adminSubject", "readSubject", "schemaSubject",
"readWriteSubject", "noneSubject", "neo4j" ) );
testCallEmpty( db, adminSubject, "CALL dbms.createUser('Henrik', 'bar', false)" );
testResult( db, adminSubject, "CALL dbms.listUsers() YIELD value AS users RETURN users",
testResult( db, adminSubject, "CALL dbms.listUsers() YIELD username AS users RETURN users",
r -> resultContainsInAnyOrder( r, "users", "adminSubject", "readSubject", "schemaSubject",
"readWriteSubject", "noneSubject", "Henrik", "neo4j" ) );
AuthSubject subject = manager.login( authToken( "Henrik", "bar" ) );
assertEquals( AuthenticationResult.SUCCESS, subject.getAuthenticationResult() );
testFailListUsers( subject, 6 );
testCallEmpty( db, adminSubject, "CALL dbms.addUserToRole('Henrik', '" + ADMIN + "')" );
testResult( db, subject, "CALL dbms.listUsers() YIELD value AS users RETURN users",
testResult( db, subject, "CALL dbms.listUsers() YIELD username AS users RETURN users",
r -> resultContainsInAnyOrder( r, "users", "adminSubject", "readSubject", "schemaSubject",
"readWriteSubject", "noneSubject", "Henrik", "neo4j" ) );
}

@Test
public void shouldReturnRoles() throws Exception
{
testResult( db, adminSubject, "CALL dbms.listRoles() YIELD value AS roles RETURN roles",
testResult( db, adminSubject, "CALL dbms.listRoles() YIELD role AS roles RETURN roles",
r -> resultContainsInAnyOrder( r, "roles", ADMIN, ARCHITECT, PUBLISHER, READER, "empty" ) );
}

@Test
public void shouldReturnRolesWithUsers() throws Exception
{
Map<String,Object> expected = map(
ADMIN, listOf( "adminSubject", "neo4j" ),
READER, listOf( "readSubject" ),
ARCHITECT, listOf( "schemaSubject" ),
PUBLISHER, listOf( "readWriteSubject" ),
"empty", listOf()
);
testResult( db, adminSubject, "CALL dbms.listRoles()",
r -> resultContainsMap( r, "role", "users", expected ) );
}

@Test
public void shouldNotAllowNonAdminListRoles() throws Exception
{
Expand Down Expand Up @@ -938,7 +987,7 @@ private void testFailDeleteUser( AuthSubject subject )

private void testSuccessfulListUsersAction( AuthSubject subject, int count )
{
testCallCount( db, subject, "CALL dbms.listUsers() YIELD value AS users RETURN users", null, count );
testCallCount( db, subject, "CALL dbms.listUsers() YIELD username AS users RETURN users", null, count );
}

private void testFailListUsers( AuthSubject subject, int count )
Expand All @@ -957,7 +1006,7 @@ private void testFailListUsers( AuthSubject subject, int count )

private void testSuccessfulListRolesAction( AuthSubject subject )
{
testCallCount( db, subject, "CALL dbms.listRoles() YIELD value AS roles RETURN roles", null, 5 );
testCallCount( db, subject, "CALL dbms.listRoles() YIELD role AS roles RETURN roles", null, 5 );
}

private void testFailListRoles( AuthSubject subject )
Expand Down Expand Up @@ -1006,6 +1055,11 @@ private void testFailListRoleUsers( AuthSubject subject, String roleName )
}
}

private List<String> listOf( String... values )
{
return Stream.of( values ).collect( Collectors.toList() );
}

private List<Object> getObjectsAsList( Result r, String key )
{
return r.stream().map( s -> s.get( key ) ).collect( Collectors.toList() );
Expand All @@ -1018,6 +1072,20 @@ private void resultContainsInAnyOrder( Result r, String key, Object... items )
assertEquals( Arrays.asList( items ).size(), results.size() );
}

private void resultContainsMap( Result r, String keyKey, String valueKey, Map<String,Object> expected )
{
r.stream().forEach( s -> {
String key = (String) s.get( keyKey );
List<String> value = (List<String>) s.get( valueKey );
assertTrue( "Expected to find values for '" + key + "'", expected.containsKey( key ) );
List<String> expectedValues = (List<String>) expected.get( key );
assertEquals(
"Results for '" + key + "' should have size " + expectedValues.size() + " but was " + value.size(),
value.size(), expectedValues.size() );
assertThat( value, containsInAnyOrder( expectedValues.toArray() ) );
} );
}

private static void testCall( GraphDatabaseAPI db, AuthSubject subject, String call,
Consumer<Map<String,Object>> consumer )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,22 @@
*/
package org.neo4j.server.rest.security;

import org.codehaus.jackson.node.ArrayNode;
import org.junit.Test;

import java.io.IOException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.ws.rs.core.HttpHeaders;

import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.server.enterprise.helpers.EnterpriseServerBuilder;
import org.neo4j.test.server.HTTP;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.junit.Assert.assertThat;

public class EnterpriseAuthenticationDocIT extends AuthenticationDocIT
{
Expand All @@ -35,4 +47,31 @@ public void startServer( boolean authEnabled ) throws IOException
.build();
server.start();
}

@Test
public void shouldHavePredefinedRoles() throws Exception
{
// Given
startServerWithConfiguredUser();

// When
String method = "POST";
String path = "db/data/transaction/commit";
HTTP.RawPayload payload = HTTP.RawPayload.quotedJson(
"{'statements':[{'statement':'CALL dbms.listRoles()'}]}" );
HTTP.Response response = HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) )
.request( method, server.baseUri().resolve( path ).toString(), payload );

// Then
assertThat(response.status(), equalTo(200));
ArrayNode errors = (ArrayNode) response.get("errors");
assertThat( "Should have no errors", errors.size(), equalTo( 0 ) );
ArrayNode results = (ArrayNode) response.get("results");
ArrayNode data = (ArrayNode) results.get(0).get("data");
assertThat( "Should have 4 predefined roles", data.size(), equalTo( 4 ) );
Stream<String> values = data.findValues( "row" ).stream().map( row -> row.get(0).asText() );
assertThat( "Expected specific roles", values.collect( Collectors.toList()),
hasItems( "admin", "architect", "publisher", "reader") );

}
}

0 comments on commit 3a78f5f

Please sign in to comment.