/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014, 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.management.cli;

import static org.hamcrest.CoreMatchers.containsString;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_ATTRIBUTE_OPERATION;
import static org.junit.Assert.assertThat;

import java.io.File;
import java.io.IOException;

import javax.inject.Inject;

import org.apache.commons.io.FileUtils;
import org.jboss.as.test.integration.management.util.CustomCLIExecutor;
import org.jboss.as.test.integration.security.common.CoreUtils;
import org.jboss.as.test.integration.security.common.SecurityTestConstants;
import org.jboss.as.test.module.util.TestModule;
import org.jboss.as.test.shared.AssumeTestGroupUtil;
import org.jboss.logging.Logger;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.wildfly.core.testrunner.ServerControl;
import org.wildfly.core.testrunner.ServerController;
import org.wildfly.core.testrunner.WildflyTestRunner;

/**
 * Testing support of custom security vaults for encrypting passwords in
 * jboss-cli configuration file. It tries to invoke CLI client with vaulted
 * passwords for truststore in its configuration, which results in resolving
 * vault expression.
 *
 * @author Filip Bogyai
 */
@RunWith(WildflyTestRunner.class)
@ServerControl(manual = true)
public class CustomVaultInCLITestCase {

    private static Logger LOGGER = Logger.getLogger(CustomVaultInCLITestCase.class);

    private static final File WORK_DIR = new File("cli-custom-vault-workdir");

    public static final File CLIENT_KEYSTORE_FILE = new File(WORK_DIR, SecurityTestConstants.CLIENT_KEYSTORE);
    public static final File CLIENT_TRUSTSTORE_FILE = new File(WORK_DIR, SecurityTestConstants.CLIENT_TRUSTSTORE);
    private static final String RIGHT_PASSWORD = SecurityTestConstants.KEYSTORE_PASSWORD;
    private static final String WRONG_PASSWORD = "someRandomWrongPass";

    private static final File MODULE_FILE = new File(WORK_DIR, "module.xml");

    private static final String JBOSS_CLI_FILE = "jboss-cli.xml";
    private static final File RIGHT_VAULT_PASSWORD_FILE = new File(WORK_DIR, "right-vault-pass.xml");
    private static final File WRONG_VAULT_PASSWORD_FILE = new File(WORK_DIR, "wrong-vault-pass.xml");
    private static final File NON_EXISTING_VAULT_PASSWORD_FILE = new File(WORK_DIR, "non-existing-vault-pass.xml");


    private static String MODULE_NAME = "test.custom.vault.in.cli";
    private static String JAR_NAME = "custom-dummy-vault-module.jar";
    private static final String CONTAINER = "default-jbossas";
    private static TestModule customVaultModule;

    @Inject
    private static ServerController containerController;

    @BeforeClass
    public static void prepareConfiguration() throws Exception {

        // Legacy security won't work on SE 14 or later; 11 is the last SE where EAP will support it
        AssumeTestGroupUtil.assumeJDKVersionBefore(12);

        FileUtils.deleteDirectory(WORK_DIR);
        WORK_DIR.mkdirs();
        CoreUtils.createKeyMaterial(WORK_DIR);

        createCustomVaultModule();
        createCustomVaultConfiguration();

        LOGGER.trace("*** starting server");
        containerController.start();
    }

    /**
     * Run CLI with vaulted keystore and truststore passwords. Vault expression
     * should return right password.
     */
    @Test
    public void testRightVaultPassword() throws Exception {

        String cliOutput = CustomCLIExecutor.execute(RIGHT_VAULT_PASSWORD_FILE, READ_ATTRIBUTE_OPERATION + " server-state");

        assertThat("Password should be right", cliOutput, containsString("Password is: " + RIGHT_PASSWORD));
        assertThat("CLI should successfully initialize ", cliOutput, containsString("running"));

    }

    /**
     * Run CLI with vaulted keystore and truststore passwords. Vault expression
     * should return wrong password, so CliInitializationException is expected
     */
    @Test
    public void testWrongVaultPassword() throws Exception {

        String cliOutput = CustomCLIExecutor.execute(WRONG_VAULT_PASSWORD_FILE, READ_ATTRIBUTE_OPERATION + " server-state");

        assertThat("Password should be wrong", cliOutput, containsString("Password is: " + WRONG_PASSWORD));
        assertThat("CLI shouldn't successfully initialize ", cliOutput, containsString("Keystore was tampered with, or password was incorrect"));

    }

