org.guanxi.idp.Bootstrap.java Source code

Java tutorial

Introduction

Here is the source code for org.guanxi.idp.Bootstrap.java

Source

//: "The contents of this file are subject to the Mozilla Public License
//: Version 1.1 (the "License"); you may not use this file except in
//: compliance with the License. You may obtain a copy of the License at
//: http://www.mozilla.org/MPL/
//:
//: Software distributed under the License is distributed on an "AS IS"
//: basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//: License for the specific language governing rights and limitations
//: under the License.
//:
//: The Original Code is Guanxi (http://www.guanxi.uhi.ac.uk).
//:
//: The Initial Developer of the Original Code is Alistair Young alistair@codebrane.com
//: All Rights Reserved.
//:

package org.guanxi.idp;

import org.springframework.context.ApplicationListener;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.support.RequestHandledEvent;
import org.guanxi.common.definitions.Guanxi;
import org.guanxi.common.GuanxiException;
import org.guanxi.common.entity.EntityFarm;
import org.guanxi.common.job.GuanxiJobConfig;
import org.guanxi.xal.idp.IdpDocument;
import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlOptions;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.openssl.PEMWriter;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import javax.servlet.ServletContext;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Random;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Date;
import java.io.*;
import java.math.BigInteger;
import java.text.ParseException;

public class Bootstrap implements ApplicationListener, ApplicationContextAware, ServletContextAware {
    private static final Logger logger = Logger.getLogger(Bootstrap.class.getName());
    /** Spring ApplicationContext */
    @SuppressWarnings("unused")
    private ApplicationContext applicationContext = null;
    /** The servlet context */
    private ServletContext servletContext = null;
    private String configFile = null;
    /** If this instance of an Engine loads the BouncyCastle security provider then it should unload it */
    private boolean okToUnloadBCProvider = false;
    /** The background jobs to start */
    private GuanxiJobConfig[] gxJobs = null;
    /** The MetadataFarm instance to use */
    private EntityFarm entityFarm = null;
    /** Our job scheduler */
    private Scheduler scheduler = null;

    /**
     * Initialise the interceptor
     */
    public void init() {
        try {
            /* If we try to add the BouncyCastle provider but another Guanxi::SP running
             * in another webapp in the same container has already done so, then we'll get
             * -1 returned from the method, in which case, we should leave unloading of the
             * provider to the particular Guanxi::SP that loaded it.
             */
            if ((Security.addProvider(new BouncyCastleProvider())) != -1) {
                // We've loaded it, so we should unload it
                okToUnloadBCProvider = true;
            }

            IdpDocument configDoc = IdpDocument.Factory.parse(new File(servletContext.getRealPath(configFile)));
            servletContext.setAttribute(Guanxi.CONTEXT_ATTR_IDP_CONFIG_DOC, configDoc);
            servletContext.setAttribute(Guanxi.CONTEXT_ATTR_IDP_CONFIG, configDoc.getIdp());

            // Sort out the cookie's age
            int cookieAge = -1;
            String cookieMaxAge = configDoc.getIdp().getCookie().getAge().getStringValue();
            String cookieAgeUnits = configDoc.getIdp().getCookie().getAge().getUnits().toString();
            if (cookieAgeUnits.equals("seconds"))
                cookieAge = Integer.parseInt(cookieMaxAge);
            else if (cookieAgeUnits.equals("minutes"))
                cookieAge = Integer.parseInt(cookieMaxAge) * 60;
            else if (cookieAgeUnits.equals("hours"))
                cookieAge = Integer.parseInt(cookieMaxAge) * 3600;
            else if (cookieAgeUnits.equals("days"))
                cookieAge = Integer.parseInt(cookieMaxAge) * 86400;
            else if (cookieAgeUnits.equals("weeks"))
                cookieAge = Integer.parseInt(cookieMaxAge) * 604800;
            else if (cookieAgeUnits.equals("months"))
                cookieAge = Integer.parseInt(cookieMaxAge) * 2419200;
            else if (cookieAgeUnits.equals("years"))
                cookieAge = Integer.parseInt(cookieMaxAge) * 29030400;
            else if (cookieAgeUnits.equals("transient"))
                cookieAge = -1;

            String cookieDomain = (configDoc.getIdp().getCookie().getDomain() == null) ? ""
                    : configDoc.getIdp().getCookie().getDomain();

            // Register the IdP's ID and cookie details in case we're embedded
            servletContext.setAttribute(Guanxi.CONTEXT_ATTR_IDP_ID, configDoc.getIdp().getID());
            servletContext.setAttribute(Guanxi.CONTEXT_ATTR_IDP_COOKIE_PREFIX,
                    configDoc.getIdp().getCookie().getPrefix());
            servletContext.setAttribute(Guanxi.CONTEXT_ATTR_IDP_COOKIE_NAME,
                    configDoc.getIdp().getCookie().getPrefix() + configDoc.getIdp().getID());
            servletContext.setAttribute(Guanxi.CONTEXT_ATTR_IDP_COOKIE_DOMAIN, cookieDomain);
            servletContext.setAttribute(Guanxi.CONTEXT_ATTR_IDP_COOKIE_AGE, new Integer(cookieAge));

            setup();

            startJobs();
        } catch (Exception e) {
        }
    }

