diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltStateMachine.java b/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltStateMachine.java index 92c8df09e18f..b6a8b0a1c084 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltStateMachine.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltStateMachine.java @@ -31,6 +31,7 @@ import org.neo4j.bolt.v1.runtime.cypher.StatementProcessor; import org.neo4j.bolt.v1.runtime.spi.BoltResult; import org.neo4j.function.ThrowingConsumer; +import org.neo4j.graphdb.security.AuthProviderFailedException; import org.neo4j.graphdb.security.AuthorizationExpiredException; import org.neo4j.graphdb.security.AuthProviderTimeoutException; import org.neo4j.kernel.api.bolt.ManagedBoltStateMachine; @@ -364,7 +365,7 @@ public State init( BoltStateMachine machine, String userAgent, } return READY; } - catch ( AuthenticationException | AuthProviderTimeoutException e ) + catch ( AuthenticationException | AuthProviderTimeoutException | AuthProviderFailedException e) { fail( machine, Neo4jError.fatalFrom( e.status(), e.getMessage() ) ); throw new BoltConnectionAuthFatality( e.getMessage() ); 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 a1316d820237..21ea7ea58267 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 @@ -27,14 +27,14 @@ import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.cache.Cache; import org.apache.shiro.crypto.hash.SimpleHash; +import org.apache.shiro.realm.ldap.DefaultLdapRealm; import org.apache.shiro.realm.ldap.JndiLdapContextFactory; -import org.apache.shiro.realm.ldap.JndiLdapRealm; import org.apache.shiro.realm.ldap.LdapContextFactory; import org.apache.shiro.realm.ldap.LdapUtils; import org.apache.shiro.subject.PrincipalCollection; -import org.apache.shiro.util.CollectionUtils; import java.io.IOException; +import java.net.ConnectException; import java.net.SocketTimeoutException; import java.util.ArrayList; import java.util.Collection; @@ -59,9 +59,9 @@ import javax.naming.ldap.StartTlsRequest; import javax.naming.ldap.StartTlsResponse; -import org.neo4j.graphdb.security.AuthorizationExpiredException; import org.neo4j.graphdb.security.AuthProviderFailedException; import org.neo4j.graphdb.security.AuthProviderTimeoutException; +import org.neo4j.graphdb.security.AuthorizationExpiredException; import org.neo4j.kernel.api.security.AuthToken; import org.neo4j.kernel.api.security.AuthenticationResult; import org.neo4j.kernel.api.security.exception.InvalidAuthTokenException; @@ -74,7 +74,7 @@ /** * Shiro realm for LDAP based on configuration settings */ -public class LdapRealm extends JndiLdapRealm implements RealmLifecycle, ShiroAuthorizationInfoProvider +public class LdapRealm extends DefaultLdapRealm implements RealmLifecycle, ShiroAuthorizationInfoProvider { private static final String GROUP_DELIMITER = ";"; private static final String KEY_VALUE_DELIMITER = "="; @@ -89,6 +89,7 @@ public class LdapRealm extends JndiLdapRealm implements RealmLifecycle, ShiroAut public static final String LDAP_CONNECTION_TIMEOUT_CLIENT_MESSAGE = "LDAP connection timed out."; public static final String LDAP_READ_TIMEOUT_CLIENT_MESSAGE = "LDAP response timed out."; public static final String LDAP_AUTHORIZATION_FAILURE_CLIENT_MESSAGE = "LDAP authorization request failed."; + public static final String LDAP_CONNECTION_REFUSED_CLIENT_MESSAGE = "LDAP connection refused."; private Boolean authenticationEnabled; private Boolean authorizationEnabled; @@ -167,6 +168,11 @@ else if ( isExceptionAnLdapReadTimeout( e ) ) securityLog.error( withRealm( "LDAP response from %s timed out.", serverString ) ); throw new AuthProviderTimeoutException( LDAP_READ_TIMEOUT_CLIENT_MESSAGE, e ); } + else if ( isExceptionConnectionRefused( e ) ) + { + securityLog.error( withRealm( "LDAP connection to %s was refused.", serverString ) ); + throw new AuthProviderFailedException( LDAP_CONNECTION_REFUSED_CLIENT_MESSAGE, e ); + } // This exception will be caught and rethrown by Shiro, and then by us, so we do not need to wrap it here throw e; } @@ -400,6 +406,12 @@ private boolean isExceptionAnLdapConnectionTimeout( Exception e ) JNDI_LDAP_CONNECTION_TIMEOUT_MESSAGE_PART ) ); } + private boolean isExceptionConnectionRefused( Exception e ) + { + return e instanceof CommunicationException && + ((CommunicationException) e).getRootCause() instanceof ConnectException; + } + private boolean isAuthorizationExceptionAnLdapReadTimeout( AuthorizationException e ) { // Shiro's doGetAuthorizationInfo() wraps a NamingException in an AuthorizationException diff --git a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManager.java b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManager.java index 56349770cef1..2152298b17ae 100644 --- a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManager.java +++ b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/MultiRealmAuthManager.java @@ -43,6 +43,7 @@ import java.util.List; import java.util.Map; +import org.neo4j.graphdb.security.AuthProviderFailedException; import org.neo4j.graphdb.security.AuthProviderTimeoutException; import org.neo4j.kernel.api.security.AuthenticationResult; import org.neo4j.kernel.api.security.SecurityContext; @@ -131,6 +132,12 @@ public EnterpriseSecurityContext login( Map authToken ) throws In escape( token.getPrincipal().toString() ) ); throw new AuthProviderTimeoutException( e.getCause().getMessage(), e.getCause() ); } + else if ( e.getCause() != null && e.getCause() instanceof AuthProviderFailedException ) + { + securityLog.error( "[%s]: failed to log in: auth server connection refused", + escape( token.getPrincipal().toString() ) ); + throw new AuthProviderFailedException( e.getCause().getMessage(), e.getCause() ); + } securityContext = new StandardEnterpriseSecurityContext( this, new ShiroSubject( securityManager, AuthenticationResult.FAILURE ) ); securityLog.error( "[%s]: failed to log in: invalid principal or credentials", diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/integration/bolt/LdapAuthIT.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/integration/bolt/LdapAuthIT.java index b51f45c58b6e..c42b2e7b0564 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/integration/bolt/LdapAuthIT.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/integration/bolt/LdapAuthIT.java @@ -78,7 +78,7 @@ import static org.neo4j.bolt.v1.transport.integration.TransportTestUtil.eventuallyDisconnects; import static org.neo4j.bolt.v1.transport.integration.TransportTestUtil.eventuallyReceives; import static org.neo4j.server.security.enterprise.auth.LdapRealm.LDAP_AUTHORIZATION_FAILURE_CLIENT_MESSAGE; -import static org.neo4j.server.security.enterprise.auth.LdapRealm.LDAP_CONNECTION_TIMEOUT_CLIENT_MESSAGE; +import static org.neo4j.server.security.enterprise.auth.LdapRealm.LDAP_CONNECTION_REFUSED_CLIENT_MESSAGE; import static org.neo4j.server.security.enterprise.auth.LdapRealm.LDAP_READ_TIMEOUT_CLIENT_MESSAGE; interface TimeoutTests { /* Category marker */ }; @@ -564,16 +564,14 @@ public void shouldBeAbleToUseProcedureAllowedAnnotationWithLdapGroupToRoleMappin } @Test - public void shouldTimeoutIfInvalidLdapServer() throws Throwable + public void shouldFailIfInvalidLdapServer() throws Throwable { // When - restartNeo4jServerWithOverriddenSettings( ldapOnlyAuthSettings.andThen( settings -> { - settings.put( SecuritySettings.ldap_server, "ldap://198.51.100.9" ); // An IP in the TEST-NET-2 range - settings.put( SecuritySettings.ldap_connection_timeout, "1s" ); - } ) ); + restartNeo4jServerWithOverriddenSettings( ldapOnlyAuthSettings.andThen( + settings -> settings.put( SecuritySettings.ldap_server, "ldap://127.0.0.1" ) ) ); - assertConnectionTimeout( authToken( "neo", "abc123", null ), - LDAP_CONNECTION_TIMEOUT_CLIENT_MESSAGE ); + assertConnectionRefused( authToken( "neo", "abc123", null ), + LDAP_CONNECTION_REFUSED_CLIENT_MESSAGE ); assertThat( client, eventuallyDisconnects() ); } @@ -1041,6 +1039,19 @@ private void assertConnectionTimeout( Map authToken, String messa assertThat( client, eventuallyDisconnects() ); } + private void assertConnectionRefused( Map authToken, String message ) throws Exception + { + client.connect( address ) + .send( TransportTestUtil.acceptedVersions( 1, 0, 0, 0 ) ) + .send( TransportTestUtil.chunk( + init( "TestClient/1.1", authToken ) ) ); + + assertThat( client, eventuallyReceives( new byte[]{0, 0, 0, 1} ) ); + assertThat( client, eventuallyReceives( msgFailure( Status.Security.AuthProviderFailed, message ) ) ); + + assertThat( client, eventuallyDisconnects() ); + } + private void testClearAuthCache() throws Exception { assertAuth( "neo4j", "abc123" );