    /**
     * Run CLI with vaulted keystore and truststore passwords. Vault expression
     * should not exists, so NullPointerException is expected
     */
    @Test
    public void testNonExistingVaultPassword() throws Exception {

        String cliOutput = CustomCLIExecutor.execute(NON_EXISTING_VAULT_PASSWORD_FILE, READ_ATTRIBUTE_OPERATION + " server-state");

        assertThat("Password should not exists", cliOutput, containsString("NullPointerException"));

    }

    @AfterClass
    public static void rollbackConfiguration() throws Exception {

        if (AssumeTestGroupUtil.isJDKVersionBefore(12)) {
            LOGGER.trace("*** stopping server");
            containerController.stop();

            customVaultModule.remove();
            FileUtils.deleteDirectory(WORK_DIR);
        }
    }

    private static void createCustomVaultModule() throws Exception {

        String moduleXML = "<module xmlns=\"urn:jboss:module:1.1\" name=\"" + MODULE_NAME + "\">" +
                "<resources> <resource-root path=\"" + JAR_NAME + "\"/>  </resources>" +
                "<dependencies> <module name=\"org.picketbox\"/> </dependencies> " +
                "</module>";

        FileUtils.write(MODULE_FILE, moduleXML);

        customVaultModule = new TestModule(MODULE_NAME, MODULE_FILE);
        customVaultModule.addResource(JAR_NAME).addClass(CustomDummyVault.class);
        customVaultModule.create(true);
    }

    private static void createCustomVaultConfiguration() throws IOException {

        String rightBlock = "rightVaultBlock";
        String wrongBlock = "wrongVaultBlock";

        String vaultConfig = "<vault code=\"" + CustomDummyVault.class.getName() + "\" module=\"" + MODULE_NAME + "\">" +
                "<vault-option name=\"" + rightBlock + "\" value=\"" + RIGHT_PASSWORD + "\"/>" +
                "<vault-option name=\"" + wrongBlock + "\" value=\"" + WRONG_PASSWORD + "\"/>" +
                "</vault>";

        //passwords are defined above and are retrieved depending only on vault block
        String vaultPasswordString = "VAULT::" + rightBlock + "::good::1";
        String wrongVaultPasswordString = "VAULT::" + wrongBlock + "::wrong::1";
        String nonExistingVaultPasswordString = "VAULT::nonExistingBlock::non::1";

        // create jboss-cli configuration file with vaulted passwords and change xsd to 3.0
        String rightVaultPassConfig = CoreUtils.propertiesReplacer(JBOSS_CLI_FILE, CLIENT_KEYSTORE_FILE, CLIENT_TRUSTSTORE_FILE,
                vaultPasswordString, vaultConfig).replaceAll("urn:jboss:cli:2.0", "urn:jboss:cli:3.0");
        String wrongVaultPassConfig = CoreUtils.propertiesReplacer(JBOSS_CLI_FILE, CLIENT_KEYSTORE_FILE, CLIENT_TRUSTSTORE_FILE,
                wrongVaultPasswordString, vaultConfig).replaceAll("urn:jboss:cli:2.0", "urn:jboss:cli:3.0");
        String nonVaultPassConfig = CoreUtils.propertiesReplacer(JBOSS_CLI_FILE, CLIENT_KEYSTORE_FILE, CLIENT_TRUSTSTORE_FILE,
                nonExistingVaultPasswordString, vaultConfig).replaceAll("urn:jboss:cli:2.0", "urn:jboss:cli:3.0");

        FileUtils.write(RIGHT_VAULT_PASSWORD_FILE, rightVaultPassConfig);
        FileUtils.write(WRONG_VAULT_PASSWORD_FILE, wrongVaultPassConfig);
        FileUtils.write(NON_EXISTING_VAULT_PASSWORD_FILE, nonVaultPassConfig);

    }

}
