/*
 * The MIT License
 *
 * Copyright (c) 2004-2010, InfraDNA, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package lib.form;

import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.Extension;
import hudson.cli.CopyJobCommand;
import hudson.cli.GetJobCommand;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.FreeStyleProject;
import hudson.model.Item;
import hudson.model.JobProperty;
import hudson.model.JobPropertyDescriptor;
import hudson.model.User;
import hudson.util.Secret;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.regex.Pattern;

import jenkins.model.Jenkins;
import org.acegisecurity.Authentication;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.DataBoundConstructor;

/**
 * @author Kohsuke Kawaguchi
 */
public class PasswordTest extends HudsonTestCase implements Describable<PasswordTest> {
    public Secret secret;

    public void test1() throws Exception {
        secret = Secret.fromString("secret");
        HtmlPage p = createWebClient().goTo("self/test1");
        String value = ((HtmlInput)p.getElementById("password")).getValueAttribute();
        assertFalse("password shouldn't be plain text",value.equals("secret"));
        assertEquals("secret",Secret.fromString(value).getPlainText());
    }

    public DescriptorImpl getDescriptor() {
        return jenkins.getDescriptorByType(DescriptorImpl.class);
    }

    @Extension
    public static final class DescriptorImpl extends Descriptor<PasswordTest> {}

    @Issue({"SECURITY-266", "SECURITY-304"})
    public void testExposedCiphertext() throws Exception {
        boolean saveEnabled = Item.EXTENDED_READ.getEnabled();
        Item.EXTENDED_READ.setEnabled(true);
        try {

            //final String plain_regex_match = ".*\\{[A-Za-z0-9+/]+={0,2}}.*";
            final String xml_regex_match = "\\{[A-Za-z0-9+/]+={0,2}}";
            final Pattern xml_regex_pattern = Pattern.compile(xml_regex_match);
            final String staticTest = "\n\nvalue=\"{AQAAABAAAAAgXhXgopokysZkduhl+v1gm0UhUBBbjKDVpKz7bGk3mIO53cNTRdlu7LC4jZYEc+vF}\"\n";
            //Just a quick verification on what could be on the page and that the regexp is correctly set up
            assertThat(xml_regex_pattern.matcher(staticTest).find(), is(true));

            jenkins.setSecurityRealm(new JenkinsRule().createDummySecurityRealm());
            jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().
                grant(Jenkins.ADMINISTER).everywhere().to("admin").
                grant(Jenkins.READ, Item.READ, Item.EXTENDED_READ,
                    Item.CREATE // so we can show CopyJobCommand would barf; more realistic would be to grant it only in a subfolder
                ).everywhere().to("dev"));
            Secret s = Secret.fromString("s3cr3t");
            //String sEnc = s.getEncryptedValue();
            FreeStyleProject p = createFreeStyleProject("p");
            p.setDisplayName("Unicode here ←");
            p.setDescription("This+looks+like+Base64+but+is+not+a+secret");
            p.addProperty(new VulnerableProperty(s));
            WebClient wc = createWebClient();
            // Control case: an administrator can read and write configuration freely.
            wc.login("admin");
            HtmlPage configure = wc.getPage(p, "configure");
            assertThat(xml_regex_pattern.matcher(configure.getWebResponse().getContentAsString()).find(), is(true));
            submit(configure.getFormByName("config"));
            VulnerableProperty vp = p.getProperty(VulnerableProperty.class);
            assertNotNull(vp);
            assertEquals(s, vp.secret);
            Page configXml = wc.goTo(p.getUrl() + "config.xml", "application/xml");
            String xmlAdmin = configXml.getWebResponse().getContentAsString();

            assertThat(Pattern.compile("<secret>" + xml_regex_match + "</secret>").matcher(xmlAdmin).find(), is(true));
            assertThat(xmlAdmin, containsString("<displayName>" + p.getDisplayName() + "</displayName>"));
            assertThat(xmlAdmin, containsString("<description>" + p.getDescription() + "</description>"));
            // CLICommandInvoker does not work here, as it sets up its own SecurityRealm + AuthorizationStrategy.
            GetJobCommand getJobCommand = new GetJobCommand();
            Authentication adminAuth = User.get("admin").impersonate();
            getJobCommand.setTransportAuth(adminAuth);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            String pName = p.getFullName();
            getJobCommand.main(Collections.singletonList(pName), Locale.ENGLISH, System.in, new PrintStream(baos), System.err);
            assertEquals(xmlAdmin, baos.toString(configXml.getWebResponse().getContentCharset()));
            CopyJobCommand copyJobCommand = new CopyJobCommand();
            copyJobCommand.setTransportAuth(adminAuth);
            String pAdminName = pName + "-admin";
            assertEquals(0, copyJobCommand.main(Arrays.asList(pName, pAdminName), Locale.ENGLISH, System.in, System.out, System.err));
            FreeStyleProject pAdmin = jenkins.getItemByFullName(pAdminName, FreeStyleProject.class);
            assertNotNull(pAdmin);
            pAdmin.setDisplayName(p.getDisplayName()); // counteract DisplayNameListener
            assertEquals(p.getConfigFile().asString(), pAdmin.getConfigFile().asString());
            // Test case: another user with EXTENDED_READ but not CONFIGURE should not get access even to encrypted secrets.
            wc.login("dev");
            configure = wc.getPage(p, "configure");
            assertThat(xml_regex_pattern.matcher(configure.getWebResponse().getContentAsString()).find(), is(false));
            configXml = wc.goTo(p.getUrl() + "config.xml", "application/xml");
            String xmlDev = configXml.getWebResponse().getContentAsString();
            assertThat(xml_regex_pattern.matcher(xmlDev).find(), is(false));
            assertEquals(xmlAdmin.replaceAll(xml_regex_match, "********"), xmlDev);
            getJobCommand = new GetJobCommand();
            Authentication devAuth = User.get("dev").impersonate();
            getJobCommand.setTransportAuth(devAuth);
            baos = new ByteArrayOutputStream();
            getJobCommand.main(Collections.singletonList(pName), Locale.ENGLISH, System.in, new PrintStream(baos), System.err);
            assertEquals(xmlDev, baos.toString(configXml.getWebResponse().getContentCharset()));
            copyJobCommand = new CopyJobCommand();
            copyJobCommand.setTransportAuth(devAuth);
            String pDevName = pName + "-dev";
            assertThat(copyJobCommand.main(Arrays.asList(pName, pDevName), Locale.ENGLISH, System.in, System.out, System.err), not(0));
            assertNull(jenkins.getItemByFullName(pDevName, FreeStyleProject.class));

        } finally {
            Item.EXTENDED_READ.setEnabled(saveEnabled);
        }
    }
    public static class VulnerableProperty extends JobProperty<FreeStyleProject> {
        public final Secret secret;
        @DataBoundConstructor
        public VulnerableProperty(Secret secret) {
            this.secret = secret;
        }
        @TestExtension("testExposedCiphertext")
        public static class DescriptorImpl extends JobPropertyDescriptor {
            @Override // TODO delete in 1.635+
            public String getDisplayName() {
                return "VulnerableProperty";
            }
        }
    }

}