    /**
     * Called by Spring to give us the ApplicationContext
     *
     * @param applicationContext Spring ApplicationContext
     * @throws org.springframework.beans.BeansException
     */
    public void setApplicationContext(ApplicationContext applicationContext)
            throws org.springframework.beans.BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * Sets the servlet context
     * @param servletContext The servlet context
     */
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    /**
     * Called by Spring when application events occur. At the moment we handle:
     * ContextClosedEvent
     * ContextRefreshedEvent
     * RequestHandledEvent
     *
     * This is where we inject the job controllers into the application context, each one
     * under it's own key.
     *
     * @param applicationEvent Spring application event
     */
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        if (applicationEvent instanceof ContextRefreshedEvent) {
            logger.info("Bootstrap init");

            // Inject the metadata farm to handle all source of metadata
            servletContext.setAttribute(Guanxi.CONTEXT_ATTR_IDP_ENTITY_FARM, entityFarm);
        }

        if (applicationEvent instanceof ContextClosedEvent) {
            if (okToUnloadBCProvider) {
                Provider[] providers = Security.getProviders();

                /* Although addProvider() returns the ID of the newly installed provider,
                 * we can't rely on this. If another webapp removes a provider from the list of
                 * installed providers, all the other providers shuffle up the list by one, thus
                 * invalidating the ID we got from addProvider().
                 */
                try {
                    for (int i = 0; i < providers.length; i++) {
                        if (providers[i].getName().equalsIgnoreCase(Guanxi.BOUNCY_CASTLE_PROVIDER_NAME)) {
                            Security.removeProvider(Guanxi.BOUNCY_CASTLE_PROVIDER_NAME);
                        }
                    }

                    // Stop the jobs
                    scheduler.shutdown();
                } catch (SecurityException se) {
                    /* We'll end up here if a security manager is installed and it refuses us
                     * permission to remove the BouncyCastle provider
                     */
                } catch (SchedulerException se) {
                    logger.error("Could not stop jobs", se);
                }
            }
        }

