package hudson.model;

import hudson.Functions;
import hudson.Launcher;
import hudson.tasks.BatchFile;
import hudson.tasks.Builder;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.recipes.LocalData;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class ParametersAction2Test {
    @Rule
    public JenkinsRule j = new JenkinsRule();

    @Test
    @Issue("SECURITY-170")
    public void undefinedParameters() throws Exception {
        FreeStyleProject p = j.createFreeStyleProject();
        p.addProperty(new ParametersDefinitionProperty(Arrays.asList(new ParameterDefinition[]{
                new StringParameterDefinition("foo", "foo"),
                new StringParameterDefinition("bar", "bar")
        })));
        ParametersCheckBuilder b = new ParametersCheckBuilder(false);
        p.getBuildersList().add(b);
        p.save();

        j.assertBuildStatusSuccess(p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(
                new StringParameterValue("foo", "baz"),
                new StringParameterValue("undef", "undef")
        )));
    }

    @Test
    @Issue("SECURITY-170")
    public void undefinedParametersOverride() throws Exception {
        FreeStyleProject p = j.createFreeStyleProject();
        p.addProperty(new ParametersDefinitionProperty(Arrays.asList(new ParameterDefinition[]{
                new StringParameterDefinition("foo", "foo"),
                new StringParameterDefinition("bar", "bar")
        })));
        ParametersCheckBuilder b = new ParametersCheckBuilder(true);
        p.getBuildersList().add(b);
        p.save();
        try {
            System.setProperty(ParametersAction.KEEP_UNDEFINED_PARAMETERS_SYSTEM_PROPERTY_NAME, "true");

            j.assertBuildStatusSuccess(p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(
                    new StringParameterValue("foo", "baz"),
                    new StringParameterValue("undef", "undef")
            )));
        } finally {
            System.clearProperty(ParametersAction.KEEP_UNDEFINED_PARAMETERS_SYSTEM_PROPERTY_NAME);
        }
    }

    @Test
    @Issue("SECURITY-170")
    @LocalData
    public void backwardCompatibility() throws Exception {
        // Local data contains a parameterized job with two parameters (FOO and BAR) and one build
        // with pre-fix format (generated with 1.609.3) with FOO, BAR and UNDEF.
        FreeStyleProject p = j.jenkins.getItemByFullName(Functions.isWindows() ? "parameterized-windows" : "parameterized", FreeStyleProject.class);

        FreeStyleBuild b1 = p.getBuildByNumber(1);
        ParametersAction pa = b1.getAction(ParametersAction.class);
        hasParameterWithName(pa, "FOO");
        hasParameterWithName(pa, "BAR");
        // legacy behaviour expected (UNDEF returned by getParameters())
        hasParameterWithName(pa, "UNDEF");

        // A new build should work as expected (undef is not published to env)
        ParametersCheckBuilder b = new ParametersCheckBuilder(false);
        p.getBuildersList().add(b);
        p.save();

        j.assertBuildStatusSuccess(p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(
                new StringParameterValue("foo", "baz"),
                new StringParameterValue("undef", "undef")
        )));
    }

    @Test
    @Issue("SECURITY-170")
    public void parametersDefinitionChange() throws Exception {
        FreeStyleProject p = j.createFreeStyleProject();
        p.addProperty(new ParametersDefinitionProperty(Arrays.<ParameterDefinition>asList(
                new StringParameterDefinition("foo", "foo"),
                new StringParameterDefinition("bar", "bar"))));

        FreeStyleBuild build = j.assertBuildStatusSuccess(p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(
                new StringParameterValue("foo", "baz"),
                new StringParameterValue("bar", "bar"),
                new StringParameterValue("undef", "undef")
        )));

        assertTrue("undef parameter is not listed in getParameters",
                !hasParameterWithName(build.getAction(ParametersAction.class), "undef"));

        p.removeProperty(ParametersDefinitionProperty.class);
        p.addProperty(new ParametersDefinitionProperty(Arrays.<ParameterDefinition>asList(
                new StringParameterDefinition("foo", "foo"),
                new StringParameterDefinition("bar", "bar"),
                new StringParameterDefinition("undef", "undef"))));

        // undef is still not listed even after being added to the job parameters definition
        assertTrue("undef parameter is not listed in getParameters",
                !hasParameterWithName(build.getAction(ParametersAction.class), "undef"));

        // remove bar and undef from parameters definition
        p.removeProperty(ParametersDefinitionProperty.class);
        p.addProperty(new ParametersDefinitionProperty(Arrays.<ParameterDefinition>asList(
                new StringParameterDefinition("foo", "foo"))));

        assertTrue("the build still have 2 parameters", build.getAction(ParametersAction.class).getParameters().size() == 2);

        p.removeProperty(ParametersDefinitionProperty.class);
        assertTrue("the build still have 2 parameters", build.getAction(ParametersAction.class).getParameters().size() == 2);
    }

    @Test
    @Issue("SECURITY-170")
    public void whitelistedParameter() throws Exception {
        FreeStyleProject p = j.createFreeStyleProject();
        p.addProperty(new ParametersDefinitionProperty(Arrays.<ParameterDefinition>asList(
                new StringParameterDefinition("foo", "foo"),
                new StringParameterDefinition("bar", "bar"))));

        try {
            System.setProperty(ParametersAction.SAFE_PARAMETERS_SYSTEM_PROPERTY_NAME, "whitelisted1,whitelisted2");
            FreeStyleBuild build = j.assertBuildStatusSuccess(p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(
                    new StringParameterValue("foo", "baz"),
                    new StringParameterValue("bar", "bar"),
                    new StringParameterValue("whitelisted1", "x"),
                    new StringParameterValue("whitelisted2", "y")
            )));

            assertTrue("whitelisted1 parameter is listed in getParameters",
                    hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted1"));
            assertTrue("whitelisted2 parameter is listed in getParameters",
                    hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted2"));
        } finally {
            System.clearProperty(ParametersAction.SAFE_PARAMETERS_SYSTEM_PROPERTY_NAME);
        }
    }

    @Test
    @Issue("SECURITY-170")
    public void whitelistedParameterByOverride() throws Exception {
        FreeStyleProject p = j.createFreeStyleProject();
        String name = p.getFullName();
        p.addProperty(new ParametersDefinitionProperty(Arrays.<ParameterDefinition>asList(
                new StringParameterDefinition("foo", "foo"),
                new StringParameterDefinition("bar", "bar"))));

        try {
            ParametersAction action = new ParametersAction(
                    Arrays.<ParameterValue>asList(
                        new StringParameterValue("foo", "baz"),
                        new StringParameterValue("bar", "bar"),
                        new StringParameterValue("whitelisted1", "x"),
                        new StringParameterValue("whitelisted2", "y"),
                        new StringParameterValue("whitelisted3", "y")
                                                 ),
                    Arrays.asList("whitelisted1", "whitelisted2"));
            FreeStyleBuild build = j.assertBuildStatusSuccess(p.scheduleBuild2(0, new Cause.UserIdCause(), action));

            assertTrue("whitelisted1 parameter is listed in getParameters",
                       hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted1"));
            assertTrue("whitelisted2 parameter is listed in getParameters",
                       hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted2"));
            assertFalse("whitelisted3 parameter is listed in getParameters",
                       hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted3"));
            p = null;
            build = null;
            j.jenkins.reload();
            //Test again after reload
            p = j.jenkins.getItemByFullName(name, FreeStyleProject.class);
            build = p.getLastBuild();
            assertTrue("whitelisted1 parameter is listed in getParameters",
                       hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted1"));
            assertTrue("whitelisted2 parameter is listed in getParameters",
                       hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted2"));
            assertFalse("whitelisted3 parameter is listed in getParameters",
                       hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted3"));
        } finally {
            System.clearProperty(ParametersAction.SAFE_PARAMETERS_SYSTEM_PROPERTY_NAME);
        }
    }

    @Test
    @Issue("SECURITY-170")
    public void whitelistedParameterSameAfterChange() throws Exception {
        FreeStyleProject p = j.createFreeStyleProject();
        String name = p.getFullName();
        p.addProperty(new ParametersDefinitionProperty(Arrays.<ParameterDefinition>asList(
                new StringParameterDefinition("foo", "foo"),
                new StringParameterDefinition("bar", "bar"))));

        try {
            System.setProperty(ParametersAction.SAFE_PARAMETERS_SYSTEM_PROPERTY_NAME, "whitelisted1,whitelisted2");
            FreeStyleBuild build = j.assertBuildStatusSuccess(p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(
                    new StringParameterValue("foo", "baz"),
                    new StringParameterValue("bar", "bar"),
                    new StringParameterValue("whitelisted1", "x"),
                    new StringParameterValue("whitelisted2", "y"),
                    new StringParameterValue("whitelisted3", "z"),
                    new StringParameterValue("whitelisted4", "w")
            )));
            assertTrue("whitelisted1 parameter is listed in getParameters",
                       hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted1"));
            assertTrue("whitelisted2 parameter is listed in getParameters",
                       hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted2"));
            assertFalse("whitelisted3 parameter is listed in getParameters",
                       hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted3"));
            assertFalse("whitelisted4 parameter is listed in getParameters",
                       hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted4"));

            System.setProperty(ParametersAction.SAFE_PARAMETERS_SYSTEM_PROPERTY_NAME, "whitelisted3,whitelisted4");
            p = null;
            build = null;
            j.jenkins.reload();
            p = j.jenkins.getItemByFullName(name, FreeStyleProject.class);
            build = p.getLastBuild();
            assertTrue("whitelisted1 parameter is listed in getParameters",
                       hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted1"));
            assertTrue("whitelisted2 parameter is listed in getParameters",
                       hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted2"));
            assertFalse("whitelisted3 parameter is listed in getParameters",
                        hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted3"));
            assertFalse("whitelisted4 parameter is listed in getParameters",
                        hasParameterWithName(build.getAction(ParametersAction.class), "whitelisted4"));

        } finally {
            System.clearProperty(ParametersAction.SAFE_PARAMETERS_SYSTEM_PROPERTY_NAME);
        }
    }



    @Test
    @Issue("SECURITY-170")
    public void nonParameterizedJob() throws Exception {
        FreeStyleProject p = j.createFreeStyleProject();
        FreeStyleBuild build = j.assertBuildStatusSuccess(p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(
                new StringParameterValue("foo", "baz"),
                new StringParameterValue("bar", "bar")
        )));

        assertTrue("foo parameter is not listed in getParameters",
                !hasParameterWithName(build.getAction(ParametersAction.class), "foo"));
        assertTrue("bar parameter is not listed in getParameters",
                !hasParameterWithName(build.getAction(ParametersAction.class), "bar"));
    }

    @Test
    @Issue("SECURITY-170")
    public void nonParameterizedJobButWhitelisted() throws Exception {
        FreeStyleProject p = j.createFreeStyleProject();

        try {
            System.setProperty(ParametersAction.SAFE_PARAMETERS_SYSTEM_PROPERTY_NAME, "foo,bar");
            FreeStyleBuild build2 = j.assertBuildStatusSuccess(p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(
                    new StringParameterValue("foo", "baz"),
                    new StringParameterValue("bar", "bar")
            )));

            assertTrue("foo parameter is listed in getParameters",
                    hasParameterWithName(build2.getAction(ParametersAction.class), "foo"));
            assertTrue("bar parameter is listed in getParameters",
                    hasParameterWithName(build2.getAction(ParametersAction.class), "bar"));
        } finally {
            System.clearProperty(ParametersAction.SAFE_PARAMETERS_SYSTEM_PROPERTY_NAME);
        }
    }

    public static boolean hasParameterWithName(Iterable<ParameterValue> values, String name) {
        for (ParameterValue v : values) {
            if (v.getName().equals(name)) {
                return true;
            }
        }
        return false;
    }


    public static class ParametersCheckBuilder extends Builder {

        private final boolean expectLegacyBehavior;

        public ParametersCheckBuilder(boolean expectLegacyBehavior) {
            this.expectLegacyBehavior = expectLegacyBehavior;
        }

        @Override
        public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
                throws InterruptedException, IOException {
            ParametersAction pa = build.getAction(ParametersAction.class);
            assertEquals("foo value expected changed", "baz", pa.getParameter("foo").getValue());

            if (expectLegacyBehavior) {
                assertTrue("undef parameter is listed in getParameters", hasParameterWithName(pa.getParameters(), "undef"));
                assertTrue("undef parameter is listed in iterator", hasParameterWithName(pa, "undef"));
                assertTrue("undef in environment", build.getEnvironment(listener).keySet().contains("undef"));
                assertTrue("UNDEF in environment", build.getEnvironment(listener).keySet().contains("UNDEF"));
            } else {
                assertFalse("undef parameter is not listed in getParameters", hasParameterWithName(pa.getParameters(), "undef"));
                assertFalse("undef parameter is not listed in iterator", hasParameterWithName(pa, "undef"));
                assertFalse("undef not in environment", build.getEnvironment(listener).keySet().contains("undef"));
                assertFalse("UNDEF not in environment", build.getEnvironment(listener).keySet().contains("UNDEF"));
            }

            assertTrue("undef parameter is listed in getAllParameters", hasParameterWithName(pa.getAllParameters(), "undef"));
            assertEquals("undef parameter direct access expected to work", "undef", pa.getParameter("undef").getValue());

            return true;
        }
    }
}
