/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2015, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.test.manualmode.security.messagingldapauth;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.ldif.LdifEntry;
import org.apache.directory.api.ldap.model.ldif.LdifReader;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.AnnotationUtils;
import org.apache.directory.server.core.annotations.ContextEntry;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.annotations.CreateIndex;
import org.apache.directory.server.core.annotations.CreatePartition;
import org.apache.directory.server.core.api.DirectoryService;
import org.apache.directory.server.core.factory.DSAnnotationProcessor;
import org.apache.directory.server.factory.ServerAnnotationProcessor;
import org.apache.directory.server.ldap.LdapServer;
import org.jboss.arquillian.container.test.api.ContainerController;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.junit.InSequence;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.as.arquillian.container.ManagementClient;
import org.jboss.as.test.integration.management.base.AbstractCliTestBase;
import org.jboss.as.test.integration.security.common.ManagedCreateLdapServer;
import org.jboss.as.test.integration.security.common.Utils;
import org.jboss.as.test.shared.TestSuiteEnvironment;
import org.jboss.vfs.VFSUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import static org.junit.Assert.assertTrue;

/**
 * This tests remoting LDAP authentication on the messaging component.
 *
 * @author istudens@redhat.com
 */
@RunWith(Arquillian.class)
@RunAsClient
public class MessagingRemotingLDAPAuthTestCase extends AbstractCliTestBase {

    private static final int REMOTING_PORT = 4447;
    private static final String SERVER_ADDRESS = TestSuiteEnvironment.getServerAddress();
    private static final String REMOTING_URL = "remote://" + SERVER_ADDRESS +":" + REMOTING_PORT;

    private static final String SECONDARY_TEST_ADDRESS = TestSuiteEnvironment.getSecondaryTestAddress(false);

    private static final String CONTAINER = "jbossas-messaging-live";

    private static final String TEST_NAME = MessagingRemotingLDAPAuthTestCase.class.getSimpleName();

    private static final String BATCH_CLI_FILENAME = TEST_NAME + "-batch.cli";
    private static final String BATCH_REMOVE_CLI_FILENAME = TEST_NAME + "-batch-remove.cli";

    private static final File WORK_DIR = new File(TEST_NAME + "-" + System.currentTimeMillis());
    private static final File BATCH_CLI_FILE = new File(WORK_DIR, BATCH_CLI_FILENAME);
    private static final File BATCH_REMOVE_CLI_FILE = new File(WORK_DIR, BATCH_REMOVE_CLI_FILENAME);

    private static final String PASSWORD = "password123";
    private static final String USER = "jduke";

    private static final int LDAP_PORT = 10389;

    private static final String QUEUE_NAME = "testQueue";

    @ArquillianResource
    private static ContainerController container;

    private static final LDAPServerSetupTask ldapSetup = new LDAPServerSetupTask();

    /**
     * Tests remote LDAP authentication.
     *
     * @throws Exception
     */
    @Test
    public void testRemoteAuthentication() throws Exception {
        final Properties jndiProps = new Properties();
        jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
        jndiProps.put(Context.PROVIDER_URL, REMOTING_URL);
        jndiProps.put("jboss.naming.client.ejb.context", "true");
        jndiProps.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT", "false");
        jndiProps.put(Context.SECURITY_PRINCIPAL, USER);
        jndiProps.put(Context.SECURITY_CREDENTIALS, PASSWORD);

        final Context ctx = new InitialContext(jndiProps);
        try {
            final ConnectionFactory remoteCF = (ConnectionFactory) ctx.lookup("jms/RemoteConnectionFactory");  // a.k.a. java:jboss/exported/jms/RemoteConnectionFactory
            Assert.assertNotNull(remoteCF);

            final Queue remoteQueue = (Queue) ctx.lookup("jms/queue/" + QUEUE_NAME);    // java:jboss/exported/jms/queue/testQueue
            Assert.assertNotNull(remoteQueue);

            final Connection remoteConnection = remoteCF.createConnection(USER, PASSWORD);
            Assert.assertNotNull(remoteConnection);

        } finally {
            ctx.close();
        }
    }

    // Server configuration part of the TestCase **********************************************

