From 1b74de13fa49bba04fee9056de456fe936f4dee0 Mon Sep 17 00:00:00 2001 From: Henrik Nyman Date: Mon, 29 Aug 2016 18:06:38 +0200 Subject: [PATCH] Add support for secure LDAP (StartTLS, LDAPS) - Add boolean setting `dbms.security.realms.ldap.use_starttls` to use opportunistic TLS (upgrading an initially insecure connection to TLS) - Change setting `dbms.security.realms.ldap.host` to string to support specifying the protocol --- .../security/enterprise/auth/LdapRealm.java | 81 +++- .../enterprise/auth/SecuritySettings.java | 10 +- .../bolt/LdapAuthenticationIT.java | 349 +++++++++++++++++- .../resources/neo4j_ldap_test_keystore.jks | Bin 0 -> 2072 bytes 4 files changed, 426 insertions(+), 14 deletions(-) create mode 100644 enterprise/security/src/test/resources/neo4j_ldap_test_keystore.jks 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 bb3e971021c7f..cac3ab28a746b 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 @@ -53,7 +53,10 @@ import javax.naming.directory.Attributes; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; +import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; +import javax.naming.ldap.StartTlsRequest; +import javax.naming.ldap.StartTlsResponse; import org.neo4j.kernel.api.security.AuthenticationResult; import org.neo4j.kernel.configuration.Config; @@ -71,6 +74,7 @@ public class LdapRealm extends JndiLdapRealm private Boolean authenticationEnabled; private Boolean authorizationEnabled; + private Boolean useStartTls; private String userSearchBase; private String userSearchFilter; private List membershipAttributeNames; @@ -98,7 +102,72 @@ protected AuthenticationInfo queryForAuthenticationInfo( AuthenticationToken tok LdapContextFactory ldapContextFactory ) throws NamingException { - return authenticationEnabled ? super.queryForAuthenticationInfo( token, ldapContextFactory ) : null; + if ( authenticationEnabled ) + { + JndiLdapContextFactory jndiLdapContextFactory = (JndiLdapContextFactory) ldapContextFactory; + // TODO: Maybe change this to security event log once we have it + log.debug( "Authenticating user '%s' against LDAP server '%s'%s", token.getPrincipal(), + jndiLdapContextFactory.getUrl(), + useStartTls ? " using StartTLS" : "" ); + try + { + return useStartTls ? queryForAuthenticationInfoUsingStartTls( token, ldapContextFactory ) : + super.queryForAuthenticationInfo( token, ldapContextFactory ); + } + catch ( Exception e ) + { + // TODO: Maybe change this to security event log once we have it + log.debug( "Authentication exception: [%s] %s", e.getClass().getName(), e.getMessage() ); + throw e; + } + } + else + { + return null; + } + } + + protected AuthenticationInfo queryForAuthenticationInfoUsingStartTls( AuthenticationToken token, + LdapContextFactory ldapContextFactory ) throws NamingException + { + JndiLdapContextFactory jndiLdapContextFactory = (JndiLdapContextFactory) ldapContextFactory; + Object principal = token.getPrincipal(); + Object credentials = token.getCredentials(); + + principal = getLdapPrincipal(token); + + LdapContext ctx = null; + Hashtable env = new Hashtable<>(); + env.put( Context.INITIAL_CONTEXT_FACTORY, jndiLdapContextFactory.getContextFactoryClassName() ); + env.put( Context.PROVIDER_URL, jndiLdapContextFactory.getUrl() ); + + try { + ctx = new InitialLdapContext( env, null ); + + StartTlsRequest startTlsRequest = new StartTlsRequest(); + StartTlsResponse tls = (StartTlsResponse) ctx.extendedOperation( startTlsRequest ); + try + { + tls.negotiate(); + } + catch ( IOException e ) + { + log.error( "Failed to negotiate TLS connection", e ); + throw new CommunicationException( e.getMessage() ); + } + + ctx.addToEnvironment( Context.SECURITY_AUTHENTICATION, ((JndiLdapContextFactory) ldapContextFactory).getAuthenticationMechanism() ); + ctx.addToEnvironment( Context.SECURITY_PRINCIPAL, principal ); + ctx.addToEnvironment( Context.SECURITY_CREDENTIALS, credentials ); + + ctx.reconnect( ctx.getConnectControls() ); + + return createAuthenticationInfo(token, principal, credentials, ctx); + } + finally + { + LdapUtils.closeContext( ctx ); + } } @Override @@ -148,6 +217,8 @@ protected AuthenticationInfo createAuthenticationInfo( AuthenticationToken token throws NamingException { // NOTE: This will be called only if authentication with the ldap context was successful + // TODO: Change this to security event log once we have it + log.debug( "Successfully authenticated user '%s' through LDAP", token.getPrincipal() ); // If authorization is enabled but useSystemAccountForAuthorization is disabled, we should perform // the search for groups directly here while the user's authenticated ldap context is open. @@ -199,7 +270,7 @@ public Collection resolvePermissionsInRole( String roleString ) private void configureRealm( Config config ) { JndiLdapContextFactory contextFactory = new JndiLdapContextFactory(); - contextFactory.setUrl( "ldap://" + config.get( SecuritySettings.ldap_server ) ); + contextFactory.setUrl( parseLdapServerUrl( config.get( SecuritySettings.ldap_server ) ) ); contextFactory.setAuthenticationMechanism( config.get( SecuritySettings.ldap_auth_mechanism ) ); contextFactory.setReferral( config.get( SecuritySettings.ldap_referral ) ); contextFactory.setSystemUsername( config.get( SecuritySettings.ldap_system_username ) ); @@ -215,6 +286,7 @@ private void configureRealm( Config config ) authenticationEnabled = config.get( SecuritySettings.ldap_authentication_enabled ); authorizationEnabled = config.get( SecuritySettings.ldap_authorization_enabled ); + useStartTls = config.get( SecuritySettings.ldap_use_starttls ); userSearchBase = config.get( SecuritySettings.ldap_authorization_user_search_base ); userSearchFilter = config.get( SecuritySettings.ldap_authorization_user_search_filter ); @@ -224,6 +296,11 @@ private void configureRealm( Config config ) parseGroupToRoleMapping( config.get( SecuritySettings.ldap_authorization_group_to_role_mapping ) ); } + private String parseLdapServerUrl( String rawLdapServer ) + { + return rawLdapServer.contains( "://" ) ? rawLdapServer : "ldap://" + rawLdapServer; + } + Map> parseGroupToRoleMapping( String groupToRoleMappingString ) { Map> map = new HashMap<>(); 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 38a2881a2b88a..e39cf398ffde1 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 @@ -65,8 +65,14 @@ public class SecuritySettings 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.realms.ldap.host", HOSTNAME_PORT, "0.0.0.0:389" ); + public static final Setting ldap_server = + setting( "dbms.security.realms.ldap.host", STRING, "0.0.0.0:389" ); + + @Description( "Use secure communication with the LDAP server using opportunistic TLS. " + + "First an initial insecure connection will be made with the LDAP server and a STARTTLS command will be " + + "issued to negotiate an upgrade of the connection to TLS before initiating authentication." ) + public static final Setting ldap_use_starttls = + setting( "dbms.security.realms.ldap.use_starttls", BOOLEAN, "false" ); @Description( "LDAP authentication mechanism. This is one of `simple` or a SASL mechanism supported by JNDI, " + "e.g. `DIGEST-MD5`. `simple` is basic username" + 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 c5267cb741d6a..849baac69a188 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 @@ -29,11 +29,19 @@ import org.apache.directory.server.core.annotations.LoadSchema; import org.apache.directory.server.core.integ.AbstractLdapTestUnit; import org.apache.directory.server.core.integ.FrameworkRunner; +import org.apache.directory.server.ldap.handlers.extended.StartTlsHandler; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.Map; +import java.util.function.Consumer; + import org.neo4j.bolt.v1.transport.integration.Neo4jWithSocket; import org.neo4j.bolt.v1.transport.integration.TransportTestUtil; import org.neo4j.bolt.v1.transport.socket.client.SecureSocketConnection; @@ -47,10 +55,6 @@ import org.neo4j.test.TestEnterpriseGraphDatabaseFactory; import org.neo4j.test.TestGraphDatabaseFactory; -import java.io.IOException; -import java.util.Map; -import java.util.function.Consumer; - import static org.hamcrest.MatcherAssert.assertThat; import static org.neo4j.bolt.v1.messaging.message.InitMessage.init; import static org.neo4j.bolt.v1.messaging.message.PullAllMessage.pullAll; @@ -75,17 +79,22 @@ }, loadedSchemas = { @LoadSchema( name = "nis", enabled = true ), - @LoadSchema( name = "posix", enabled = false ) } ) @CreateLdapServer( - transports = { @CreateTransport( protocol = "LDAP", port = 10389, address = "0.0.0.0" ) }, + transports = { @CreateTransport( protocol = "LDAP", port = 10389, address = "0.0.0.0" ), + @CreateTransport( protocol = "LDAPS", port = 10636, address = "0.0.0.0", ssl = true ) + }, + saslMechanisms = { @SaslMechanism( name = "DIGEST-MD5", implClass = org.apache.directory.server.ldap.handlers.sasl .digestMD5.DigestMd5MechanismHandler.class ), - @SaslMechanism( name = "CRAM-MD5", implClass = org.apache.directory.server.ldap.handlers.sasl + @SaslMechanism( name = "CRAM-MD5", implClass = org.apache.directory.server.ldap.handlers.sasl .cramMD5.CramMd5MechanismHandler.class ) }, - saslHost = "0.0.0.0" + saslHost = "0.0.0.0", + extendedOpHandlers = { StartTlsHandler.class }, + keyStore = "target/test-classes/neo4j_ldap_test_keystore.jks", + certificatePassword = "secret" ) @ApplyLdifFiles( "ldap_test_data.ldif" ) public class LdapAuthenticationIT extends AbstractLdapTestUnit @@ -330,17 +339,209 @@ public void shouldBeAbleToLoginWithLdapAndAuthorizeInternally() throws Throwable } ); // Then - //-------------------------- // First login as the admin user 'neo4j' and create the internal user 'neo' with role 'reader' testCreateReaderUser(); - //-------------------------- // Then login user 'neo' with LDAP and test that internal authorization gives correct permission reconnect(); testAuthWithReaderUser(); } + //------------------------------------------------------------------ + // Embedded secure LDAP tests + // NOTE: These can potentially mess up the environment for any subsequent tests relying on the + // default Java key/trust stores + + @Test + public void shouldBeAbleToLoginAndAuthorizeReaderWithLdapOnlyUsingLDAPS() throws Throwable + { + try( EmbeddedTestCertificates ignore = new EmbeddedTestCertificates() ) + { + // When + restartNeo4jServerWithOverriddenSettings( ldapOnlyAuthSettings.andThen( settings -> { + settings.put( SecuritySettings.ldap_server, "ldaps://localhost:10636" ); + } ) ); + + // Then + testAuthWithReaderUser(); + } + } + + @Test + public void shouldBeAbleToLoginAndAuthorizeReaderWithUserLdapContextUsingLDAPS() throws Throwable + { + try( EmbeddedTestCertificates ignore = new EmbeddedTestCertificates() ) + { + // When + restartNeo4jServerWithOverriddenSettings( ldapOnlyAuthSettings.andThen( settings -> { + settings.put( SecuritySettings.ldap_authorization_use_system_account, "false" ); + settings.put( SecuritySettings.ldap_server, "ldaps://localhost:10636" ); + } ) ); + + // Then + testAuthWithReaderUser(); + } + } + + @Test + public void shouldBeAbleToLoginAndAuthorizeReaderWithLdapOnlyUsingStartTls() throws Throwable + { + try( EmbeddedTestCertificates ignore = new EmbeddedTestCertificates() ) + { + // When + restartNeo4jServerWithOverriddenSettings( ldapOnlyAuthSettings.andThen( settings -> { + settings.put( SecuritySettings.ldap_server, "localhost:10389" ); + settings.put( SecuritySettings.ldap_use_starttls, "true" ); + } ) ); + + // Then + testAuthWithReaderUser(); + } + } + + @Test + public void shouldBeAbleToLoginAndAuthorizeReaderWithLdapUserContextUsingStartTls() throws Throwable + { + try( EmbeddedTestCertificates ignore = new EmbeddedTestCertificates() ) + { + // When + restartNeo4jServerWithOverriddenSettings( ldapOnlyAuthSettings.andThen( settings -> { + settings.put( SecuritySettings.ldap_authorization_use_system_account, "false" ); + settings.put( SecuritySettings.ldap_server, "localhost:10389" ); + settings.put( SecuritySettings.ldap_use_starttls, "true" ); + } ) ); + + // Then + testAuthWithReaderUser(); + } + } + + //------------------------------------------------------------------ + // Active Directory tests on EC2 + // NOTE: These rely on an external server and are not executed by automated testing + // They are here as a convenience for running local testing. + + //@Test + public void shouldNotBeAbleToLoginUnknownUserOnEC2() throws Throwable + { + restartNeo4jServerWithOverriddenSettings( activeDirectoryOnEc2NotUsingSystemAccountSettings ); + + assertAuthFail( "unknown", "abc123ABC123" ); + } + + //@Test + public void shouldBeAbleToLoginAndAuthorizeReaderWithUserLdapContextOnEC2() throws Throwable + { + restartNeo4jServerWithOverriddenSettings( activeDirectoryOnEc2NotUsingSystemAccountSettings ); + + assertAuth( "neo", "abc123ABC123" ); + assertReadSucceeds(); + assertWriteFails( "neo" ); + } + + //@Test + public void shouldBeAbleToLoginAndAuthorizeReaderOnEC2() throws Throwable + { + restartNeo4jServerWithOverriddenSettings( activeDirectoryOnEc2UsingSystemAccountSettings ); + + assertAuth( "neo", "abc123ABC123" ); + assertReadSucceeds(); + assertWriteFails( "neo" ); + } + + //@Test + public void shouldBeAbleToLoginAndAuthorizePublisherWithUserLdapContextOnEC2() throws Throwable + { + restartNeo4jServerWithOverriddenSettings( activeDirectoryOnEc2NotUsingSystemAccountSettings ); + + assertAuth( "tank", "abc123ABC123" ); + assertWriteSucceeds(); + } + + //@Test + public void shouldBeAbleToLoginAndAuthorizePublisherOnEC2() throws Throwable + { + restartNeo4jServerWithOverriddenSettings( activeDirectoryOnEc2UsingSystemAccountSettings ); + + assertAuth( "tank", "abc123ABC123" ); + assertWriteSucceeds(); + } + + //@Test + public void shouldBeAbleToLoginAndAuthorizeNoPermissionUserWithUserLdapContextOnEC2() throws Throwable + { + restartNeo4jServerWithOverriddenSettings( activeDirectoryOnEc2NotUsingSystemAccountSettings ); + + assertAuth( "smith", "abc123ABC123" ); + assertReadFails( "smith" ); + } + + //@Test + public void shouldBeAbleToLoginAndAuthorizeNoPermissionUserOnEC2() throws Throwable + { + restartNeo4jServerWithOverriddenSettings( activeDirectoryOnEc2UsingSystemAccountSettings ); + + assertAuth( "smith", "abc123ABC123" ); + assertReadFails( "smith" ); + } + + //------------------------------------------------------------------ + // Secure Active Directory tests on EC2 + // NOTE: These tests does not work together with EmbeddedTestCertificates used in the embedded secure LDAP tests! + // (This is because the embedded tests override the Java default key/trust store locations using + // system properties that will not be re-read) + + //@Test + public void shouldBeAbleToLoginAndAuthorizeReaderUsingLdapsOnEC2() throws Throwable + { + restartNeo4jServerWithOverriddenSettings( activeDirectoryOnEc2UsingSystemAccountSettings.andThen( settings -> { + settings.put( SecuritySettings.ldap_server, "ldaps://henrik.neohq.net:636" ); + }) ); + + assertAuth( "neo", "abc123ABC123" ); + assertReadSucceeds(); + assertWriteFails( "neo" ); + } + + //@Test + public void shouldBeAbleToLoginAndAuthorizeReaderWithUserLdapContextUsingLDAPSOnEC2() throws Throwable + { + restartNeo4jServerWithOverriddenSettings( activeDirectoryOnEc2NotUsingSystemAccountSettings.andThen( settings -> { + settings.put( SecuritySettings.ldap_server, "ldaps://henrik.neohq.net:636" ); + }) ); + + assertAuth( "neo", "abc123ABC123" ); + assertReadSucceeds(); + assertWriteFails( "neo" ); + } + + //@Test + public void shouldBeAbleToLoginAndAuthorizeReaderUsingStartTlsOnEC2() throws Throwable + { + restartNeo4jServerWithOverriddenSettings( activeDirectoryOnEc2UsingSystemAccountSettings.andThen( settings -> { + settings.put( SecuritySettings.ldap_use_starttls, "true" ); + }) ); + + assertAuth( "neo", "abc123ABC123" ); + assertReadSucceeds(); + assertWriteFails( "neo" ); + } + + //@Test + public void shouldBeAbleToLoginAndAuthorizeReaderWithUserLdapContextUsingStartTlsOnEC2() throws Throwable + { + restartNeo4jServerWithOverriddenSettings( activeDirectoryOnEc2NotUsingSystemAccountSettings.andThen( settings -> { + settings.put( SecuritySettings.ldap_use_starttls, "true" ); + }) ); + + assertAuth( "neo", "abc123ABC123" ); + assertReadSucceeds(); + assertWriteFails( "neo" ); + } + + //------------------------------------------------------------------------- + @Before public void setup() { @@ -457,6 +658,54 @@ private void assertAuthFail( String username, String password ) throws Exception assertThat( client, eventuallyReceives( msgFailure( Status.Security.Unauthorized, "The client is unauthorized due to authentication failure." ) ) ); } + protected void assertReadSucceeds() throws Exception + { + // When + client.send( TransportTestUtil.chunk( + run( "MATCH (n) RETURN n" ), + pullAll() ) ); + + // Then + assertThat( client, eventuallyReceives( msgSuccess(), msgSuccess() ) ); + } + + protected void assertReadFails( String username ) throws Exception + { + // When + client.send( TransportTestUtil.chunk( + run( "MATCH (n) RETURN n" ), + pullAll() ) ); + + // Then + assertThat( client, eventuallyReceives( + msgFailure( Status.Security.Forbidden, + String.format( "Read operations are not allowed for '%s'.", username ) ) ) ); + } + + protected void assertWriteSucceeds() throws Exception + { + // When + client.send( TransportTestUtil.chunk( + run( "CREATE ()" ), + pullAll() ) ); + + // Then + assertThat( client, eventuallyReceives( msgSuccess(), msgSuccess() ) ); + } + + protected void assertWriteFails( String username ) throws Exception + { + // When + client.send( TransportTestUtil.chunk( + run( "CREATE ()" ), + pullAll() ) ); + + // Then + assertThat( client, eventuallyReceives( + msgFailure( Status.Security.Forbidden, + String.format( "Write operations are not allowed for '%s'.", username ) ) ) ); + } + private Consumer,String>> ldapOnlyAuthSettings = settings -> { settings.put( SecuritySettings.internal_authentication_enabled, "false" ); @@ -464,4 +713,84 @@ private void assertAuthFail( String username, String password ) throws Exception settings.put( SecuritySettings.ldap_authentication_enabled, "true" ); settings.put( SecuritySettings.ldap_authorization_enabled, "true" ); }; + + private Consumer,String>> activeDirectoryOnEc2Settings = settings -> { + //settings.put( SecuritySettings.ldap_server, "ec2-176-34-79-113.eu-west-1.compute.amazonaws.com:389" ); + settings.put( SecuritySettings.ldap_server, "henrik.neohq.net:389" ); + settings.put( SecuritySettings.ldap_user_dn_template, "cn={0},cn=Users,dc=neo4j,dc=com" ); + settings.put( SecuritySettings.ldap_authorization_user_search_base, "cn=Users,dc=neo4j,dc=com" ); + settings.put( SecuritySettings.ldap_authorization_user_search_filter, "(&(objectClass=*)(CN={0}))" ); + settings.put( SecuritySettings.ldap_authorization_group_membership_attribute_names, "memberOf" ); + settings.put( SecuritySettings.ldap_authorization_group_to_role_mapping, + "'CN=Neo4j Read Only,CN=Users,DC=neo4j,DC=com'=reader;" + + "CN=Neo4j Read-Write,CN=Users,DC=neo4j,DC=com=publisher;" + + "CN=Neo4j Schema Manager,CN=Users,DC=neo4j,DC=com=architect;" + + "CN=Neo4j Administrator,CN=Users,DC=neo4j,DC=com=admin" + ); + }; + + private Consumer,String>> activeDirectoryOnEc2NotUsingSystemAccountSettings = + activeDirectoryOnEc2Settings.andThen( settings -> { + settings.put( SecuritySettings.ldap_authorization_use_system_account, "false" ); + } ); + + private Consumer,String>> activeDirectoryOnEc2UsingSystemAccountSettings = + activeDirectoryOnEc2Settings.andThen( settings -> { + settings.put( SecuritySettings.ldap_authorization_use_system_account, "true" ); + settings.put( SecuritySettings.ldap_system_username, "Petra Selmer" ); + settings.put( SecuritySettings.ldap_system_password, "S0uthAfrica" ); + } ); + + //------------------------------------------------------------------------- + // TLS helper + private class EmbeddedTestCertificates implements AutoCloseable + { + private static final String KEY_STORE = "javax.net.ssl.keyStore"; + private static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; + private static final String TRUST_STORE = "javax.net.ssl.trustStore"; + private static final String TRUST_STORE_PASSWORD = "javax.net.ssl.trustStorePassword"; + + private final String keyStore = System.getProperty( KEY_STORE ); + private final String keyStorePassword = System.getProperty( KEY_STORE_PASSWORD ); + private final String trustStore = System.getProperty( TRUST_STORE ); + private final String trustStorePassword = System.getProperty( TRUST_STORE_PASSWORD ); + + public EmbeddedTestCertificates() + { + File keyStoreFile = fileFromResources( "/neo4j_ldap_test_keystore.jks" ); + String keyStorePath = keyStoreFile.getAbsolutePath(); + + System.setProperty( KEY_STORE, keyStorePath ); + System.setProperty( KEY_STORE_PASSWORD, "secret" ); + System.setProperty( TRUST_STORE, keyStorePath ); + System.setProperty( TRUST_STORE_PASSWORD, "secret" ); + } + + @Override + public void close() throws Exception + { + resetProperty( KEY_STORE, keyStore ); + resetProperty( KEY_STORE_PASSWORD, keyStorePassword ); + resetProperty( TRUST_STORE, trustStore ); + resetProperty( TRUST_STORE_PASSWORD, trustStorePassword ); + } + + private File fileFromResources( String path ) + { + URL url = getClass().getResource( path ); + return new File( url.getFile() ); + } + + private void resetProperty( String property, String value ) + { + if ( property != null ) + { + System.clearProperty( property ); + } + else + { + System.setProperty( property, value ); + } + } + } } diff --git a/enterprise/security/src/test/resources/neo4j_ldap_test_keystore.jks b/enterprise/security/src/test/resources/neo4j_ldap_test_keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..d979ff366ca2b14459babe18ad6c97042607a978 GIT binary patch literal 2072 zcmV+z2UN7&?>dSDF9k zo;K>o*mwiT(fRqlJMPDjdAje3fKC-olpd?|ZR%HKNg26X2>39rCDcF~5{GkEo9F;b z7ItoZ8NN9J^NsDO`_{`4L?9~mfCzGu-VB2o$)#eqX@d1mQ*etfV5@ex)6N+Is?(Ht z_07x1*SLK1H3)--OY6*Mb*V)9mgbfIo=`)hu4euBnw33`K-o`juV7Y3Rb}Pc8F9hZ z(a;Hw% zru6iSr!iHKoOyGkAh--DSNfbFwdI7iLl2S9mIEV!i|oY(j#m5#|= zzKfj1fZ(59C=||VXAx~=Vpo}I``g+H!TF5>uDUT}wC4m)Zo?qa)RnUEN_yNTE?CT( z=dG4K5Zsr~5$y~MgAc3jis!3D*fbf@It`~_>S9CKqo=P<&Bn$|kwF*A_%Mn5kP<07 zAitv4+2CBUl&RaBjyvhhFTxqqJWEpXEaUXKGA~;GX zNw^XM;LojRiOj7&WG~Q%8(~7IS(+GG$mMb6qKMCS$>{g&&&a1~ZgqdgyEJ7wa_<5p z_XoNLUp-S!YNA^PGtqXyvD`3p-A+N&`qx03eEGojflDLPTs+9}60tQ%qy!njzux{|i`Q6Bn za$!EVraac2hSG!GNSOCpaP8tFG=GmgM)T~&k)2p1o zBE{*IH9pW_bGaBHAu z6&U6y`yrfD|1q#Md*`uGK2t zI}bCM6U>-H<=m+)NJOrHtF0VosNbtXE^ZH+N|?!i*l3L`Yq1^p&S$Q4Eqn~eZ)X?} z=Nz0)m$^gQ-I|%QG%Fl@MGf+L{*1{W2$ItIFM|DQ4~}9o5M(-pTYUIxb=M(FA@(Pu zKt$AbXT-wjFgLP`HmPV|t}1)wlWK)lpL4_j000311z0XMFgXAK0?jaj0?9Cf0kNP1 z0s#U71U=$LA}|dG2`Yw2hW8Bt0Sg5HFcdKoFc1aB@2FfcYTG%z_eG&43@FcdKoFc1a&LNQU+5* zrRxX4sNF<1i;A0*fg_ggatmTr;K#RmY4kVzrtWD~QeYra$lj515?|-p2{t~>Da~8H z24_`hd=x@{@6I|U9<@vpCp)I_qRjo$AGw^h_W=h(xRCyP0MEPNUtV7j8cRc1YEBx; zS*1)2Ms;#0Sl!D)$P8|z13UipK!0`qPCZDtSq8^9j8TXqc8cGO+v52t?*ob%aKSFJ#8^NfsjkrhYb93176%BfAVRqFU zaGt64$jmQMp-1@rPMEz`Agt{et>Mkl>h)E5YReKs!TbWcD-0Yf;vk8$v;-xtc z(;~ea^;FD4m==DZ;=dwCVOiR0o&;#bz?*_|L0(O}8!PDbMf*N{ScEW-#T>fBUf(FA zhGXCe2kuppU^STKOO@LTtJ(Jb%1uAR)Hun%yi@v5Ky^S_c65zQbuEM2H~!HmVZ7ll z*$#-)VUB9cqMj0NWAWn?*O%{~QBOIq$d5h*