jp.ikedam.jenkins.plugins.updatesitesmanager.ManagedUpdateSite.java Source code

Java tutorial

Introduction

Here is the source code for jp.ikedam.jenkins.plugins.updatesitesmanager.ManagedUpdateSite.java

Source

/*
 * The MIT License
 * 
 * Copyright (c) 2013 IKEDA Yasuyuki
 * 
 * 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 jp.ikedam.jenkins.plugins.updatesitesmanager;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.logging.Level;
import java.util.logging.Logger;

import jenkins.model.Jenkins;

import hudson.Extension;
import hudson.util.FormValidation;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

/**
 * Extended UpdateSite to be managed in UpdateSitesManager.
 * 
 * ManagedUpdateSite provides following features.
 * <ul>
 *   <li>can switch enabled/disabled.</li>
 *   <li>have a note field.</li>
 *   <li>can set a CA certificate for the signature of the site.</li>
 * </ul>
 * 
 * The CA certificate is written to a temporary file under ${JENKINS_HOME}/update-center-rootCAs/ .
 * I think it is a better implementation to override UpdateSite#verifySignature,
 * but it is private method.
 * Re-implementing doPostBack results to re-implement whole the UpdateSite.
 */
public class ManagedUpdateSite extends DescribedUpdateSite {
    private static final long serialVersionUID = -714713790690982048L;

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

    private String caCertificate;

    /**
     * Returns the CA certificate to verify the signature.
     * 
     * This is useful when the UpdateSite is signed with a self-signed private key.
     * 
     * @return the CA certificate
     */
    public String getCaCertificate() {
        return caCertificate;
    }

    /**
     * Set the CA certificate to verify the signature.
     * 
     * Mainly for testing Purpose.
     */
    public void setCaCertificate(String caCertificate) {
        this.caCertificate = caCertificate;
    }

    /**
     * Returns whether to use CA certificate.
     * 
     * @return whether to use CA certificate
     */
    public boolean isUseCaCertificate() {
        return getCaCertificate() != null;
    }

    private boolean disabled;

    /**
     * Returns whether this site is disabled.
     * 
     * When disabled, plugins in this site gets unavailable.
     * 
     * @return the whether this site is disabled
     */
    @Override
    public boolean isDisabled() {
        return disabled;
    }

    private String note;

    /**
     * Returns the note
     * 
     * Note is only used for the displaying purpose.
     * 
     * @return the note
     */
    @Override
    public String getNote() {
        return note;
    }

    /**
     * Returns the path to the CA certificate file.
     * 
     * The file to store the CA certificate temporary.
     * 
     * @return the path to the CA certificate file
     */
    protected File getCaCertificateFile() {
        File caCertificateDir = new File(Jenkins.getInstance().getRootDir(), "update-center-rootCAs");
        if (!caCertificateDir.exists()) {
            assert (caCertificateDir.mkdir());
        }

        return new File(caCertificateDir, String.format("tmp-ManageUpdateSite-%s.crt", getId()));
    }

    /**
     * Create a new instance
     * 
     * @param id
     * @param url
     * @param useCaCertificate
     * @param caCertificate
     * @param note
     * @param disabled
     */
    @DataBoundConstructor
    public ManagedUpdateSite(String id, String url, boolean useCaCertificate, String caCertificate, String note,
            boolean disabled) {
        super(id, url);
        this.caCertificate = useCaCertificate ? StringUtils.trim(caCertificate) : null;
        this.note = note;
        this.disabled = disabled;
    }

    /**
     * Process update-center.json.
     * 
     * If a CA certificate is set, write it to file and make it ready to verify the signature.
     * 
     * @param req
     * @return FormValidation object
     * @throws IOException
     * @throws GeneralSecurityException
     * @see hudson.model.UpdateSite#doPostBack(org.kohsuke.stapler.StaplerRequest)
     */
    @Override
    public FormValidation doPostBack(StaplerRequest req) throws IOException, GeneralSecurityException {
        if (isUseCaCertificate()) {
            return doPostBackWithCaCertificate(req);
        }

        return super.doPostBack(req);
    }

