at.gv.egiz.slbinding.SLUnmarshaller.java Source code

Java tutorial

Introduction

Here is the source code for at.gv.egiz.slbinding.SLUnmarshaller.java

Source

/*
 * Copyright 2011 by Graz University of Technology, Austria
 * MOCCA has been developed by the E-Government Innovation Center EGIZ, a joint
 * initiative of the Federal Chancellery Austria and Graz University of Technology.
 *
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
 * the European Commission - subsequent versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 * http://www.osor.eu/eupl/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and
 * limitations under the Licence.
 *
 * This product combines work with different licenses. See the "NOTICE" text
 * file for details on the various modules and licenses.
 * The "NOTICE" text file is part of the distribution. Any derivative works
 * that you distribute must include a readable copy of the "NOTICE" text file.
 */

package at.gv.egiz.slbinding;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import at.gv.egiz.bku.utils.ClasspathURLStreamHandler;
import at.gv.egiz.dom.DOMUtils;
import at.gv.egiz.validation.ReportingValidationEventHandler;

public class SLUnmarshaller {

    /**
     * Logging facility.
     */
    private final Logger log = LoggerFactory.getLogger(SLUnmarshaller.class);

    private static class DefaultSchema {

        /**
         * Schema files required for Security Layer command validation.
         */
        public static final String[] SCHEMA_FILES = new String[] { "classpath:at/gv/egiz/bku/slschema/xml.xsd",
                "classpath:at/gv/egiz/bku/slschema/xmldsig-core-schema.xsd",
                "classpath:at/gv/egiz/bku/slschema/Core-1.2.xsd",
                "classpath:at/gv/egiz/bku/slschema/Core.20020225.xsd",
                "classpath:at/gv/egiz/bku/slschema/Core.20020831.xsd" };

        private static final Schema SCHEMA;

        static {
            try {
                SCHEMA = createSchema(Arrays.asList(SCHEMA_FILES));
            } catch (IOException e) {
                Logger log = LoggerFactory.getLogger(SLUnmarshaller.class);
                log.error("Failed to load security layer schema.", e);
                throw new RuntimeException(e);
            } catch (SAXException e) {
                Logger log = LoggerFactory.getLogger(SLUnmarshaller.class);
                log.error("Failed to load security layer schema.", e);
                throw new RuntimeException(e);
            }

        }
    }

    public static Collection<String> getDefaultSchemaUrls() {
        return Collections.unmodifiableList(Arrays.asList(DefaultSchema.SCHEMA_FILES));
    }

    private static Schema createSchema(Collection<String> schemaUrls) throws SAXException, IOException {
        Logger log = LoggerFactory.getLogger(SLUnmarshaller.class);
        Source[] sources = new Source[schemaUrls.size()];
        Iterator<String> urls = schemaUrls.iterator();
        StringBuilder sb = null;
        if (log.isDebugEnabled()) {
            sb = new StringBuilder();
            sb.append("Created schema using URLs: ");
        }
        for (int i = 0; i < sources.length && urls.hasNext(); i++) {
            String url = urls.next();
            if (url != null && url.startsWith("classpath:")) {
                URL schemaUrl = new URL(null, url, new ClasspathURLStreamHandler());
                sources[i] = new StreamSource(schemaUrl.openStream());
            } else {
                sources[i] = new StreamSource(url);
            }
            if (sb != null) {
                sb.append(url);
                if (urls.hasNext()) {
                    sb.append(", ");
                }
            }
        }
        SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = schemaFactory.newSchema(sources);
        if (sb != null) {
            log.debug(sb.toString());
        }
        return schema;
    }

    private static class DefaultContext {

        private static final String[] packageNames = {
                at.buergerkarte.namespaces.securitylayer._1_2_3.ObjectFactory.class.getPackage().getName(),
                org.w3._2000._09.xmldsig_.ObjectFactory.class.getPackage().getName(),
                at.buergerkarte.namespaces.cardchannel.ObjectFactory.class.getPackage().getName(),
                at.buergerkarte.namespaces.securitylayer._20020225_.ObjectFactory.class.getPackage().getName(),
                at.buergerkarte.namespaces.securitylayer._20020831_.ObjectFactory.class.getPackage().getName() };

        private static final JAXBContext CONTEXT;

        static {
            try {
                CONTEXT = createJAXBContext(Arrays.asList(packageNames));
            } catch (JAXBException e) {
                Logger log = LoggerFactory.getLogger(SLUnmarshaller.class);
                log.error("Failed to setup JAXBContext security layer request/response.", e);
                throw new RuntimeException(e);
            }
        }

    }

    public static Collection<String> getDefaultJAXBContextPackageNames() {
        return Collections.unmodifiableList(Arrays.asList(DefaultContext.packageNames));
    }

    private static JAXBContext createJAXBContext(Collection<String> packageNames) throws JAXBException {
        StringBuilder contextPath = new StringBuilder();
        for (String pkg : packageNames) {
            if (contextPath.length() > 0) {
                contextPath.append(':');
            }
            contextPath.append(pkg);
        }
        return JAXBContext.newInstance(contextPath.toString());
    }

    /**
     * Schema for Security Layer command validation.
     */
    protected Schema slSchema = DefaultSchema.SCHEMA;

