package jenkins.security;

import hudson.Extension;
import hudson.Util;
import hudson.Functions;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.TaskListener;
import hudson.util.HttpResponses;
import hudson.util.SecretRewriter;
import hudson.util.VersionNumber;
import jenkins.management.AsynchronousAdministrativeMonitor;
import jenkins.model.Jenkins;
import jenkins.util.io.FileBoolean;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.interceptor.RequirePOST;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.security.GeneralSecurityException;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Warns the administrator to run {@link SecretRewriter}
 *
 * @author Kohsuke Kawaguchi
 */
@Extension @Symbol("rekeySecret")
public class RekeySecretAdminMonitor extends AsynchronousAdministrativeMonitor {

    /**
     * Whether we detected a need to run the rewrite program.
     * Once we set it to true, we'll never turn it off.
     *
     * If the admin decides to dismiss this warning, we use {@link #isEnabled()} for that.
     *
     * In this way we can correctly differentiate all the different states.
     */
    private final FileBoolean needed = state("needed");

    /**
     * If the scanning process has run to the completion, we set to this true.
     */
    private final FileBoolean done = state("done");

    /**
     * If the rewrite process is scheduled upon the next boot.
     */
    private final FileBoolean scanOnBoot = state("scanOnBoot");

    @SuppressWarnings("OverridableMethodCallInConstructor") // should have been final
    public RekeySecretAdminMonitor() throws IOException {
        // if JENKINS_HOME existed <1.497, we need to offer rewrite
        // this computation needs to be done and the value be captured,
        // since $JENKINS_HOME/config.xml can be saved later before the user has
        // actually rewritten XML files.
        Jenkins j = Jenkins.getInstance();
        if (j.isUpgradedFromBefore(new VersionNumber("1.496.*"))
        &&  new FileBoolean(new File(j.getRootDir(),"secret.key.not-so-secret")).isOff())
            needed.on();
        Util.deleteRecursive(new File(getBaseDir(), "backups")); // SECURITY-376: no longer used
    }

    @Override
    public boolean isActivated() {
        return needed.isOn();
    }

    /**
     * Indicates that the re-keying has run to the completion.
     */
    public boolean isDone() {
        return done.isOn();
    }

    public void setNeeded() {
        needed.on();
    }

    public boolean isScanOnBoot() {
        return scanOnBoot.isOn();
    }

    @RequirePOST
    public HttpResponse doScan(StaplerRequest req) throws IOException, GeneralSecurityException {
        if(req.hasParameter("background")) {
            start(false);
        } else
        if(req.hasParameter("schedule")) {
            scanOnBoot.on();
        } else
        if(req.hasParameter("dismiss")) {
            disable(true);
        } else
            throw HttpResponses.error(400,"Invalid request submission: " + req.getParameterMap());

        return HttpResponses.redirectViaContextPath("/manage");
    }


    private FileBoolean state(String name) {
        return new FileBoolean(new File(getBaseDir(),name));
    }

    @Initializer(fatal=false,after=InitMilestone.PLUGINS_STARTED,before=InitMilestone.EXTENSIONS_AUGMENTED)
    // as early as possible, but this needs to be late enough that the ConfidentialStore is available
    public void scanOnReboot() throws InterruptedException, IOException, GeneralSecurityException {
        FileBoolean flag = scanOnBoot;
        if (flag.isOn()) {
            flag.off();
            start(false).join();
            // block the boot until the rewrite process is complete
            // don't let the failure in RekeyThread block Jenkins boot.
        }
    }

    @Override
    public String getDisplayName() {
        return Messages.RekeySecretAdminMonitor_DisplayName();
    }

    /**
     * Rewrite log file.
     */
    @Override
    protected File getLogFile() {
        return new File(getBaseDir(),"rekey.log");
    }

    @Override
    protected void fix(TaskListener listener) throws Exception {
        LOGGER.info("Initiating a re-keying of secrets. See "+getLogFile());

        SecretRewriter rewriter = new SecretRewriter();

        try {
            PrintStream log = listener.getLogger();
            log.println("Started re-keying " + new Date());
            int count = rewriter.rewriteRecursive(Jenkins.getInstance().getRootDir(), listener);
            log.printf("Completed re-keying %d files on %s\n",count,new Date());
            new RekeySecretAdminMonitor().done.on();
            LOGGER.info("Secret re-keying completed");
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Fatal failure in re-keying secrets",e);
            Functions.printStackTrace(e, listener.error("Fatal failure in rewriting secrets"));
        }
    }

    private static final Logger LOGGER = Logger.getLogger(RekeySecretAdminMonitor.class.getName());

}
