/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *  
 *    http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License. 
 *  
 */

package org.apache.directory.studio.test.integration.core;


import static org.apache.directory.studio.test.integration.junit5.TestFixture.CONTEXT_DN;
import static org.apache.directory.studio.test.integration.junit5.TestFixture.MISC111_DN;
import static org.apache.directory.studio.test.integration.junit5.TestFixture.MISC_DN;
import static org.apache.directory.studio.test.integration.junit5.TestFixture.REFERRALS_DN;
import static org.apache.directory.studio.test.integration.junit5.TestFixture.REFERRAL_LOOP_1_DN;
import static org.apache.directory.studio.test.integration.junit5.TestFixture.REFERRAL_LOOP_2_DN;
import static org.apache.directory.studio.test.integration.junit5.TestFixture.REFERRAL_TO_REFERRALS_DN;
import static org.apache.directory.studio.test.integration.junit5.TestFixture.REFERRAL_TO_REFERRAL_TO_USERS_DN;
import static org.apache.directory.studio.test.integration.junit5.TestFixture.REFERRAL_TO_USERS_DN;
import static org.apache.directory.studio.test.integration.junit5.TestFixture.USER1_DN;
import static org.apache.directory.studio.test.integration.junit5.TestFixture.USER8_DN;
import static org.apache.directory.studio.test.integration.junit5.TestFixture.USERS_DN;
import static org.apache.directory.studio.test.integration.junit5.TestFixture.dn;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.net.ConnectException;
import java.nio.channels.UnresolvedAddressException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;

import javax.naming.directory.SearchControls;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.directory.api.ldap.codec.api.LdapApiService;
import org.apache.directory.api.ldap.codec.api.LdapApiServiceFactory;
//import org.apache.directory.api.ldap.extras.extended.endTransaction.EndTransactionRequest;
//import org.apache.directory.api.ldap.extras.extended.endTransaction.EndTransactionResponse;
import org.apache.directory.api.ldap.extras.extended.pwdModify.PasswordModifyRequest;
//import org.apache.directory.api.ldap.extras.extended.startTransaction.StartTransactionRequest;
//import org.apache.directory.api.ldap.extras.extended.startTransaction.StartTransactionResponse;
import org.apache.directory.api.ldap.extras.extended.whoAmI.WhoAmIRequest;
import org.apache.directory.api.ldap.extras.extended.whoAmI.WhoAmIResponse;
import org.apache.directory.api.ldap.model.constants.SaslQoP;
import org.apache.directory.api.ldap.model.constants.SaslSecurityStrength;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.DefaultModification;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Modification;
import org.apache.directory.api.ldap.model.entry.ModificationOperation;
import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
import org.apache.directory.api.ldap.model.exception.LdapAuthenticationNotSupportedException;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapLoopDetectedException;
import org.apache.directory.api.ldap.model.message.ExtendedResponse;
import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.util.Strings;
import org.apache.directory.ldap.client.api.exception.InvalidConnectionException;
import org.apache.directory.studio.common.core.jobs.StudioProgressMonitor;
import org.apache.directory.studio.connection.core.Connection;
import org.apache.directory.studio.connection.core.Connection.AliasDereferencingMethod;
import org.apache.directory.studio.connection.core.Connection.ReferralHandlingMethod;
import org.apache.directory.studio.connection.core.ConnectionCorePlugin;
import org.apache.directory.studio.connection.core.ConnectionParameter;
import org.apache.directory.studio.connection.core.ConnectionParameter.AuthenticationMethod;
import org.apache.directory.studio.connection.core.ConnectionParameter.EncryptionMethod;
import org.apache.directory.studio.connection.core.ConnectionParameter.Krb5CredentialConfiguration;
import org.apache.directory.studio.connection.core.ICertificateHandler.TrustLevel;
import org.apache.directory.studio.connection.core.IReferralHandler;
import org.apache.directory.studio.connection.core.event.ConnectionEventRegistry;
import org.apache.directory.studio.connection.core.io.ConnectionWrapper;
import org.apache.directory.studio.connection.core.io.StudioLdapException;
import org.apache.directory.studio.connection.core.io.api.DirectoryApiConnectionWrapper;
import org.apache.directory.studio.connection.core.io.api.StudioSearchResult;
import org.apache.directory.studio.connection.core.io.api.StudioSearchResultEnumeration;
import org.apache.directory.studio.ldapbrowser.core.jobs.InitializeRootDSERunnable;
import org.apache.directory.studio.ldapbrowser.core.model.impl.BrowserConnection;
import org.apache.directory.studio.test.integration.junit5.LdapServerType;
import org.apache.directory.studio.test.integration.junit5.LdapServersSource;
import org.apache.directory.studio.test.integration.junit5.LdapServersSource.Mode;
import org.apache.directory.studio.test.integration.junit5.SkipTestIfLdapServerIsNotAvailableInterceptor;
import org.apache.directory.studio.test.integration.junit5.TestData;
import org.apache.directory.studio.test.integration.junit5.TestFixture;
import org.apache.directory.studio.test.integration.junit5.TestLdapServer;
import org.apache.mina.util.AvailablePortFinder;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;


