org.fcrepo.server.security.PolicyParser.java Source code

Java tutorial

Introduction

Here is the source code for org.fcrepo.server.security.PolicyParser.java

Source

/* The contents of this file are subject to the license and copyright terms
 * detailed in the license directory at the root of the source tree (also
 * available online at http://fedora-commons.org/license/).
 */
package org.fcrepo.server.security;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.SoftReferenceObjectPool;
import org.fcrepo.server.errors.ValidationException;
import org.fcrepo.utilities.XmlTransformUtility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.jboss.security.xacml.sunxacml.AbstractPolicy;
import org.jboss.security.xacml.sunxacml.ParsingException;
import org.jboss.security.xacml.sunxacml.Policy;
import org.jboss.security.xacml.sunxacml.PolicySet;

/**
 * A validating parser for XACML policies.
 * <p>
 * This class also provides a commandline XACML validation utility.
 * <p>
 * NOTE: Although instances may be re-used, this class is not thread-safe.
 * Use the <code>copy()</code> method to support concurrent parsing.
 */
public class PolicyParser {
    private static final Logger logger = LoggerFactory.getLogger(PolicyParser.class);
    private static final String W3C_XML_SCHEMA_NS_URI = "http://www.w3.org/2001/XMLSchema";

    // Neither of these factories are thread-safe, so access is synchronized in methods below
    private static final SchemaFactory SCHEMA_FACTORY = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI);

    private static final ErrorHandler THROW_ALL = new ThrowAllErrorHandler();

    private final SoftReferenceObjectPool<Validator> m_validators;

    /**
     * Creates an instance that will validate according to the given schema.
     *
     * @param schemaStream the XSD schema to use for schema validation
     * @throws IOException if the schema can't be read
     * @throws SAXException if the schema isn't valid
     */
    public PolicyParser(InputStream schemaStream) throws IOException, SAXException {
        this(getSchema(schemaStream));
    }

    // actual constructor keeps schema (which is thread safe) for cheap copying
    private PolicyParser(Schema schema) throws SAXException {
        m_validators = new SoftReferenceObjectPool<Validator>(new PoolableValidatorFactory(schema));
    }

    private PolicyParser(SoftReferenceObjectPool<Validator> validators) {
        m_validators = validators;
    }

    /**
     * Schema Factory is not thread safe
     * @return
     */
    private static Schema getSchema(InputStream schemaStream) throws SAXException {
        Schema result;
        synchronized (SCHEMA_FACTORY) {
            result = SCHEMA_FACTORY.newSchema(new StreamSource(schemaStream));
        }
        return result;
    }

    /**
     * Gets a new instance that uses the same schema as this one.
     *
     * @return a copy of this instance
     */
    public PolicyParser copy() {
        return new PolicyParser(m_validators);
    }

    /**
     * Parses the given policy and optionally schema validates it.
     *
     * @param policyStream
     *          the serialized XACML policy
     * @param validate
     *          whether to schema validate
     * @return the parsed policy.
     * @throws ValidationException
     *           if the given xml is not a valid policy. This will occur if it
     *           is not well-formed XML, its root element is not named
     *           <code>Policy</code> or <code>PolicySet</code>, it triggers
     *           a parse exception in the Sun libraries when constructing an
     *           <code>AbstractPolicy</code> from the DOM, or (if validation
     *           is true) it is not schema-valid.
     */
    public AbstractPolicy parse(InputStream policyStream, boolean schemaValidate) throws ValidationException {

        // Parse; die if not well-formed
        Document doc = null;
        DocumentBuilder domParser = null;
        try {
            domParser = XmlTransformUtility.borrowDocumentBuilder();
            domParser.setErrorHandler(THROW_ALL);
            doc = domParser.parse(policyStream);
        } catch (Exception e) {
            throw new ValidationException("Policy invalid; malformed XML", e);
        } finally {
            if (domParser != null) {
                XmlTransformUtility.returnDocumentBuilder(domParser);
            }
        }

        if (schemaValidate) {
            // XSD-validate; die if not schema-valid
            Validator validator = null;
            try {
                validator = m_validators.borrowObject();
                validator.validate(new DOMSource(doc));
            } catch (Exception e) {
                throw new ValidationException("Policy invalid; schema" + " validation failed", e);
            } finally {
                if (validator != null)
                    try {
                        m_validators.returnObject(validator);
                    } catch (Exception e) {
                        logger.warn(e.getMessage(), e);
                    }
            }
        }

        // Construct AbstractPolicy from doc; die if root isn't "Policy[Set]"
        Element root = doc.getDocumentElement();
        String rootName = root.getTagName();
        try {
            if (rootName.equals("Policy")) {
                return Policy.getInstance(root);
            } else if (rootName.equals("PolicySet")) {
                return PolicySet.getInstance(root);
            } else {
                throw new ValidationException(
                        "Policy invalid; root element is " + rootName + ", but should be " + "Policy or PolicySet");
            }
        } catch (ParsingException e) {
            throw new ValidationException("Policy invalid; failed parsing by " + "Sun XACML implementation", e);
        }
    }

    /**
     * Command-line utility for validating XACML policies.
     * <p>
     * Accepts a single argument: the path to the policy instance to validate.
     * <p>
     * Also requires that the com.sun.xacml.PolicySchema system property points
     * to the XACML schema.
     */
    public static void main(String[] args) {
        if (args.length != 1) {
            fail("One argument required: /path/to/xacml-policy-to-validate.xml");
        }
        final String schemaPathProperty = "com.sun.xacml.PolicySchema";
        String schemaPath = System.getProperty(schemaPathProperty);
        if (schemaPath == null) {
            fail("System property " + schemaPathProperty + " (path to XACML " + "schema) must be set. (e.g. -D"
                    + schemaPathProperty + "=/path/to/schema)");
        }
        try {
            InputStream instance = getStream(args[0]);
            PolicyParser parser = new PolicyParser(getStream(schemaPath));
            parser.parse(instance, true);
            System.out.println("Validation successful");
            System.exit(0);
        } catch (ValidationException e) {
            if (e.getCause() != null && e.getCause() instanceof SAXParseException) {
                fail(e.getCause().getMessage());
            } else {
                fail(e);
            }
        } catch (Exception e) {
            fail(e);
        }
    }

    private static InputStream getStream(String path) {
        try {
            return new FileInputStream(path);
        } catch (Exception e) {
            fail("File not found: " + path);
            return null;
        }
    }

    private static void fail(String message) {
        System.out.println("ERROR: " + message);
        System.out.println("Validation failed");
        System.exit(1);
    }

    private static void fail(Exception e) {
        e.printStackTrace();
        fail(e.getClass().getName() + ": See above for detail");
    }

    /**
     * This class is a workaround to some shift in the behavior of anonymous inner classes
     *
     */
    public static class ThrowAllErrorHandler implements ErrorHandler {
        public void error(SAXParseException e) throws SAXParseException {
            throw e;
        }

        public void fatalError(SAXParseException e) throws SAXParseException {
            throw e;
        }

        public void warning(SAXParseException e) throws SAXParseException {
            throw e;
        }
    }

    public static class PoolableValidatorFactory implements PoolableObjectFactory<Validator> {
        private final Schema m_schema;

        public PoolableValidatorFactory(Schema schema) {
            m_schema = schema;
        }

        @Override
        public Validator makeObject() throws Exception {
            return m_schema.newValidator();
        }

        @Override
        public void destroyObject(Validator obj) throws Exception {
            // no op
        }

        @Override
        public boolean validateObject(Validator obj) {
            // no op
            return true;
        }

        @Override
        public void activateObject(Validator obj) throws Exception {
            obj.reset();
        }

        @Override
        public void passivateObject(Validator obj) throws Exception {
            // no op
        }
    }
}