diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/factory/EditionModule.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/factory/EditionModule.java index 8cfab2ad47ab9..91cd6d9e97bbb 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/factory/EditionModule.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/factory/EditionModule.java @@ -177,7 +177,7 @@ public DependencySatisfier dependencySatisfier() } ); return; } - catch ( KernelException e ) + catch ( Exception e ) { String errorMessage = "Failed to load security module."; log.error( errorMessage ); diff --git a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/EnterpriseSecurityModule.java b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/EnterpriseSecurityModule.java index c0c899b0564b8..b9df934446d2e 100644 --- a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/EnterpriseSecurityModule.java +++ b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/EnterpriseSecurityModule.java @@ -28,6 +28,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.neo4j.commandline.admin.security.SetDefaultAdminCommand; import org.neo4j.dbms.DatabaseManagementSystemSettings; @@ -58,6 +59,7 @@ import org.neo4j.server.security.enterprise.log.SecurityLog; import org.neo4j.time.Clocks; +import static java.lang.String.format; import static org.neo4j.kernel.api.proc.Context.SECURITY_CONTEXT; @Service.Implementation( SecurityModule.class ) @@ -147,16 +149,18 @@ public EnterpriseAuthAndUserManager newAuthManager( Config config, LogProvider l realms.add( new LdapRealm( config, securityLog, secureHasher ) ); } - // Load plugin realms if we have any - realms.addAll( createPluginRealms( config, securityLog, secureHasher ) ); + // Load plugin realms if a plugin provider in configured + if ( configuredRealms.stream().anyMatch( ( r ) -> r.startsWith( SecuritySettings.PLUGIN_REALM_NAME_PREFIX ) ) ) + { + realms.addAll( createPluginRealms( config, securityLog, secureHasher, configuredRealms ) ); + } // Select the active realms in the order they are configured List orderedActiveRealms = selectOrderedActiveRealms( configuredRealms, realms ); if ( orderedActiveRealms.isEmpty() ) { - String message = "Illegal configuration: No valid security realm is active."; - throw new IllegalArgumentException( message ); + throw new IllegalArgumentException( "Illegal configuration: No valid auth provider is active." ); } return new MultiRealmAuthManager( internalRealm, orderedActiveRealms, createCacheManager( config ), @@ -203,9 +207,10 @@ private static CacheManager createCacheManager( Config config ) return new ShiroCaffeineCache.Manager( Ticker.systemTicker(), ttl, maxCapacity ); } - private static List createPluginRealms( Config config, SecurityLog securityLog, SecureHasher secureHasher ) + private static List createPluginRealms( + Config config, SecurityLog securityLog, SecureHasher secureHasher, List configuredRealms ) { - List realms = new ArrayList<>(); + List availablePluginRealms = new ArrayList<>(); Set excludedClasses = new HashSet<>(); Boolean pluginAuthenticationEnabled = config.get( SecuritySettings.plugin_authentication_enabled ); @@ -220,7 +225,7 @@ private static List createPluginRealms( Config config, SecurityLog securi { PluginRealm pluginRealm = new PluginRealm( plugin, config, securityLog, Clocks.systemClock(), secureHasher ); - realms.add( pluginRealm ); + availablePluginRealms.add( pluginRealm ); } } @@ -248,7 +253,7 @@ private static List createPluginRealms( Config config, SecurityLog securi pluginRealm = new PluginRealm( plugin, null, config, securityLog, Clocks.systemClock(), secureHasher ); } - realms.add( pluginRealm ); + availablePluginRealms.add( pluginRealm ); } } @@ -263,11 +268,30 @@ private static List createPluginRealms( Config config, SecurityLog securi { PluginRealm pluginRealm = new PluginRealm( null, plugin, config, securityLog, Clocks.systemClock(), secureHasher ); - realms.add( pluginRealm ); + availablePluginRealms.add( pluginRealm ); } } } + List realms = + availablePluginRealms.stream() + .filter( realm -> configuredRealms.contains( realm.getName() ) ) + .collect( Collectors.toList() ); + boolean missingAuthenticationRealm = pluginAuthenticationEnabled && + !realms.stream().anyMatch( PluginRealm::canAuthenticate ); + boolean missingAuthorizingRealm = pluginAuthorizationEnabled && + !realms.stream().anyMatch( PluginRealm::canAuthorize ); + + if ( missingAuthenticationRealm || missingAuthorizingRealm ) + { + String missingProvider = + ( missingAuthenticationRealm && missingAuthorizingRealm ) ? "authentication or authorization" : + ( missingAuthenticationRealm ) ? "authentication" : "authorization"; + + throw new IllegalArgumentException( format( "Illegal configuration: No plugin %s provider loaded even " + + "though required by configuration.", missingProvider ) ); + } + return realms; } diff --git a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/plugin/PluginRealm.java b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/plugin/PluginRealm.java index be4b79124f839..d4f69956525d8 100644 --- a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/plugin/PluginRealm.java +++ b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/plugin/PluginRealm.java @@ -40,11 +40,12 @@ import org.neo4j.kernel.internal.Version; import org.neo4j.logging.Log; import org.neo4j.server.security.enterprise.auth.PredefinedRolesBuilder; +import org.neo4j.server.security.enterprise.auth.RealmLifecycle; import org.neo4j.server.security.enterprise.auth.SecureHasher; import org.neo4j.server.security.enterprise.auth.ShiroAuthToken; import org.neo4j.server.security.enterprise.auth.ShiroAuthorizationInfoProvider; -import org.neo4j.server.security.enterprise.auth.plugin.api.AuthToken; import org.neo4j.server.security.enterprise.auth.plugin.api.AuthProviderOperations; +import org.neo4j.server.security.enterprise.auth.plugin.api.AuthToken; import org.neo4j.server.security.enterprise.auth.plugin.api.AuthorizationExpiredException; import org.neo4j.server.security.enterprise.auth.plugin.spi.AuthInfo; import org.neo4j.server.security.enterprise.auth.plugin.spi.AuthPlugin; @@ -52,7 +53,6 @@ import org.neo4j.server.security.enterprise.auth.plugin.spi.AuthorizationPlugin; import org.neo4j.server.security.enterprise.auth.plugin.spi.CustomCacheableAuthenticationInfo; import org.neo4j.server.security.enterprise.log.SecurityLog; -import org.neo4j.server.security.enterprise.auth.RealmLifecycle; import static org.neo4j.server.security.enterprise.configuration.SecuritySettings.PLUGIN_REALM_NAME_PREFIX; @@ -224,6 +224,22 @@ private void cacheAuthorizationInfo( PluginAuthInfo authInfo ) authorizationCache.put( key, authInfo ); } + public boolean canAuthenticate() + { + return authPlugin != null || authenticationPlugin !=null; + } + + public boolean canAuthorize() + { + return authPlugin != null || authorizationPlugin != null; + } + + @Override + public AuthorizationInfo getAuthorizationInfoSnapshot( PrincipalCollection principalCollection ) + { + return getAuthorizationInfo( principalCollection ); + } + @Override protected Object getAuthorizationCacheKey( PrincipalCollection principals ) { @@ -331,12 +347,6 @@ private static CustomCacheableAuthenticationInfo.CredentialsMatcher getCustomCre return null; } - @Override - public AuthorizationInfo getAuthorizationInfoSnapshot( PrincipalCollection principalCollection ) - { - return getAuthorizationInfo( principalCollection ); - } - private class CredentialsMatcher implements org.apache.shiro.authc.credential.CredentialsMatcher { @Override diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/EnterpriseSecurityModuleTest.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/EnterpriseSecurityModuleTest.java index dd99cd80a46ae..39f2882c62ec6 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/EnterpriseSecurityModuleTest.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/EnterpriseSecurityModuleTest.java @@ -26,16 +26,13 @@ import java.util.Arrays; import org.neo4j.kernel.configuration.Config; -import org.neo4j.server.security.enterprise.configuration.SecuritySettings; -import org.neo4j.server.security.enterprise.log.SecurityLog; import org.neo4j.logging.Log; import org.neo4j.logging.LogProvider; +import org.neo4j.server.security.enterprise.configuration.SecuritySettings; +import org.neo4j.server.security.enterprise.log.SecurityLog; import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.contains; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class EnterpriseSecurityModuleTest @@ -52,21 +49,20 @@ public void shouldFailOnIllegalRealmNameConfiguration() Log mockLog = mock( Log.class ); when( mockLogProvider.getLog( anyString() ) ).thenReturn( mockLog ); when( mockLog.isDebugEnabled() ).thenReturn( true ); - when( config.get( SecuritySettings.native_authentication_enabled ) ).thenReturn( true ); - when( config.get( SecuritySettings.native_authorization_enabled ) ).thenReturn( true ); - when( config.get( SecuritySettings.ldap_authentication_enabled ) ).thenReturn( true ); - when( config.get( SecuritySettings.ldap_authorization_enabled ) ).thenReturn( true ); - when( config.get( SecuritySettings.plugin_authentication_enabled ) ).thenReturn( true ); - when( config.get( SecuritySettings.plugin_authorization_enabled ) ).thenReturn( true ); + when( config.get( SecuritySettings.native_authentication_enabled ) ).thenReturn( true ); + when( config.get( SecuritySettings.native_authorization_enabled ) ).thenReturn( true ); + when( config.get( SecuritySettings.ldap_authentication_enabled ) ).thenReturn( true ); + when( config.get( SecuritySettings.ldap_authorization_enabled ) ).thenReturn( true ); + when( config.get( SecuritySettings.plugin_authentication_enabled ) ).thenReturn( false ); + when( config.get( SecuritySettings.plugin_authorization_enabled ) ).thenReturn( false ); when( config.get( SecuritySettings.auth_providers ) ).thenReturn( Arrays.asList( "this-realm-does-not-exist" ) ); + + // Then thrown.expect( IllegalArgumentException.class ); + thrown.expectMessage( "Illegal configuration: No valid auth provider is active." ); // When new EnterpriseSecurityModule().newAuthManager( config, mockLogProvider, mock( SecurityLog.class), null, null ); - - // Then - verify( mockLog, atLeastOnce() ).debug( anyString(), - contains( "Illegal configuration: No valid security realm is active." ), anyString() ); } @Test @@ -78,24 +74,57 @@ public void shouldFailOnIllegalAdvancedRealmConfiguration() Log mockLog = mock( Log.class ); when( mockLogProvider.getLog( anyString() ) ).thenReturn( mockLog ); when( mockLog.isDebugEnabled() ).thenReturn( true ); - when( config.get( SecuritySettings.native_authentication_enabled ) ).thenReturn( false ); - when( config.get( SecuritySettings.native_authorization_enabled ) ).thenReturn( false ); - when( config.get( SecuritySettings.ldap_authentication_enabled ) ).thenReturn( false ); - when( config.get( SecuritySettings.ldap_authorization_enabled ) ).thenReturn( false ); - when( config.get( SecuritySettings.plugin_authentication_enabled ) ).thenReturn( true ); - when( config.get( SecuritySettings.plugin_authorization_enabled ) ).thenReturn( true ); + when( config.get( SecuritySettings.native_authentication_enabled ) ).thenReturn( false ); + when( config.get( SecuritySettings.native_authorization_enabled ) ).thenReturn( false ); + when( config.get( SecuritySettings.ldap_authentication_enabled ) ).thenReturn( false ); + when( config.get( SecuritySettings.ldap_authorization_enabled ) ).thenReturn( false ); + when( config.get( SecuritySettings.plugin_authentication_enabled ) ).thenReturn( true ); + when( config.get( SecuritySettings.plugin_authorization_enabled ) ).thenReturn( true ); when( config.get( SecuritySettings.auth_providers ) ).thenReturn( Arrays.asList( SecuritySettings.NATIVE_REALM_NAME, SecuritySettings.LDAP_REALM_NAME ) ); + + // Then thrown.expect( IllegalArgumentException.class ); + thrown.expectMessage( "Illegal configuration: No valid auth provider is active." ); // When new EnterpriseSecurityModule().newAuthManager( config, mockLogProvider, mock( SecurityLog.class), null, null ); + } + + @Test + public void shouldFailOnNotLoadedPluginAuthProvider() + { + // Given + Config config = mock( Config.class ); + LogProvider mockLogProvider = mock( LogProvider.class ); + Log mockLog = mock( Log.class ); + when( mockLogProvider.getLog( anyString() ) ).thenReturn( mockLog ); + when( mockLog.isDebugEnabled() ).thenReturn( true ); + when( config.get( SecuritySettings.auth_cache_ttl ) ).thenReturn( 0L ); + when( config.get( SecuritySettings.auth_cache_max_capacity ) ).thenReturn( 10 ); + when( config.get( SecuritySettings.security_log_successful_authentication ) ).thenReturn( false ); + + when( config.get( SecuritySettings.native_authentication_enabled ) ).thenReturn( false ); + when( config.get( SecuritySettings.native_authorization_enabled ) ).thenReturn( false ); + when( config.get( SecuritySettings.ldap_authentication_enabled ) ).thenReturn( false ); + when( config.get( SecuritySettings.ldap_authorization_enabled ) ).thenReturn( false ); + when( config.get( SecuritySettings.plugin_authentication_enabled ) ).thenReturn( true ); + when( config.get( SecuritySettings.plugin_authorization_enabled ) ).thenReturn( true ); + when( config.get( SecuritySettings.auth_providers ) ).thenReturn( + Arrays.asList( + SecuritySettings.PLUGIN_REALM_NAME_PREFIX + "TestAuthenticationPlugin", + SecuritySettings.PLUGIN_REALM_NAME_PREFIX + "IllConfiguredAuthorizationPlugin" + ) ); // Then - verify( mockLog, atLeastOnce() ).debug( anyString(), - contains( "Illegal configuration: No valid security realm is active." ), anyString() ); + thrown.expect( IllegalArgumentException.class ); + thrown.expectMessage( "Illegal configuration: No plugin authorization provider loaded even though required by " + + "configuration." ); + + // When + new EnterpriseSecurityModule().newAuthManager( config, mockLogProvider, mock( SecurityLog.class), null, null ); } }