    /**
     * Uses custom CA certificate to process update-center.json.
     * 
     * To avoid having the certificate file overwritten by another thread,
     * this method is synchronized.
     * 
     * @param req
     * @return FormValidation object
     * @throws IOException
     * @throws GeneralSecurityException
     */
    private synchronized FormValidation doPostBackWithCaCertificate(StaplerRequest req)
            throws IOException, GeneralSecurityException {
        File caFile = getCaCertificateFile();
        try {
            FileUtils.writeStringToFile(getCaCertificateFile(), getCaCertificate());
            return super.doPostBack(req);
        } finally {
            if (caFile.exists()) {
                caFile.delete();
            }
        }
    }

    /**
     * Verify the signature of downloaded update-center.json.
     * 
     * @return FormValidation object.
     * @throws IOException
     * @see hudson.model.UpdateSite#doVerifySignature()
     */
    @Override
    public FormValidation doVerifySignature() throws IOException {
        if (isUseCaCertificate()) {
            return doVerifySignatureWithCaCertificate();
        }

        return super.doVerifySignature();
    }

    /**
     * Verify the signature with custom CA certificate.
     * 
     * To avoid having the certificate file overwritten by another thread,
     * this method is synchronized.
     * 
     * @return FormValidation object.
     * @throws IOException
     */
    private synchronized FormValidation doVerifySignatureWithCaCertificate() throws IOException {
        File caFile = getCaCertificateFile();
        try {
            FileUtils.writeStringToFile(getCaCertificateFile(), getCaCertificate());
            return super.doVerifySignature();
        } finally {
            if (caFile.exists()) {
                caFile.delete();
            }
        }
    }

    /**
     * Descriptor for this class.
     */
    @Extension
    static public class DescriptorImpl extends DescribedUpdateSite.Descriptor {
        /**
         * Returns the name of this UpdateSite.
         * 
         * shown when select UpdateSite to create.
         * 
         * @return
         * @see hudson.model.Descriptor#getDisplayName()
         */
        @Override
        public String getDisplayName() {
            return Messages.ManagedUpdateSite_DisplayName();
        }

        /**
         * Returns the description of this UpdateSite.
         * 
         * shown when select UpdateSite to create.
         * 
         * @return
         * @see jp.ikedam.jenkins.plugins.updatesitesmanager.DescribedUpdateSite.Descriptor#getDescription()
         */
        @Override
        public String getDescription() {
            return Messages.ManagedUpdateSite_Description();
        }

        /**
         * Returns whether the certificate is valid.
         * 
         * @return FormValidation object
         */
        public FormValidation doCheckCaCertificate(@QueryParameter boolean useCaCertificate,
                @QueryParameter String caCertificate) {
            if (!useCaCertificate) {
                return FormValidation.ok();
            }

            if (StringUtils.isBlank(caCertificate)) {
                return FormValidation.error(Messages.ManagedUpdateSite_caCertificate_required());
            }

            CertificateFactory cf = null;
            try {
                cf = CertificateFactory.getInstance("X509");
            } catch (CertificateException e) {
                LOGGER.log(Level.WARNING, "Failed to retrieve CertificateFactory for X509", e);
            }

            if (cf != null) {
                try {
                    cf.generateCertificate(
                            new ByteArrayInputStream(StringUtils.trim(caCertificate).getBytes("UTF-8")));
                } catch (CertificateException e) {
                    return FormValidation
                            .error(Messages.ManagedUpdateSite_caCertificate_invalid(e.getLocalizedMessage()));
                } catch (UnsupportedEncodingException e) {
                    LOGGER.log(Level.WARNING, "Failed to decode Certificate", e);
                }
            }

            return FormValidation.ok();
        }

    };
}