/**
 * Tests the {@link DirectoryApiConnectionWrapper}.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 * @version $Rev$, $Date$
 */
@ExtendWith(SkipTestIfLdapServerIsNotAvailableInterceptor.class)
public class DirectoryApiConnectionWrapperTest
{

    protected ConnectionWrapper connectionWrapper;

    @BeforeAll
    public static void suspendEventFiringInCurrentThread()
    {
        ConnectionEventRegistry.suspendEventFiringInCurrentThread();
        ConnectionCorePlugin.getDefault().setCertificateHandler( null );
    }


    @AfterAll
    public static void resumeEventFiringInCurrentThread()
    {
        ConnectionEventRegistry.resumeEventFiringInCurrentThread();
    }


    @AfterEach
    public void tearDown() throws Exception
    {
        if ( connectionWrapper != null )
        {
            connectionWrapper.disconnect();
        }
    }


    /**
     * Tests connecting to the server without encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All)
    public void testConnectPlain( TestLdapServer ldapServer )
    {
        StudioProgressMonitor monitor = getProgressMonitor();
        getConnection( monitor, ldapServer, null, null );

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        assertTrue( connectionWrapper.isConnected() );
        assertFalse( connectionWrapper.isSecured() );
        assertNull( connectionWrapper.getSslSession() );
        assertNull( monitor.getException() );

        connectionWrapper.disconnect();
        assertFalse( connectionWrapper.isConnected() );
    }


    /**
     * Tests connecting to the server using ldaps:// encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All)
    public void testConnectLdaps( TestLdapServer ldapServer )
    {
        StudioProgressMonitor monitor = getProgressMonitor();
        Connection connection = getConnection( monitor, ldapServer, null, null );
        connection.setPort( ldapServer.getPortSSL() );
        connection.setEncryptionMethod( EncryptionMethod.LDAPS );
        acceptAllCertificates();

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        assertTrue( connectionWrapper.isConnected() );
        assertTrue( connectionWrapper.isSecured() );
        assertSslSession( ldapServer );
        assertNull( monitor.getException() );

        connectionWrapper.disconnect();
        assertFalse( connectionWrapper.isConnected() );
    }


    /**
     * Tests connecting to the server using StartTLS encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All)
    public void testConnectStartTls( TestLdapServer ldapServer )
    {
        StudioProgressMonitor monitor = getProgressMonitor();
        Connection connection = getConnection( monitor, ldapServer, null, null );
        connection.setEncryptionMethod( EncryptionMethod.START_TLS );
        acceptAllCertificates();

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        assertTrue( connectionWrapper.isConnected() );
        assertTrue( connectionWrapper.isSecured() );
        assertSslSession( ldapServer );
        assertNull( monitor.getException() );

        connectionWrapper.disconnect();
        assertFalse( connectionWrapper.isConnected() );
    }


    /**
     * Test failed connections to the server.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All)
    public void testConnectFailures( TestLdapServer ldapServer )
    {
        StudioProgressMonitor monitor = null;
        Connection connection = null;

        // invalid port
        monitor = getProgressMonitor();
        connection = getConnection( monitor, ldapServer, null, null );
        connection.setPort( AvailablePortFinder.getNextAvailable() );
        connectionWrapper.connect( monitor );
        assertFalse( connectionWrapper.isConnected() );
        assertNotNull( monitor.getException() );
        assertTrue( monitor.getException() instanceof StudioLdapException );
        assertNotNull( monitor.getException().getCause() );
        assertTrue( monitor.getException().getCause() instanceof InvalidConnectionException );
        assertNotNull( monitor.getException().getCause().getCause() );
        assertTrue( monitor.getException().getCause().getCause() instanceof ConnectException );

        // unknown host
        monitor = getProgressMonitor();
        connection = getConnection( monitor, ldapServer, null, null );
        connection.setHost( "555.555.555.555" );
        connectionWrapper.connect( monitor );
        assertFalse( connectionWrapper.isConnected() );
        assertNotNull( monitor.getException() );
        assertTrue( monitor.getException() instanceof StudioLdapException );
        assertNotNull( monitor.getException().getCause() );
        assertTrue( monitor.getException().getCause() instanceof InvalidConnectionException );
        assertNotNull( monitor.getException().getCause().getCause() );
        assertTrue( monitor.getException().getCause().getCause() instanceof UnresolvedAddressException );
    }


    private void assertSslSession( TestLdapServer ldapServer )
    {
        try
        {
            SSLSession sslSession = connectionWrapper.getSslSession();
            assertNotNull( sslSession );
            assertNotNull( sslSession.getProtocol() );
            assertNotNull( sslSession.getCipherSuite() );
            assertNotNull( sslSession.getPeerCertificates() );
            if ( ldapServer.getType() == LdapServerType.ApacheDS )
            {
                assertEquals( "TLSv1.2", sslSession.getProtocol() );
            }
            else
            {
                assertEquals( "TLSv1.3", sslSession.getProtocol() );
            }
        }
        catch ( SSLPeerUnverifiedException e )
        {
            throw new RuntimeException( e );
        }
    }


    /**
     * Test binding to the server using simple auth and no encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All)
    public void testSimpleBindPlain( TestLdapServer ldapServer )
    {
        ldapServer.setConfidentialityRequired( false );
        StudioProgressMonitor monitor = getProgressMonitor();
        getConnection( monitor, ldapServer, ldapServer.getAdminDn(), ldapServer.getAdminPassword() );

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        connectionWrapper.bind( monitor );
        assertTrue( connectionWrapper.isConnected() );
        assertFalse( connectionWrapper.isSecured() );
        assertNull( monitor.getException() );

        connectionWrapper.unbind();
        connectionWrapper.disconnect();
        assertFalse( connectionWrapper.isConnected() );
    }


    /**
     * Test binding to the server using simple auth and no encryption should fail if the server requires confidentially.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All)
    public void testSimpleBindPlainConfidentiallyRequired( TestLdapServer ldapServer )
    {
        ldapServer.setConfidentialityRequired( true );
        StudioProgressMonitor monitor = getProgressMonitor();
        getConnection( monitor, ldapServer, ldapServer.getAdminDn(), ldapServer.getAdminPassword() );

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        connectionWrapper.bind( monitor );

        assertFalse( connectionWrapper.isConnected() );
        assertFalse( connectionWrapper.isSecured() );
        assertNotNull( monitor.getException() );
        assertTrue( monitor.getException() instanceof StudioLdapException );
        assertTrue( monitor.getException().getMessage().contains( "[LDAP result code 13 - confidentialityRequired]" ) );
        assertNotNull( monitor.getException().getCause() );
        assertTrue( monitor.getException().getCause() instanceof LdapAuthenticationNotSupportedException );
    }


    /**
     * Test binding to the server using simple auth and ldaps:// encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All)
    public void testSimpleBindLdaps( TestLdapServer ldapServer )
    {
        ldapServer.setConfidentialityRequired( true );
        StudioProgressMonitor monitor = getProgressMonitor();
        Connection connection = getConnection( monitor, ldapServer, ldapServer.getAdminDn(),
            ldapServer.getAdminPassword() );
        connection.setPort( ldapServer.getPortSSL() );
        connection.setEncryptionMethod( EncryptionMethod.LDAPS );
        acceptAllCertificates();

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        connectionWrapper.bind( monitor );
        assertTrue( connectionWrapper.isConnected() );
        assertTrue( connectionWrapper.isSecured() );
        assertSslSession( ldapServer );
        assertNull( monitor.getException() );

        connectionWrapper.unbind();
        connectionWrapper.disconnect();
        assertFalse( connectionWrapper.isConnected() );
    }


    /**
     * Test binding to the server using simple auth and StartTLS encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All)
    public void testSimpleBindStartTls( TestLdapServer ldapServer )
    {
        ldapServer.setConfidentialityRequired( true );
        StudioProgressMonitor monitor = getProgressMonitor();
        Connection connection = getConnection( monitor, ldapServer, ldapServer.getAdminDn(),
            ldapServer.getAdminPassword() );
        connection.setEncryptionMethod( EncryptionMethod.START_TLS );
        acceptAllCertificates();

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        connectionWrapper.bind( monitor );
        assertTrue( connectionWrapper.isConnected() );
        assertTrue( connectionWrapper.isSecured() );
        assertSslSession( ldapServer );
        assertNull( monitor.getException() );

        connectionWrapper.unbind();
        connectionWrapper.disconnect();
        assertFalse( connectionWrapper.isConnected() );
    }


    /**
     * Test binding to the server using SASL auth and no encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All)
    public void testSaslDigestMd5BindAuthPlain( TestLdapServer ldapServer )
    {
        testSaslDigestMd5BindPlain( ldapServer, SaslQoP.AUTH );
    }


    /**
     * Test binding to the server using SASL auth-int and no encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds)
    public void testSaslDigestMd5BindAuthIntPlain( TestLdapServer ldapServer )
    {
        testSaslDigestMd5BindPlain( ldapServer, SaslQoP.AUTH_INT );
    }


    /**
     * Test binding to the server using SASL auth-conf and no encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds)
    public void testSaslDigestMd5BindAuthConfPlain( TestLdapServer ldapServer )
    {
        testSaslDigestMd5BindPlain( ldapServer, SaslQoP.AUTH_CONF );
    }


    private void testSaslDigestMd5BindPlain( TestLdapServer ldapServer, SaslQoP saslQoP )
    {
        ldapServer.setConfidentialityRequired( false );
        StudioProgressMonitor monitor = getProgressMonitor();
        Connection connection = getConnection( monitor, ldapServer, "user.1", "password" );
        connection.setAuthMethod( AuthenticationMethod.SASL_DIGEST_MD5 );
        connection.getConnectionParameter().setSaslQop( SaslQoP.AUTH_CONF );

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        connectionWrapper.bind( monitor );

        assertTrue( connectionWrapper.isConnected() );
        assertFalse( connectionWrapper.isSecured() );
        assertNull( monitor.getException() );

        connectionWrapper.unbind();
        connectionWrapper.disconnect();
        assertFalse( connectionWrapper.isConnected() );
    }


    /**
     * Test binding to the server using SASL and no encryption should fail if the server requires confidentially.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds)
    public void testSaslBindPlainConfidentiallyRequired( TestLdapServer ldapServer )
    {
        ldapServer.setConfidentialityRequired( true );
        StudioProgressMonitor monitor = getProgressMonitor();
        Connection connection = getConnection( monitor, ldapServer, "user.1", "password" );
        connection.setAuthMethod( AuthenticationMethod.SASL_DIGEST_MD5 );

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        connectionWrapper.bind( monitor );

        assertFalse( connectionWrapper.isConnected() );
        assertFalse( connectionWrapper.isSecured() );
        assertNotNull( monitor.getException() );
        assertTrue( monitor.getException() instanceof StudioLdapException );
        assertTrue( monitor.getException().getMessage().contains( "[LDAP result code 13 - confidentialityRequired]" ) );
        assertNotNull( monitor.getException().getCause() );
        assertTrue( monitor.getException().getCause() instanceof LdapAuthenticationNotSupportedException );
    }


    /**
     * Test binding to the server using SASL auth and ldaps:// encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds)
    public void testSaslDigestMd5BindAuthLdaps( TestLdapServer ldapServer )
    {
        testSaslDigestMd5BindLdaps( ldapServer, SaslQoP.AUTH );
    }


    /**
     * Test binding to the server using SASL auth-int and ldaps:// encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds)
    public void testSaslDigestMd5BindAuthIntLdaps( TestLdapServer ldapServer )
    {
        testSaslDigestMd5BindLdaps( ldapServer, SaslQoP.AUTH_INT );
    }


    /**
     * Test binding to the server using SASL auth-conf and ldaps:// encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All)
    public void testSaslDigestMd5BindAuthConfLdaps( TestLdapServer ldapServer )
    {
        testSaslDigestMd5BindLdaps( ldapServer, SaslQoP.AUTH_CONF );
    }


    private void testSaslDigestMd5BindLdaps( TestLdapServer ldapServer, SaslQoP saslQoP )
    {
        ldapServer.setConfidentialityRequired( true );
        StudioProgressMonitor monitor = getProgressMonitor();
        Connection connection = getConnection( monitor, ldapServer, "user.1", "password" );
        connection.setPort( ldapServer.getPortSSL() );
        connection.setEncryptionMethod( EncryptionMethod.LDAPS );
        connection.setAuthMethod( AuthenticationMethod.SASL_DIGEST_MD5 );
        connection.getConnectionParameter().setSaslQop( saslQoP );
        acceptAllCertificates();

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        connectionWrapper.bind( monitor );

        assertTrue( connectionWrapper.isConnected() );
        assertTrue( connectionWrapper.isSecured() );
        assertSslSession( ldapServer );
        assertNull( monitor.getException() );

        connectionWrapper.unbind();
        connectionWrapper.disconnect();
        assertFalse( connectionWrapper.isConnected() );
    }


    /**
     * Test binding to the server using SASL auth and StartTLS encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds)
    public void testSaslDigestMd5BindAuthStartTls( TestLdapServer ldapServer )
    {
        testSaslDigestMd5BindStartTls( ldapServer, SaslQoP.AUTH );
    }


    /**
     * Test binding to the server using SASL auth-int and StartTLS encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds)
    public void testSaslDigestMd5BindAuthIntStartTls( TestLdapServer ldapServer )
    {
        testSaslDigestMd5BindStartTls( ldapServer, SaslQoP.AUTH_INT );
    }


    /**
     * Test binding to the server using SASL auth-conf and StartTLS encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All)
    public void testSaslDigestMd5BindAuthConfStartTls( TestLdapServer ldapServer )
    {
        testSaslDigestMd5BindStartTls( ldapServer, SaslQoP.AUTH_CONF );
    }


    private void testSaslDigestMd5BindStartTls( TestLdapServer ldapServer, SaslQoP saslQoP )
    {
        ldapServer.setConfidentialityRequired( true );
        StudioProgressMonitor monitor = getProgressMonitor();
        Connection connection = getConnection( monitor, ldapServer, "user.1", "password" );
        connection.setEncryptionMethod( EncryptionMethod.START_TLS );
        connection.setAuthMethod( AuthenticationMethod.SASL_DIGEST_MD5 );
        connection.getConnectionParameter().setSaslQop( saslQoP );
        acceptAllCertificates();

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        connectionWrapper.bind( monitor );

        assertTrue( connectionWrapper.isConnected() );
        assertTrue( connectionWrapper.isSecured() );
        assertSslSession( ldapServer );
        assertNull( monitor.getException() );

        connectionWrapper.unbind();
        connectionWrapper.disconnect();
        assertFalse( connectionWrapper.isConnected() );
    }


    /**
     * Test binding to the server using GSSAPI auth and no encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
    public void testSaslGssapiBindAuthPlain( TestLdapServer ldapServer )
    {
        testSaslGssapiBindPlain( ldapServer, SaslQoP.AUTH );
    }


    /**
     * Test binding to the server using GSSAPI auth-int and no encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
    public void testSaslGssapiBindAuthIntPlain( TestLdapServer ldapServer )
    {
        testSaslGssapiBindPlain( ldapServer, SaslQoP.AUTH_INT );
    }


    /**
     * Test binding to the server using GSSAPI auth-conf and no encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
    public void testSaslGssapiBindAuthConfPlain( TestLdapServer ldapServer )
    {
        testSaslGssapiBindPlain( ldapServer, SaslQoP.AUTH_CONF );
    }


    private void testSaslGssapiBindPlain( TestLdapServer ldapServer, SaslQoP saslQoP )
    {
        TestFixture.skipIfKdcServerIsNotAvailable();

        ldapServer.setConfidentialityRequired( false );
        StudioProgressMonitor monitor = getProgressMonitor();
        Connection connection = getConnection( monitor, ldapServer, "hnelson", "secret" );
        connection.setAuthMethod( AuthenticationMethod.SASL_GSSAPI );
        connection.getConnectionParameter().setKrb5CredentialConfiguration( Krb5CredentialConfiguration.OBTAIN_TGT );
        connection.getConnectionParameter().setSaslQop( saslQoP );

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        connectionWrapper.bind( monitor );

        assertTrue( connectionWrapper.isConnected() );
        assertFalse( connectionWrapper.isSecured() );
        assertNull( monitor.getException() );

        connectionWrapper.unbind();
        connectionWrapper.disconnect();
        assertFalse( connectionWrapper.isConnected() );
    }


    /**
     * Test binding to the server using GSSAPI auth and ldaps:// encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
    public void testSaslGssapiBindAuthLdaps( TestLdapServer ldapServer ) throws Exception
    {
        testSaslGssapiBindLdaps( ldapServer, SaslQoP.AUTH );
    }


    /**
     * Test binding to the server using GSSAPI auth-int and ldaps:// encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
    public void testSaslGssapiBindAuthIntLdaps( TestLdapServer ldapServer ) throws Exception
    {
        testSaslGssapiBindLdaps( ldapServer, SaslQoP.AUTH_INT );
    }


    /**
     * Test binding to the server using GSSAPI auth-conf and ldaps:// encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
    public void testSaslGssapiBindAuthConfLdaps( TestLdapServer ldapServer ) throws Exception
    {
        testSaslGssapiBindLdaps( ldapServer, SaslQoP.AUTH_CONF );
    }


    private void testSaslGssapiBindLdaps( TestLdapServer ldapServer, SaslQoP saslQoP ) throws Exception
    {
        TestFixture.skipIfKdcServerIsNotAvailable();

        // obtain native TGT
        String[] cmd =
            { "/bin/sh", "-c", "echo secret | /usr/bin/kinit hnelson" };
        Process process = Runtime.getRuntime().exec( cmd );
        int exitCode = process.waitFor();
        assertEquals( 0, exitCode );

        ldapServer.setConfidentialityRequired( true );
        StudioProgressMonitor monitor = getProgressMonitor();
        Connection connection = getConnection( monitor, ldapServer, "hnelson", "secret" );
        connection.setPort( ldapServer.getPortSSL() );
        connection.setEncryptionMethod( EncryptionMethod.LDAPS );
        connection.setAuthMethod( AuthenticationMethod.SASL_GSSAPI );
        connection.getConnectionParameter().setKrb5CredentialConfiguration( Krb5CredentialConfiguration.USE_NATIVE );
        connection.getConnectionParameter().setSaslQop( saslQoP );
        acceptAllCertificates();

        assertFalse( connectionWrapper.isConnected() );

        connectionWrapper.connect( monitor );
        connectionWrapper.bind( monitor );

        assertTrue( connectionWrapper.isConnected() );
        assertTrue( connectionWrapper.isSecured() );
        assertSslSession( ldapServer );
        assertNull( monitor.getException() );

        connectionWrapper.unbind();
        connectionWrapper.disconnect();
        assertFalse( connectionWrapper.isConnected() );
    }


    /**
     * Test binding to the server using GSSAPI auth and StartTLS encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
    public void testSaslGssapiBindAuthStartTls( TestLdapServer ldapServer )
    {
        testSaslGssapiBindStartTls( ldapServer, SaslQoP.AUTH );
    }


    /**
     * Test binding to the server using GSSAPI auth-int and StartTLS encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
    public void testSaslGssapiBindAuthIntStartTls( TestLdapServer ldapServer )
    {
        testSaslGssapiBindStartTls( ldapServer, SaslQoP.AUTH_INT );
    }


    /**
     * Test binding to the server using GSSAPI auth-conf and StartTLS encryption.
     */
    @ParameterizedTest
    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
    public void testSaslGssapiBindAuthConfStartTls( TestLdapServer ldapServer )
    {
        testSaslGssapiBindStartTls( ldapServer, SaslQoP.AUTH_CONF );