        if (applicationEvent instanceof RequestHandledEvent) {
        }
    }

    private void setup() throws GuanxiException {
        String KEYSTORE_FILE = "/WEB-INF/guanxi_idp/keystore/guanxi_idp.jks";
        String KEYSTORE_TYPE = "jks";
        String KEYSTORE_PRIVATE_KEY_ALIAS = "idp";

        if (!keystoreExists(servletContext.getRealPath(KEYSTORE_FILE))) {
            Random randomNumberGenerator = new Random();
            String certCN = getCNPrefix() + String.valueOf(randomNumberGenerator.nextInt());
            String keystorePassword = String.valueOf(randomNumberGenerator.nextInt());

            /* Create the keystore. Note that the keystore password and key entry password
             * must be the same for Tomcat to load the keystore
             */
            try {
                if (createSelfSignedKeystore(certCN, servletContext.getRealPath(KEYSTORE_FILE), keystorePassword,
                        keystorePassword, KEYSTORE_PRIVATE_KEY_ALIAS)) {
                    createConfigFile(certCN, certCN, KEYSTORE_TYPE, servletContext.getRealPath(KEYSTORE_FILE),
                            keystorePassword, KEYSTORE_PRIVATE_KEY_ALIAS, keystorePassword,
                            KEYSTORE_PRIVATE_KEY_ALIAS);
                }
            } catch (IOException ioe) {
                throw new GuanxiException(ioe);
            }
        }
    }

    private String getCNPrefix() {
        // Are we running inside Bodington?
        File markerFile = new File(servletContext.getRealPath("WEB-INF/bodington.properties"));
        if (markerFile.exists())
            return "BODGUANXI-";
        else
            return "GUANXI-";
    }

    private boolean keystoreExists(String keystoreFile) {
        return new File(keystoreFile).exists();
    }

    public boolean createSelfSignedKeystore(String cn, String keystoreFile, String keystorePassword,
            String privateKeyPassword, String privateKeyAlias) {
        KeyStore ks = null;

        try {
            ks = KeyStore.getInstance("JKS");
            ks.load(null, null);

            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
            keyGen.initialize(1024, new SecureRandom());
            KeyPair keypair = keyGen.generateKeyPair();
            PrivateKey privkey = keypair.getPrivate();
            PublicKey pubkey = keypair.getPublic();

            Hashtable<DERObjectIdentifier, String> attrs = new Hashtable<DERObjectIdentifier, String>();
            Vector<DERObjectIdentifier> ordering = new Vector<DERObjectIdentifier>();
            ordering.add(X509Name.CN);
            attrs.put(X509Name.CN, cn);
            X509Name issuerDN = new X509Name(ordering, attrs);
            X509Name subjectDN = new X509Name(ordering, attrs);

            Date validFrom = new Date();
            validFrom.setTime(validFrom.getTime() - (10 * 60 * 1000));
            Date validTo = new Date();
            validTo.setTime(validTo.getTime() + (20 * (24 * 60 * 60 * 1000)));

            X509V3CertificateGenerator x509 = new X509V3CertificateGenerator();
            x509.setSignatureAlgorithm("SHA1withDSA");
            x509.setIssuerDN(issuerDN);
            x509.setSubjectDN(subjectDN);
            x509.setPublicKey(pubkey);
            x509.setNotBefore(validFrom);
            x509.setNotAfter(validTo);
            x509.setSerialNumber(new BigInteger(128, new Random()));

            X509Certificate[] cert = new X509Certificate[1];
            cert[0] = x509.generate(privkey, "BC");
            java.security.cert.Certificate[] chain = new java.security.cert.Certificate[1];
            chain[0] = cert[0];

            ks.setKeyEntry(privateKeyAlias, privkey, privateKeyPassword.toCharArray(), cert);
            ks.setKeyEntry(privateKeyAlias, privkey, privateKeyPassword.toCharArray(), chain);
            ks.store(new FileOutputStream(keystoreFile), keystorePassword.toCharArray());

            String IDP_RFC_CERT = "WEB-INF/guanxi_idp/keystore/guanxi_idp_cert.txt";

            PEMWriter pemWriter = new PEMWriter(new FileWriter(servletContext.getRealPath(IDP_RFC_CERT)));
            pemWriter.writeObject(cert[0]);
            pemWriter.close();

            return true;
        } catch (Exception se) {
            return false;
        }
    }

    private void createConfigFile(String issuer, String nameQualifier, String ksType, String ksFile,
            String ksPassword, String privKeyAlias, String privKeyPassword, String certAlias) throws IOException {

        String SSO_CONFIG_FILE = "/WEB-INF/guanxi_idp/config/idp.xml";
        String KEYSTORE_KEY_TYPE = "dsa";

        IdpDocument idpDoc = null;
        try {
            idpDoc = IdpDocument.Factory.parse(new File(servletContext.getRealPath(SSO_CONFIG_FILE)));
        } catch (XmlException xe) {
            logger.error("Can't create config file", xe);
            return;
        }

        IdpDocument.Idp idp = idpDoc.getIdp();

        idp.getServiceProviderArray(0).setIdentity("exampleIdentity");
        idp.getServiceProviderArray(0).setCreds("exampleCreds");
        idp.getServiceProviderArray(0).setName("REPLACE_WITH_PROVIDER_ID_OF_SERVICE_PROVIDER");

        idp.getIdentityArray(0).setName("exampleIdentity");
        idp.getIdentityArray(0).setNameQualifier(nameQualifier);
        idp.getIdentityArray(0).setIssuer(issuer);

        idp.getCredsArray(0).setName("exampleCreds");
        idp.getCredsArray(0).setKeystoreType("jks");
        idp.getCredsArray(0).setKeyType(ksType);
        idp.getCredsArray(0).setKeystoreFile(ksFile);
        idp.getCredsArray(0).setKeystorePassword(ksPassword);
        idp.getCredsArray(0).setPrivateKeyAlias(privKeyAlias);
        idp.getCredsArray(0).setPrivateKeyPassword(privKeyPassword);
        idp.getCredsArray(0).setCertificateAlias(certAlias);
        idp.getCredsArray(0).setKeyType(KEYSTORE_KEY_TYPE);

        XmlOptions xmlOptions = new XmlOptions();
        xmlOptions.setSavePrettyPrint();
        xmlOptions.setSavePrettyPrintIndent(2);
        xmlOptions.setUseDefaultNamespace();

        idpDoc.save(new File(servletContext.getRealPath(SSO_CONFIG_FILE)), xmlOptions);

        servletContext.setAttribute(Guanxi.CONTEXT_ATTR_IDP_CONFIG_DOC, idpDoc);
        servletContext.setAttribute(Guanxi.CONTEXT_ATTR_IDP_CONFIG, idpDoc.getIdp());
    }

    private void startJobs() {
        try {
            // Get a new scheduler
            scheduler = new StdSchedulerFactory().getScheduler();
            // Start it up. This won't start any jobs though.
            scheduler.start();

            for (GuanxiJobConfig gxJob : gxJobs) {
                // Need a new JobDetail to hold custom data to send to the job we're controlling
                JobDetail jobDetail = new JobDetail(gxJob.getKey(), Scheduler.DEFAULT_GROUP,
                        Class.forName(gxJob.getJobClass()));

                // Create a new JobDataMap for custom data to be sent to the job...
                JobDataMap jobDataMap = new JobDataMap();
                // ...and add the job's custom config object
                jobDataMap.put(GuanxiJobConfig.JOB_KEY_JOB_CONFIG, gxJob);

                // Put the job's custom data in it's JobDetail
                jobDetail.setJobDataMap(jobDataMap);

                /* Tell the scheduler when this job will run. Nothing will happen
                 * until the start method is called.
                 */
                Trigger trigger = new CronTrigger(gxJob.getKey(), Scheduler.DEFAULT_GROUP, gxJob.getCronLine());

                // Start the job
                scheduler.scheduleJob(jobDetail, trigger);

                if (gxJob.isStartImmediately()) {
                    scheduler.triggerJob(gxJob.getKey(), Scheduler.DEFAULT_GROUP);
                }
            }
        } catch (ClassNotFoundException cnfe) {
            logger.error("Error locating job class", cnfe);
        } catch (SchedulerException se) {
            logger.error("Job scheduling error", se);
        } catch (ParseException pe) {
            logger.error("Error parsing job cronline", pe);
        }
    }

    public void setConfigFile(String configFile) {
        this.configFile = configFile;
    }

    public String getConfigFile() {
        return configFile;
    }

    public void setGxJobs(GuanxiJobConfig[] gxJobs) {
        this.gxJobs = gxJobs;
    }

    public EntityFarm getEntityFarm() {
        return entityFarm;
    }

    public void setEntityFarm(EntityFarm entityFarm) {
        this.entityFarm = entityFarm;
    }
}