    /**
     * The JAXBContext.
     */
    protected JAXBContext jaxbContext = DefaultContext.CONTEXT;

    /**
     * Returns the schema used for validation.
     * 
     * @return the slSchema
     */
    public Schema getSlSchema() {
        return slSchema;
    }

    /**
     * Sets the schema for validation.
     * 
     * @param slSchema the slSchema to set
     */
    public void setSlSchema(Schema slSchema) {
        this.slSchema = slSchema;
    }

    /**
     * Sets the schema created from the given {@code schemaUrls}.
     * 
     * @param schemaUrls a collection of URLs of schema files (supports {@code classpath:} URLs)
     * @throws SAXException if schema creation fails
     * @throws IOException if an error occurs upon dereferencing the given {@code schemaUrls}
     */
    public void setSchemaUrls(Collection<String> schemaUrls) throws SAXException, IOException {
        slSchema = createSchema(schemaUrls);
    }

    /**
     * @return the jaxbContext
     */
    public JAXBContext getJaxbContext() {
        return jaxbContext;
    }

    /**
     * @param jaxbContext the jaxbContext to set
     */
    public void setJaxbContext(JAXBContext jaxbContext) {
        this.jaxbContext = jaxbContext;
    }

    /**
     * Sets the JAXBContext for unmarshalling using the given {@code packageNames}.
     * 
     * @param packageNames a collection of java package names
     * @throws JAXBException if creating the JAXBContext with the given {@code packageNames} fails
     */
    public void setJaxbContextPackageNames(Collection<String> packageNames) throws JAXBException {
        this.jaxbContext = createJAXBContext(packageNames);
    }

    /**
     * @param source a StreamSource wrapping a Reader (!) for the marshalled Object
     * @return the unmarshalled Object
     * @throws XMLStreamException
     * @throws JAXBException
     */
    public Object unmarshal(StreamSource source) throws XMLStreamException, JAXBException {
        Reader inputReader = source.getReader();

        /* Validate XML against XXE, XEE, and SSRF
         * 
         * This pre-validation step is required because com.sun.xml.stream.sjsxp-1.0.2 XML stream parser library does not 
         * support all XML parser features to prevent these types of attacks  
         */
        if (inputReader instanceof InputStreamReader) {
            try {
                //create copy of input stream
                InputStreamReader isReader = (InputStreamReader) inputReader;
                String encoding = isReader.getEncoding();
                byte[] backup = IOUtils.toByteArray(isReader, encoding);

                //validate input stream
                DOMUtils.validateXMLAgainstXXEAndSSRFAttacks(new ByteArrayInputStream(backup));

                //create new inputStreamReader for reak processing
                inputReader = new InputStreamReader(new ByteArrayInputStream(backup), encoding);

            } catch (XMLStreamException e) {
                log.error("XML data validation FAILED with msg: " + e.getMessage(), e);
                throw new XMLStreamException("XML data validation FAILED with msg: " + e.getMessage(), e);

            } catch (IOException e) {
                log.error("XML data validation FAILED with msg: " + e.getMessage(), e);
                throw new XMLStreamException("XML data validation FAILED with msg: " + e.getMessage(), e);

            }

        } else {
            log.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
            log.error(
                    "Reader is not of type InputStreamReader -> can not make a copy of the InputStream --> extended XML validation is not possible!!! ");
            log.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");

        }

        /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
         * parse XML with original functionality
         * 
         * This code implements the the original mocca XML processing by using 
         *  com.sun.xml.stream.sjsxp-1.0.2 XML stream parser library. Currently, this library is required to get full 
         *  security-layer specific XML processing. However, this lib does not fully support XXE, XEE and SSRF
         *  prevention mechanisms (e.g.: XMLInputFactory.SUPPORT_DTD flag is not used)    
         * 
         * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
         */
        XMLInputFactory inputFactory = XMLInputFactory.newInstance();

        //disallow DTD and external entities
        inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
        inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false);

        XMLEventReader eventReader = inputFactory.createXMLEventReader(inputReader);
        RedirectEventFilter redirectEventFilter = new RedirectEventFilter();
        XMLEventReader filteredReader = inputFactory.createFilteredReader(eventReader, redirectEventFilter);

        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        ReportingValidationEventHandler validationEventHandler = new ReportingValidationEventHandler();
        unmarshaller.setEventHandler(validationEventHandler);

        unmarshaller.setListener(new RedirectUnmarshallerListener(redirectEventFilter));
        unmarshaller.setSchema(slSchema);

        Object object;
        try {
            log.trace("Before unmarshal().");
            object = unmarshaller.unmarshal(filteredReader);
            log.trace("After unmarshal().");
        } catch (UnmarshalException e) {
            if (log.isDebugEnabled()) {
                log.debug("Failed to unmarshal security layer message.", e);
            } else {
                log.info("Failed to unmarshal security layer message."
                        + (e.getMessage() != null ? " " + e.getMessage() : ""));
            }

            if (validationEventHandler.getErrorEvent() != null) {
                ValidationEvent errorEvent = validationEventHandler.getErrorEvent();
                if (e.getLinkedException() == null) {
                    e.setLinkedException(errorEvent.getLinkedException());
                }
            }
            throw e;
        }

        return object;

    }

}