    /**
     * Configure the AS and LDAP as the first step in this testcase.
     *
     * @throws Exception
     */
    @Test
    @InSequence(Integer.MIN_VALUE)
    public void initServer() throws Exception {
        ldapSetup.startDirectoryServer();
        container.start(CONTAINER);

        WORK_DIR.mkdirs();

        final Map<String, String> map = new HashMap<String, String>();
        map.put("ldapHost", SECONDARY_TEST_ADDRESS);
        map.put("ldapPort", Integer.toString(LDAP_PORT));
        FileUtils.write(BATCH_CLI_FILE,
                StrSubstitutor.replace(IOUtils.toString(getClass().getResourceAsStream(BATCH_CLI_FILENAME), "UTF-8"), map),
                "UTF-8");
        FileUtils.write(BATCH_REMOVE_CLI_FILE,
                StrSubstitutor.replace(IOUtils.toString(getClass().getResourceAsStream(BATCH_REMOVE_CLI_FILENAME), "UTF-8"), map),
                "UTF-8");

        initCLI();
        final boolean batchResult = runBatch(BATCH_CLI_FILE);
        closeCLI();

        try {
            assertTrue("Server configuration failed", batchResult);
        } finally {
            container.stop(CONTAINER);
        }
        container.start(CONTAINER);
    }

    /**
     * Revert the AS configuration and stop the server as the last but one step.
     *
     * @throws Exception
     */
    @Test
    @InSequence(Integer.MAX_VALUE - 1)
    public void closeServer() throws Exception {
        final ManagementClient managementClient = new ManagementClient(TestSuiteEnvironment.getModelControllerClient(),
                SERVER_ADDRESS, TestSuiteEnvironment.getServerPort());
        if (!managementClient.isServerInRunningState()) {
            container.start(CONTAINER);
        }

        initCLI();
        final boolean batchResult = runBatch(BATCH_REMOVE_CLI_FILE);
        closeCLI();

        container.stop(CONTAINER);

        FileUtils.deleteQuietly(WORK_DIR);

        assertTrue("Reverting server configuration failed", batchResult);
    }

    /**
     * Stop the LDAP as a last step in this testcase.
     *
     * @throws Exception
     */
    @Test
    @InSequence(Integer.MAX_VALUE)
    public void stopLdap() throws Exception {
        ldapSetup.shutdownDirectoryServer();
    }

    /**
     * A server setup task which configures and starts LDAP server. It enables additional ApacheDS interceptor to count LDAP
     * search requests.
     */
    //@formatter:off
    @CreateDS(
            name = "JBossDS",
            partitions = {
                @CreatePartition(
                name = "jboss",
                suffix = "dc=jboss,dc=org",
                contextEntry = @ContextEntry(
                entryLdif = "dn: dc=jboss,dc=org\n"
                + "dc: jboss\n"
                + "objectClass: top\n"
                + "objectClass: domain\n\n"),
                indexes = {
                    @CreateIndex(attribute = "objectClass"),
                    @CreateIndex(attribute = "dc"),
                    @CreateIndex(attribute = "ou")
                }
                )
            })
    @CreateLdapServer(
            transports = {
                    @CreateTransport(protocol = "LDAP", port = LDAP_PORT, address = "0.0.0.0")
            })
    //@formatter:on
    static class LDAPServerSetupTask {

        private DirectoryService directoryService;
        private LdapServer ldapServer;

        /**
         * Creates directory services, starts LDAP server.
         */
        public void startDirectoryServer() throws Exception {
            directoryService = DSAnnotationProcessor.getDirectoryService();
            final SchemaManager schemaManager = directoryService.getSchemaManager();
            final Class<MessagingRemotingLDAPAuthTestCase> clazz = MessagingRemotingLDAPAuthTestCase.class;
            LdifReader ldifReader = new LdifReader(clazz.getResourceAsStream(clazz.getSimpleName() + ".ldif"));
            try {
                for (LdifEntry ldifEntry : ldifReader) {
                    directoryService.getAdminSession().add(new DefaultEntry(schemaManager, ldifEntry.getEntry()));
                }
            } finally {
                VFSUtils.safeClose(ldifReader);
            }
            final ManagedCreateLdapServer createLdapServer = new ManagedCreateLdapServer(
                    (CreateLdapServer) AnnotationUtils.getInstance(CreateLdapServer.class));
            Utils.fixApacheDSTransportAddress(createLdapServer, StringUtils.strip(SECONDARY_TEST_ADDRESS));
            ldapServer = ServerAnnotationProcessor.instantiateLdapServer(createLdapServer, directoryService);
            startLdapServer();
        }

        /**
         * Stops the LDAP server, directory service and removes the working files of ApacheDS.
         */
        public void shutdownDirectoryServer() throws Exception {
            stopLdapServer();
            directoryService.shutdown();
            FileUtils.deleteDirectory(directoryService.getInstanceLayout().getInstanceDirectory());
        }

        /**
         * Starts LDAP server instance.
         *
         * @throws Exception
         */
        public void startLdapServer() throws Exception {
            ldapServer.start();
        }

        /**
         * Stops LDAP server instance.
         *
         * @throws Exception
         */
        public void stopLdapServer() {
            ldapServer.stop();
        }

    }

}
