diff --git a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/EnterpriseAuthManagerFactory.java b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/EnterpriseAuthManagerFactory.java index b822d51df8fd6..a771bdbc5ea05 100644 --- a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/EnterpriseAuthManagerFactory.java +++ b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/EnterpriseAuthManagerFactory.java @@ -57,19 +57,26 @@ public EnterpriseAuthManagerFactory() @Override public AuthManager newInstance( Config config, LogProvider logProvider ) { - InternalFlatFileRealm internalRealm = createInternalRealm( config, logProvider ); - List realms = new ArrayList<>( 2 ); - realms.add( internalRealm ); + // We always create the internal realm as it is our only UserManager implementation + InternalFlatFileRealm internalRealm = createInternalRealm( config, logProvider ); + + if ( config.get( SecuritySettings.internal_authentication_enabled ) || + config.get( SecuritySettings.internal_authorization_enabled ) ) + { + realms.add( internalRealm ); + } - if ( config.get( SecuritySettings.external_auth_enabled ) ) + if ( config.get( SecuritySettings.ldap_authentication_enabled ) || + config.get( SecuritySettings.ldap_authorization_enabled ) ) { - if ( config.get( SecuritySettings.ldap_auth_enabled ) ) - { - realms.add( new LdapRealm( config ) ); - } + realms.add( new LdapRealm( config ) ); + } + if ( config.get( SecuritySettings.plugin_authentication_enabled ) || + config.get( SecuritySettings.plugin_authorization_enabled ) ) + { // TODO: Load pluggable realms } @@ -101,6 +108,7 @@ private InternalFlatFileRealm createInternalRealm( Config config, LogProvider lo AuthenticationStrategy authenticationStrategy = new RateLimitedAuthenticationStrategy( systemUTC(), 3 ); return new InternalFlatFileRealm( userRepository, roleRepository, passwordPolicy, authenticationStrategy, - true ); + config.get( SecuritySettings.internal_authentication_enabled ), + config.get( SecuritySettings.internal_authorization_enabled ) ); } } diff --git a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealm.java b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealm.java index 6d9f0cb428f78..a029cc948a057 100644 --- a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealm.java +++ b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealm.java @@ -92,11 +92,12 @@ public Collection resolvePermissionsInRole( String roleString ) private final PasswordPolicy passwordPolicy; private final AuthenticationStrategy authenticationStrategy; private final boolean authenticationEnabled; + private final boolean authorizationEnabled; private final Map roles; public InternalFlatFileRealm( UserRepository userRepository, RoleRepository roleRepository, PasswordPolicy passwordPolicy, AuthenticationStrategy authenticationStrategy, - boolean authenticationEnabled ) + boolean authenticationEnabled, boolean authorizationEnabled ) { super(); @@ -105,12 +106,19 @@ public InternalFlatFileRealm( UserRepository userRepository, RoleRepository role this.passwordPolicy = passwordPolicy; this.authenticationStrategy = authenticationStrategy; this.authenticationEnabled = authenticationEnabled; + this.authorizationEnabled = authenticationEnabled; setCredentialsMatcher( new AllowAllCredentialsMatcher() ); setRolePermissionResolver( rolePermissionResolver ); roles = new PredefinedRolesBuilder().buildRoles(); } + public InternalFlatFileRealm( UserRepository userRepository, RoleRepository roleRepository, + PasswordPolicy passwordPolicy, AuthenticationStrategy authenticationStrategy ) + { + this( userRepository, roleRepository, passwordPolicy, authenticationStrategy, true, true ); + } + @Override public void initialize() throws Throwable { @@ -124,7 +132,12 @@ public void start() throws Throwable userRepository.start(); roleRepository.start(); - if ( authenticationEnabled ) + ensureDefaultUsersAndRoles(); + } + + private void ensureDefaultUsersAndRoles() throws IOException, IllegalCredentialsException + { + if ( authenticationEnabled || authorizationEnabled ) { if ( numberOfRoles() == 0 ) { @@ -177,6 +190,11 @@ public boolean supports( AuthenticationToken token ) @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals ) throws AuthenticationException { + if ( !authorizationEnabled ) + { + return null; + } + String username = (String) getAvailablePrincipal( principals ); if ( username == null ) { @@ -203,6 +221,11 @@ protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principa @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token ) throws AuthenticationException { + if ( !authenticationEnabled ) + { + return null; + } + ShiroAuthToken shiroAuthToken = (ShiroAuthToken) token; String username; diff --git a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/LdapRealm.java b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/LdapRealm.java index f5741e856febd..c82f45342cb5e 100644 --- a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/LdapRealm.java +++ b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/LdapRealm.java @@ -23,7 +23,6 @@ import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.Permission; -import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.authz.SimpleRole; import org.apache.shiro.authz.permission.RolePermissionResolver; import org.apache.shiro.realm.ldap.JndiLdapContextFactory; @@ -44,6 +43,9 @@ */ public class LdapRealm extends JndiLdapRealm { + private boolean authenticationEnabled; + private boolean authorizationEnabled; + public LdapRealm( Config config ) { super(); @@ -51,12 +53,23 @@ public LdapRealm( Config config ) configureRealm( config ); } + @Override + protected AuthenticationInfo queryForAuthenticationInfo( AuthenticationToken token, + LdapContextFactory ldapContextFactory ) + throws NamingException + { + return authenticationEnabled ? super.queryForAuthenticationInfo( token, ldapContextFactory ) : null; + } + @Override protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals, LdapContextFactory ldapContextFactory) throws NamingException { - // TODO: This is just temporary - return new SimpleAuthorizationInfo( Collections.singleton( PredefinedRolesBuilder.READER ) ); + if ( authorizationEnabled ) + { + // TODO: Implement LDAP authorization + } + return null; } @Override @@ -96,5 +109,8 @@ private void configureRealm( Config config ) setContextFactory( contextFactory ); setUserDnTemplate( config.get( SecuritySettings.ldap_user_dn_template ) ); + + authenticationEnabled = config.get( SecuritySettings.ldap_authentication_enabled ); + authorizationEnabled = config.get( SecuritySettings.ldap_authorization_enabled ); } } diff --git a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/SecuritySettings.java b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/SecuritySettings.java index 313fce3d2a6f9..a017c2545ef95 100644 --- a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/SecuritySettings.java +++ b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/SecuritySettings.java @@ -37,35 +37,51 @@ public class SecuritySettings { @SuppressWarnings( "unused" ) // accessed by reflection - @Description( "Enable auth via external authentication providers." ) - public static final Setting external_auth_enabled = - setting( "dbms.security.external_auth_enabled", BOOLEAN, "false" ); + @Description( "Enable authentication via internal authentication provider." ) + public static final Setting internal_authentication_enabled = + setting( "dbms.security.realms.internal.authentication_enabled", BOOLEAN, "true" ); - @Description( "Enable auth via a settings configurable LDAP authentication realm." ) - public static final Setting ldap_auth_enabled = - setting( "dbms.security.ldap.enabled", BOOLEAN, "false" ); + @Description( "Enable authorization via internal authorization provider." ) + public static final Setting internal_authorization_enabled = + setting( "dbms.security.realms.internal.authorization_enabled", BOOLEAN, "true" ); + + @Description( "Enable authentication via settings configurable LDAP authentication realm." ) + public static final Setting ldap_authentication_enabled = + setting( "dbms.security.realms.ldap.authentication_enabled", BOOLEAN, "false" ); + + @Description( "Enable authotization via settings configurable LDAP authorization realm." ) + public static final Setting ldap_authorization_enabled = + setting( "dbms.security.realms.ldap.authorization_enabled", BOOLEAN, "false" ); + + @Description( "Enable authentication via plugin authentication realms." ) + public static final Setting plugin_authentication_enabled = + setting( "dbms.security.realms.plugin.authentication_enabled", BOOLEAN, "false" ); + + @Description( "Enable authotization via plugin authorization realms." ) + public static final Setting plugin_authorization_enabled = + setting( "dbms.security.realms.plugin.authorization_enabled", BOOLEAN, "false" ); @Description( "Hostname and port of LDAP server to use for authentication and authorization." ) public static final Setting ldap_server = - setting( "dbms.security.ldap.host", HOSTNAME_PORT, "0.0.0.0:389" ); + setting( "dbms.security.realms.ldap.host", HOSTNAME_PORT, "0.0.0.0:389" ); @Description( "Authentication mechanism." ) public static final Setting ldap_auth_mechanism = - setting( "dbms.security.ldap.auth_mechanism", STRING, "simple" ); + setting( "dbms.security.realms.ldap.auth_mechanism", STRING, "simple" ); @Description( "Referral" ) public static final Setting ldap_referral = - setting( "dbms.security.ldap.referral", STRING, "follow" ); + setting( "dbms.security.realms.ldap.referral", STRING, "follow" ); @Description( "User DN template." ) public static final Setting ldap_user_dn_template = - setting( "dbms.security.ldap.user_dn_template", STRING, "uid={0},ou=users,dc=example,dc=com" ); + setting( "dbms.security.realms.ldap.user_dn_template", STRING, "uid={0},ou=users,dc=example,dc=com" ); @Description( "System username" ) public static final Setting ldap_system_username = - setting( "dbms.security.ldap.system_username", STRING, NO_DEFAULT ); + setting( "dbms.security.realms.ldap.system_username", STRING, NO_DEFAULT ); @Description( "System password" ) public static final Setting ldap_system_password = - setting( "dbms.security.ldap.system_password", STRING, NO_DEFAULT ); + setting( "dbms.security.realms.ldap.system_password", STRING, NO_DEFAULT ); } diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/AuthProcedureTestBase.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/AuthProcedureTestBase.java index 804e4c9309c6a..25d1e66f24a12 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/AuthProcedureTestBase.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/AuthProcedureTestBase.java @@ -80,7 +80,7 @@ public void setUp() throws Throwable { db = (GraphDatabaseAPI) new TestEnterpriseGraphDatabaseFactory().newImpermanentDatabase(); internalRealm = new InternalFlatFileRealm( new InMemoryUserRepository(), new InMemoryRoleRepository(), - new BasicPasswordPolicy(), new RateLimitedAuthenticationStrategy( systemUTC(), 3 ), true ); + new BasicPasswordPolicy(), new RateLimitedAuthenticationStrategy( systemUTC(), 3 ) ); manager = new MultiRealmAuthManager( internalRealm, Collections.singletonList( internalRealm ) ); manager.init(); manager.start(); diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/AuthScenariosIT.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/AuthScenariosIT.java index bf8c90f3405b7..f8b1f3fde6576 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/AuthScenariosIT.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/AuthScenariosIT.java @@ -45,7 +45,6 @@ */ public class AuthScenariosIT extends AuthProcedureTestBase { - //---------- User creation ----------- /* diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealmTest.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealmTest.java index 383f78ca7e181..4f88f1e76921d 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealmTest.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/InternalFlatFileRealmTest.java @@ -120,7 +120,7 @@ public void addUserToRoleShouldBeAtomic() throws Exception CodePosition codePosition = getCodePositionAfterCall( "addUserToRole", "getUserByName" ); InternalFlatFileRealm realm = new InternalFlatFileRealm( userRepository, roleRepository, passwordPolicy, - authenticationStrategy, true ); + authenticationStrategy ); // When RunResult result = InterleavedRunner.interleave( @@ -143,7 +143,7 @@ public void deleteUserShouldBeAtomic() throws Exception CodePosition codePosition = getCodePositionAfterCall( "deleteUser", "getUserByName" ); InternalFlatFileRealm realm = new InternalFlatFileRealm( userRepository, roleRepository, passwordPolicy, - authenticationStrategy, true ); + authenticationStrategy ); // When RunResult result = InterleavedRunner.interleave( diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerTest.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerTest.java index 8c588c62ffa82..1d343d00723dc 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerTest.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManagerTest.java @@ -64,7 +64,7 @@ public void setUp() throws Throwable authStrategy = mock( AuthenticationStrategy.class ); passwordPolicy = mock( PasswordPolicy.class ); - internalFlatFileRealm = new InternalFlatFileRealm( users, roles, passwordPolicy, authStrategy, true ); + internalFlatFileRealm = new InternalFlatFileRealm( users, roles, passwordPolicy, authStrategy ); manager = new MultiRealmAuthManager( internalFlatFileRealm, Collections.singleton( internalFlatFileRealm )); manager.init(); userManager = manager.getUserManager(); diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/integration/bolt/EnterpriseAuthenticationIT.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/integration/bolt/EnterpriseAuthenticationIT.java index bab10bf301af3..b5685c564d28b 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/integration/bolt/EnterpriseAuthenticationIT.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/integration/bolt/EnterpriseAuthenticationIT.java @@ -43,7 +43,6 @@ protected Consumer, String>> getSettingsFunction() return settings -> { settings.put( GraphDatabaseSettings.auth_enabled, "true" ); settings.put( GraphDatabaseSettings.auth_manager, "enterprise-auth-manager" ); - settings.put( SecuritySettings.external_auth_enabled, "false" ); }; } } diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/integration/bolt/LdapAuthenticationIT.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/integration/bolt/LdapAuthenticationIT.java index 74784c05ee269..de0e21e4c528e 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/integration/bolt/LdapAuthenticationIT.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/integration/bolt/LdapAuthenticationIT.java @@ -34,6 +34,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Collections; import java.util.Map; import java.util.function.Consumer; @@ -45,6 +46,7 @@ import org.neo4j.graphdb.config.Setting; import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.helpers.HostnamePort; +import org.neo4j.kernel.api.exceptions.Status; import org.neo4j.server.security.enterprise.auth.SecuritySettings; import org.neo4j.test.TestEnterpriseGraphDatabaseFactory; import org.neo4j.test.TestGraphDatabaseFactory; @@ -53,6 +55,7 @@ import static org.neo4j.bolt.v1.messaging.message.Messages.init; import static org.neo4j.bolt.v1.messaging.message.Messages.pullAll; import static org.neo4j.bolt.v1.messaging.message.Messages.run; +import static org.neo4j.bolt.v1.messaging.util.MessageMatchers.msgFailure; import static org.neo4j.bolt.v1.messaging.util.MessageMatchers.msgSuccess; import static org.neo4j.bolt.v1.transport.integration.TransportTestUtil.eventuallyRecieves; import static org.neo4j.helpers.collection.MapUtil.map; @@ -87,13 +90,14 @@ protected Consumer, String>> getSettingsFunction() return settings -> { settings.put( GraphDatabaseSettings.auth_enabled, "true" ); settings.put( GraphDatabaseSettings.auth_manager, "enterprise-auth-manager" ); - settings.put( SecuritySettings.external_auth_enabled, "true" ); - settings.put( SecuritySettings.ldap_auth_enabled, "true" ); - // TODO: This is the configuration for an ldap test server + settings.put( SecuritySettings.internal_authentication_enabled, "true" ); + settings.put( SecuritySettings.internal_authorization_enabled, "true" ); + settings.put( SecuritySettings.ldap_authentication_enabled, "true" ); + settings.put( SecuritySettings.ldap_authorization_enabled, "false" ); settings.put( SecuritySettings.ldap_server, "0.0.0.0:10389" ); settings.put( SecuritySettings.ldap_user_dn_template, "cn={0},ou=users,dc=example,dc=com" ); - //settings.put( SecuritySettings.ldap_system_username, "uid=admin,ou=system" ); - //settings.put( SecuritySettings.ldap_system_password, "secret" ); + settings.put( SecuritySettings.ldap_system_username, "uid=admin,ou=system" ); + settings.put( SecuritySettings.ldap_system_password, "secret" ); }; } @@ -117,14 +121,73 @@ public void shouldBeAbleToLoginWithLdap() throws Throwable // Then assertThat( client, eventuallyRecieves( new byte[]{0, 0, 0, 1} ) ); assertThat( client, eventuallyRecieves( msgSuccess() ) ); + } + + @Test + @ApplyLdifFiles( "ldap_test_data.ldif" ) + public void shouldBeAbleToLoginWithLdapAndAuthorizeInternally() throws Throwable + { + //-------------------------- + // First login as admin and create the internal user 'neo' with role 'reader' + + client.connect( address ) + .send( TransportTestUtil.acceptedVersions( 1, 0, 0, 0 ) ) + .send( TransportTestUtil.chunk( + init( "TestClient/1.1", map( "principal", "neo4j", + "credentials", "neo4j", "scheme", "basic" ) ) ) ); + + assertThat( client, eventuallyRecieves( new byte[]{0, 0, 0, 1} ) ); + assertThat( client, eventuallyRecieves( msgSuccess( Collections.singletonMap( "credentials_expired", true )) ) ); - // When client.send( TransportTestUtil.chunk( - run( "MATCH (n) RETURN n" ), + run( "CALL dbms.changePassword", Collections.singletonMap( "password", "secret" ) ), + pullAll() ) ); + + assertThat( client, eventuallyRecieves( msgSuccess() ) ); + + reconnect(); + + client.connect( address ) + .send( TransportTestUtil.acceptedVersions( 1, 0, 0, 0 ) ) + .send( TransportTestUtil.chunk( + init( "TestClient/1.1", map( "principal", "neo4j", + "credentials", "secret", "scheme", "basic" ) ) ) ); + + assertThat( client, eventuallyRecieves( new byte[]{0, 0, 0, 1} ) ); + assertThat( client, eventuallyRecieves( msgSuccess() ) ); + + client.send( TransportTestUtil.chunk( + run( "CALL dbms.createUser( 'neo', 'invalid', false ) CALL dbms.addUserToRole( 'neo', 'reader' ) RETURN 0" ), pullAll() ) ); - // Then assertThat( client, eventuallyRecieves( msgSuccess() ) ); + + //-------------------------- + // Then login user 'neo' with LDAP and test that internal authorization gives correct permission + reconnect(); + + client.connect( address ) + .send( TransportTestUtil.acceptedVersions( 1, 0, 0, 0 ) ) + .send( TransportTestUtil.chunk( + init( "TestClient/1.1", map( "principal", "neo", + "credentials", "abc123", "scheme", "basic" ) ) ) ); + + assertThat( client, eventuallyRecieves( new byte[]{0, 0, 0, 1} ) ); + assertThat( client, eventuallyRecieves( msgSuccess() ) ); + + client.send( TransportTestUtil.chunk( + run( "MATCH (n) RETURN n" ), + pullAll() ) ); + + assertThat( client, eventuallyRecieves( msgSuccess(), msgSuccess() ) ); + + client.send( TransportTestUtil.chunk( + run( "CREATE ()" ), + pullAll() ) ); + + assertThat( client, eventuallyRecieves( + msgFailure( Status.Security.Forbidden, + String.format( "Write operations are not allowed for `neo` transactions." ) ) ) ); } @Before diff --git a/enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/security/EnterpriseAuthenticationDocIT.java b/enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/security/EnterpriseAuthenticationDocIT.java index bccda823832ab..1cd4f7578e561 100644 --- a/enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/security/EnterpriseAuthenticationDocIT.java +++ b/enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/security/EnterpriseAuthenticationDocIT.java @@ -44,7 +44,6 @@ public void startServer( boolean authEnabled ) throws IOException server = EnterpriseServerBuilder.server() .withProperty( GraphDatabaseSettings.auth_enabled.name(), Boolean.toString( authEnabled ) ) .withProperty( GraphDatabaseSettings.auth_manager.name(), "enterprise-auth-manager" ) - .withProperty( SecuritySettings.ldap_auth_enabled.name(), "false" ) .build(); server.start(); }