package edu.internet2.middleware.shibboleth.serviceprovider;
-import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.TreeMap;
import org.apache.log4j.Logger;
-import org.apache.xerces.parsers.DOMParser;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlOptions;
import org.opensaml.SAMLAssertion;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
import x0.maceShibboleth1.AttributeAcceptancePolicyDocument;
import x0.maceShibbolethTargetConfig1.ApplicationDocument;
import edu.internet2.middleware.shibboleth.common.AAP;
import edu.internet2.middleware.shibboleth.common.AttributeRule;
import edu.internet2.middleware.shibboleth.common.Credentials;
-import edu.internet2.middleware.shibboleth.common.ShibResource;
import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
import edu.internet2.middleware.shibboleth.common.XML;
import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
import edu.internet2.middleware.shibboleth.metadata.Metadata;
import edu.internet2.middleware.shibboleth.metadata.Provider;
import edu.internet2.middleware.shibboleth.metadata.ProviderRole;
+import edu.internet2.middleware.shibboleth.xml.Parser;
/**
* Load the configuration files into objects, index them, and return them on request.
- private DOMParser parser = new DOMParser();
/**
* The constructor prepares for, but does not parse the configuration.
* @throws ShibbolethConfigurationException
* if XML Parser cannot be initialized (Classpath problem)
*/
- public ServiceProviderConfig()
- throws ShibbolethConfigurationException {
- try {
- parser.setFeature("http://xml.org/sax/features/validation", true);
- parser.setFeature("http://apache.org/xml/features/validation/schema", true);
-
-
- parser.setErrorHandler(new ErrorHandler() {
-
- public void error(SAXParseException arg0) throws SAXException {
- throw new SAXException("Error parsing xml file: " + arg0);
- }
-
- public void fatalError(SAXParseException arg0) throws SAXException {
- throw new SAXException("Error parsing xml file: " + arg0);
- }
-
- public void warning(SAXParseException arg0) throws SAXException {
- throw new SAXException("Error parsing xml file: " + arg0);
- }
- });
-
- } catch (SAXException e) {
- log.error("Unable to setup a workable XML parser: " + e);
- throw new ShibbolethConfigurationException("Unable to setup a workable XML parser.");
- }
+ public ServiceProviderConfig() {
}
/**
}
Document configDoc;
- try {
- configDoc = loadDom(configFilePath, MAINSCHEMA);
- } catch (InternalConfigurationException e) {
+ configDoc = Parser.loadDom(configFilePath, true);
+ if (configDoc==null) {
throw new ShibbolethConfigurationException("XML error in "+configFilePath);
}
loadConfigBean(configDoc);
schemaname=tempname;
try {
- Document extdoc = loadDom(uri,schemaname);
+ Document extdoc = Parser.loadDom(uri,true);
+ if (extdoc==null)
+ return null;
impl.initialize(extdoc);
} catch (Exception e) {
log.error("XML error " + e);
uri.startsWith(INLINEURN))
return false;
try {
- Document sitedoc = loadDom(uri,METADATASCHEMA);
+ Document sitedoc = Parser.loadDom(uri,true);
+ if (sitedoc==null)
+ return false;
XMLMetadataImpl impl = new XMLMetadataImpl();
impl.initialize(sitedoc);
addOrReplaceMetadataImplementor(uri,impl);
uri.startsWith(INLINEURN))
return false;
try {
- Document aapdoc = loadDom(uri,AAPSCHEMA);
+ Document aapdoc = Parser.loadDom(uri,true);
+ if (aapdoc==null)
+ return false;
AttributeAcceptancePolicyDocument aap = AttributeAcceptancePolicyDocument.Factory.parse(aapdoc);
XMLAAPImpl impl = new XMLAAPImpl();
impl.initialize(aapdoc);
uri.startsWith(INLINEURN))
return false;
try {
- Document trustdoc = loadDom(uri,TRUSTSCHEMA);
+ Document trustdoc = Parser.loadDom(uri,true);
+ if (trustdoc==null)
+ return false;
XMLTrustImpl impl = new XMLTrustImpl();
impl.initialize(trustdoc);
addOrReplaceTrustImplementor(uri,impl);
} else { // external file
try {
- mapdoc = loadDom(uri,METADATASCHEMA);
+ mapdoc = Parser.loadDom(uri,true);
+ if (mapdoc==null)
+ return true;
requestMapDoc = RequestMapDocument.Factory.parse(mapdoc);
} catch (Exception e) {
log.error("Error while parsing RequestMap file "+uri);
- /**
- * Given a file URI, parse it (validated against a schema) to a DOM.
- *
- * <p>Note: The exact format of the URI (absolute file path, resource
- * path, URL) is interpreted here. Changes to URI conventions can be
- * implemented here and apply throughout the configuration system.</p>
- *
- * @param configFilePath Path to the particular configuration file
- * @param schemaFilePath Path to the schema for this type of file
- * @return DOM Document object created from the file
- * @throws ShibbolethConfigurationException if file not found or invalid
- */
- private Document loadDom(String configFilePath, String schemaFilePath)
- throws InternalConfigurationException {
-
- log.debug("Loading Configuration from (" + configFilePath + ").");
-
- InputSource insrc;
- String schemaCannonicalFilePath;
- ShibResource configResource;
- ShibResource schemaResource;
- try {
- configResource = new ShibResource(configFilePath,this.getClass());
- insrc = new InputSource(configResource.getInputStream());
- } catch (Exception e1) {
- log.error("Configuration file "+configFilePath+" could not be located.");
- throw new InternalConfigurationException();
- }
-
- try {
- schemaResource = new ShibResource(schemaFilePath,this.getClass());
- schemaCannonicalFilePath = schemaResource.getFile().getCanonicalPath();
- } catch (Exception e) {
- log.error("Schema file "+schemaFilePath+" could not be located.");
- throw new InternalConfigurationException();
- }
-
-
- return loadDom(insrc,schemaCannonicalFilePath);
-
- }
-
-
- /**
- * Parse an InputSource (maybe file, maybe buffer) into a DOM
- * @param insrc XML InputSource object
- * @param schemaFilePath
- * @return DOM Document
- * @throws ShibbolethConfigurationException
- */
- private Document loadDom(InputSource insrc, String schemaFilePath)
- throws InternalConfigurationException {
-
- try {
- parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource",
- schemaFilePath);
-
- parser.parse(insrc);
-
- } catch (SAXException e) {
- log.error("Error while parsing shibboleth configuration: " + e);
- throw new InternalConfigurationException();
- } catch (IOException e) {
- log.error("Could not load shibboleth configuration file: " + e);
- throw new InternalConfigurationException();
- }
-
- return parser.getDocument();
-
- }
-
-
-
-
private int inlinenum = 1;
private String genDummyUri() {
return INLINEURN+Integer.toString(inlinenum++);
--- /dev/null
+/*
+ * Parser.java
+ *
+ * Validating and non-validating XML parsing using JAXP 1.3.
+ *
+ * Previous versions of the code directly used the Xerces DOMParser
+ * class. This class has been hidden in the Sun XML stack, and the
+ * public interface is to use DocumentBuilderFactory. This module
+ * requires the DOM 3 and JAXP 1.3 support built into J2SE 5.0 and
+ * distributed separately for earlier releases of Java from
+ * https://jaxp.dev.java.net/. It should also work with Xerces 2.7.0
+ * when that release becomes available.
+ *
+ * The org.opensaml.XML class already has most of the parsing code,
+ * but it uses a subset of the required Schemas. Here we build a
+ * wider Schema object, set it as the default SAML schema (because
+ * some Shibboleth namespace fields appear in SAML statements), and
+ * demand that Schema for every parser (DocumentBuilder) we request.
+ *
+ * Currently, this class exposes static methods. Should a real
+ * framework be installed, it would become a singleton object.
+ */
+package edu.internet2.middleware.shibboleth.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.validation.Schema;
+
+import org.apache.log4j.Logger;
+import org.opensaml.SAMLException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * Obtain schema validating and non-validating XML parsers.
+ *
+ * @author Howard Gilbert
+ */
+public class Parser {
+ private static Logger log = Logger.getLogger(Parser.class);
+
+
+ /**
+ * All the namespaces used by any part of Shibboleth
+ *
+ * Note: The current Schema compiler requires that dependencies
+ * (imports) be listed before the namespace of the schema
+ * that imports them.
+ */
+ private static String[] namespaces = new String[]{
+ "http://www.w3.org/2000/09/xmldsig#",
+ "http://www.w3.org/2001/04/xmlenc#",
+ "urn:oasis:names:tc:SAML:1.0:assertion",
+ "urn:oasis:names:tc:SAML:2.0:assertion",
+ "http://www.w3.org/XML/1998/namespace",
+ "http://schemas.xmlsoap.org/soap/envelope/",
+ "urn:mace:shibboleth:credentials:1.0",
+ "urn:oasis:names:tc:SAML:1.0:protocol",
+ "urn:oasis:names:tc:SAML:2.0:protocol",
+ "urn:mace:shibboleth:namemapper:1.0",
+ "urn:mace:shibboleth:origin:1.0",
+ "urn:mace:shibboleth:arp:1.0",
+ "urn:mace:shibboleth:resolver:1.0",
+ "urn:mace:shibboleth:target:config:1.0",
+ "urn:mace:shibboleth:trust:1.0",
+ "urn:mace:shibboleth:1.0",
+ "http://schemas.xmlsoap.org/soap/envelope/",
+ "urn:oasis:names:tc:SAML:2.0:metadata",
+ "http://www.w3.org/2002/06/xmldsig-filter2"
+ };
+
+ // If there were a real Framework here (like Spring) then
+ // the schemaBuilder would be inserted
+ private static Schemas schemaBuilder = new SchemasDirectoryImpl();
+ private static Schema schema = schemaBuilder.compileSchema(namespaces);
+
+ /**
+ * Load a DOM from a wrapped byte stream.
+ *
+ * @param ins InputSource The XML document
+ * @param validate If true, use Schema. Otherwise, its raw XML.
+ * @return A DOM 3 tree
+ */
+ public static Document loadDom(InputSource ins, boolean validate) {
+ Document doc=null;
+ log.info("Loading XML from (" + ins.getSystemId() + ")"
+ + (validate?" with Schema validation":"")
+ );
+ try {
+ if (validate)
+ doc = org.opensaml.XML.parserPool.parse(ins,schema);
+ else
+ doc = org.opensaml.XML.parserPool.parse(ins,null);
+
+ } catch (SAXException e) {
+ log.error("Error in XML configuration file"+e);
+ return null;
+ } catch (IOException e) {
+ log.error("Error accessing XML configuration file"+e);
+ return null;
+ } catch (SAMLException e) {
+ log.error("Error with XML parser "+e);
+ return null;
+ }
+
+ return doc;
+ }
+
+
+ /**
+ * A dummy class that pretends to be an old Xerces DOMParser
+ * to simplify conversion of existing code.
+ */
+ public static class DOMParser {
+ Document doc = null;
+ boolean validate = false;
+
+ public DOMParser(boolean validate) {
+ this.validate=validate;
+ }
+
+ public Document parse(InputSource ins) throws SAXException, IOException {
+ doc = loadDom(ins,true);
+ return doc;
+ }
+
+ public Document getDocument() {
+ return doc;
+ }
+ }
+
+ /**
+ * Write a DOM out to a character stream (for debugging and logging)
+ *
+ * @param dom The DOM tree to write
+ * @return A string containing the XML in character form.
+ */
+ public static String serialize(Node dom) {
+ String ret = null;
+
+ TransformerFactory factory = TransformerFactory.newInstance();
+ Transformer transformer = null;
+ DOMSource source = new DOMSource(dom);
+ try {
+ transformer = factory.newTransformer();
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ } catch (TransformerConfigurationException e) {
+ return null;
+ }
+ StringWriter stringWriter = new StringWriter();
+ StreamResult result = new StreamResult(stringWriter);
+ try {
+ transformer.transform(source, result);
+ } catch (TransformerException e1) {
+ return null;
+ }
+ return stringWriter.toString();
+ }
+
+ /**
+ * Version of loadDom where the file is specified as a resource name
+ *
+ * @param configFilePath input resource
+ * @param validate if true, use Schema
+ * @return DOM tree
+ */
+ public static Document loadDom(String configFilePath,boolean validate)
+ {
+ InputSource insrc;
+ String schemaCannonicalFilePath;
+ try {
+ InputStream resourceAsStream = Parser.class.getResourceAsStream(configFilePath);
+ insrc = new InputSource(resourceAsStream);
+ insrc.setSystemId(configFilePath);
+ } catch (Exception e1) {
+ log.error("Configuration file "+configFilePath+" could not be located.");
+ return null;
+ }
+
+ return loadDom(insrc,validate); // Now pass on to the main routine
+
+ }
+
+ /**
+ * Override the OpenSAML default schema from SAML 1.1 to
+ * SAML 1.1 plus Shibboleth (and some SAML 2.0).
+ */
+ static {
+ org.opensaml.XML.parserPool.setDefaultSchema(schema);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Schemas.java
+ *
+ * For now there is only one implementation of Schema store -
+ * a resource directory in the WAR file of the application.
+ * So there is only one current implementation of this interface,
+ * the SchemasDirectoryImpl class. But we define the interface
+ * and later on there may be other schema-store options.
+ */
+package edu.internet2.middleware.shibboleth.xml;
+
+import javax.xml.validation.Schema;
+
+/**
+ * @author Howard Gilbert
+ */
+public interface Schemas {
+
+ Schema compileSchema(String[] namespaces);
+
+}
--- /dev/null
+/*
+ * SchemasDirectoryImpl.java
+ *
+ * Find Schemas in a Resource directory
+ *
+ *
+ */
+package edu.internet2.middleware.shibboleth.xml;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.XMLConstants;
+import javax.xml.transform.Source;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * @author Howard Gilbert
+ */
+public class SchemasDirectoryImpl implements Schemas {
+
+ private static Logger log = Logger.getLogger(SchemasDirectoryImpl.class);
+
+ // The default is the /schemas/ resource directory in the WAR file.
+ private String resourcedir = "/schemas/";
+
+ public String getResourcedir() {
+ return resourcedir;
+ }
+
+ /**
+ * Allow alternate schema directories to be injected if xsd files
+ * cannot coexist within a single schema. For example, SAML 1.0 and
+ * 1.1 schemas use the same targetNamespace incompatibly. If you
+ * upgrade from "list of file names" to "list of namespaces", then
+ * two incompatible uses of the same namespace have to be stored in
+ * two separate schema directories.
+ *
+ * @param resourcedir Resource directory from which to load xsd files.
+ */
+ public void setResourcedir(String resourcedir) {
+ this.resourcedir = resourcedir;
+ }
+ /**
+ * Create JAXP 1.3 Schema object from list of namespaces and resource dir
+ *
+ * <p>This is an alternate approach to the Schema building logic used in
+ * org.opensaml.XML. That module is driven off a list of file names.
+ * This code reads in all the *.xsd files in a directory, indexes them
+ * by the namespace the schema defines, and then is driven off a list
+ * of namespaces. This is more more indirect and requires a bit more
+ * code, but it is more in line with the actual XSD standard where files
+ * and filenames are incidental. It can also be quickly ported to some
+ * other schema storage medium (LDAP, Database, ...).</p>
+ *
+ * @param namespaces Array of required XML namespaces for validation
+ * @param resourcedir Resource directory with schema files ("/schemas/")
+ * @return Schema object combining all namespaces.
+ */
+ public Schema compileSchema(String[] namespaces) {
+
+ Schema schema = null;
+
+ Map bucket = new HashMap();
+
+ // Find a directory of schemas
+ // It is a resource in WEB-INF/classes or the same jar file
+ // from which this class was loaded.
+ URL resource = Parser.class.getResource(resourcedir);
+ String path = resource.getPath();
+ File dir = new File(path);
+ if (!dir.isDirectory()) {
+ log.error("Cannot find the schemas resource directory");
+ return null;
+ }
+
+ // for each .xsd file in the directory
+ String[] filenames = dir.list();
+ int nextsource=0;
+ for (int i=0;i<filenames.length;i++) {
+ String filename = filenames[i];
+ if (!filename.endsWith(".xsd"))
+ continue;
+ InputStream inputStream =
+ Parser.class.getResourceAsStream(
+ "/schemas/" + filename);
+ InputSource insrc = new InputSource(inputStream);
+
+ // Non-validating parse to DOM
+ Document xsddom = Parser.loadDom(insrc,false);
+
+ // Get the target namespace from the root element
+ Element ele = xsddom.getDocumentElement();
+ if (!ele.getLocalName().equals("schema")) {
+ log.error("Schema file wrong root element:"+filename);
+ continue;
+ }
+ String targetNamespace = ele.getAttribute("targetNamespace");
+ if (targetNamespace==null) {
+ log.error("Schema has no targetNamespace: "+filename);
+ continue;
+ }
+
+ // Put the DOM in the Bucket keyed by namespace
+ if (bucket.containsKey(targetNamespace)) {
+ log.error("Schema for already defined namespace: "+targetNamespace+" "+filename);
+ continue;
+ }
+ bucket.put(targetNamespace,xsddom);
+ }
+ // Ok, so now we have a bucket of DOM objects keyed by the
+ // namespaces that they internally declare they define
+
+
+ // Now we have a list of Namespaces in the order we need
+ // to process them (imported dependencies first)
+ Source[] sources = new Source[namespaces.length];
+ for (int i=0;i<namespaces.length;i++) {
+ Document doc = (Document) bucket.get(namespaces[i]);
+ if (doc==null)
+ log.error("Schema missing for namespace "+namespaces[i]);
+ sources[i]= new DOMSource(doc);
+ }
+
+ // Now compile all the XSD files into a single composite Schema object
+ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ try {
+ schema = factory.newSchema(sources);
+ } catch (SAXException e) {
+ log.error("Schemas failed to compile, dependencies may have changed "+e);
+ System.out.println(e);
+ }
+ return schema;
+
+ }
+
+}