/* 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 fedora.server.security; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; 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 com.sun.xacml.AbstractPolicy; import com.sun.xacml.ParsingException; import com.sun.xacml.Policy; import com.sun.xacml.PolicySet; 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 fedora.common.FaultException; import fedora.server.errors.ValidationException; import fedora.server.utilities.StreamUtility; /** * 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 final byte[] m_schemaBytes; private final Validator m_validator; private final DocumentBuilder m_domParser; /** * 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(StreamUtility.getBytes(schemaStream)); } // actual constructor keeps schema bytes to enable cheap copying private PolicyParser(byte[] schemaBytes) throws SAXException { m_schemaBytes = schemaBytes; m_validator = createXSDValidator(new ByteArrayInputStream(m_schemaBytes)); m_domParser = createDOMParser(); } /** * Gets a new instance that uses the same schema as this one. * * @return a copy of this instance */ public PolicyParser copy() { try { return new PolicyParser(m_schemaBytes); } catch (SAXException wontHappen) { throw new FaultException(wontHappen); } } /** * 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; try { doc = m_domParser.parse(policyStream); } catch (Exception e) { throw new ValidationException("Policy invalid; malformed XML", e); } if (schemaValidate) { // XSD-validate; die if not schema-valid try { m_validator.validate(new DOMSource(doc)); } catch (Exception e) { throw new ValidationException("Policy invalid; schema" + " validation failed", 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); } } private static DocumentBuilder createDOMParser() { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setIgnoringComments(true); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setErrorHandler(new 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; } }); return builder; } catch (ParserConfigurationException e) { throw new FaultException(e); } } private static Validator createXSDValidator(InputStream schemaStream) throws SAXException { if (schemaStream == null) return null; SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = factory.newSchema(new StreamSource(schemaStream)); return schema.newValidator(); } /** * 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"); } }