Support both SAML 1.0 and 1.1 schemas
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / xml / Parser.java
1 /*
2  * Parser.java
3  * 
4  * Validating and non-validating XML parsing using JAXP 1.3.
5  * 
6  * Previous versions of the code directly used the Xerces DOMParser
7  * class. This class has been hidden in the Sun XML stack, and the
8  * public interface is to use DocumentBuilderFactory. This module 
9  * requires the DOM 3 and JAXP 1.3 support built into J2SE 5.0 and
10  * distributed separately for earlier releases of Java from
11  * https://jaxp.dev.java.net/. It should also work with Xerces 2.7.0
12  * when that release becomes available.
13  * 
14  * The org.opensaml.XML class already has most of the parsing code,
15  * but it uses a subset of the required Schemas. Here we build a
16  * wider Schema object, set it as the default SAML schema (because
17  * some Shibboleth namespace fields appear in SAML statements), and
18  * demand that Schema for every parser (DocumentBuilder) we request.
19  * 
20  * Currently, this class exposes static methods. Should a real 
21  * framework be installed, it would become a singleton object.
22  */
23 package edu.internet2.middleware.shibboleth.xml;
24
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.StringWriter;
28
29 import javax.xml.transform.OutputKeys;
30 import javax.xml.transform.Transformer;
31 import javax.xml.transform.TransformerConfigurationException;
32 import javax.xml.transform.TransformerException;
33 import javax.xml.transform.TransformerFactory;
34 import javax.xml.transform.dom.DOMSource;
35 import javax.xml.transform.stream.StreamResult;
36 import javax.xml.validation.Schema;
37
38 import org.apache.log4j.Logger;
39 import org.opensaml.SAMLException;
40 import org.w3c.dom.Document;
41 import org.w3c.dom.Node;
42 import org.xml.sax.InputSource;
43 import org.xml.sax.SAXException;
44
45 /**
46  * Obtain schema validating and non-validating XML parsers.
47  * 
48  * @author Howard Gilbert
49  */
50 public class Parser {
51     private static Logger log = Logger.getLogger(Parser.class);
52     
53     
54     /**
55      * All the namespaces used by any part of Shibboleth
56      * 
57      * Note: The current Schema compiler requires that dependencies
58      * (imports) be listed before the namespace of the schema
59      * that imports them.
60      */
61     private static String[] namespaces = new String[]{
62             "http://www.w3.org/2000/09/xmldsig#",  
63             "http://www.w3.org/2001/04/xmlenc#",
64             "urn:oasis:names:tc:SAML:1.0:assertion",
65             "urn:oasis:names:tc:SAML:2.0:assertion",
66             "http://www.w3.org/XML/1998/namespace",
67             "http://schemas.xmlsoap.org/soap/envelope/",
68             "urn:mace:shibboleth:credentials:1.0",
69             "urn:oasis:names:tc:SAML:1.0:protocol",
70             "urn:oasis:names:tc:SAML:2.0:protocol",
71             "urn:mace:shibboleth:namemapper:1.0",
72             "urn:mace:shibboleth:idp:config:1.0",
73             "urn:mace:shibboleth:arp:1.0",
74             "urn:mace:shibboleth:resolver:1.0",
75             "urn:mace:shibboleth:target:config:1.0",
76             "urn:mace:shibboleth:trust:1.0",
77             "urn:mace:shibboleth:metadata:1.0",
78             "urn:mace:shibboleth:1.0",
79             "http://schemas.xmlsoap.org/soap/envelope/",
80             "urn:oasis:names:tc:SAML:2.0:metadata"
81       };
82     
83     // If there were a real Framework here (like Spring) then
84     // the schemaBuilder would be inserted 
85     private static SchemasDirectoryImpl schemaBuilder = new SchemasDirectoryImpl();
86     private static Schema schema = schemaBuilder.compileSchema(namespaces);
87         private static Schema schemaOldSAML= schemaBuilder.compileSchema(namespaces,"/schemas/saml-1.0/");
88     
89     /**
90      * Load a DOM from a wrapped byte stream.
91      * 
92      * @param ins InputSource The XML document
93      * @param validate If true, use Schema. Otherwise, its raw XML.
94      * @return A DOM 3 tree
95      */
96     public static Document loadDom(InputSource ins, boolean validate) throws SAMLException, SAXException, IOException {
97
98                 Document doc = null;
99                 log.debug("Loading XML from (" + ins.getSystemId() + ")" + (validate ? " with Schema validation" : ""));
100                 if (validate) {
101                         doc = org.opensaml.XML.parserPool.parse(ins, schema);
102                 } else {
103                         doc = org.opensaml.XML.parserPool.parse(ins, null);
104                 }
105                 return doc;
106         }
107     
108     
109     /**
110          * A dummy class that pretends to be an old Xerces DOMParser to simplify conversion of existing code.
111          */
112     public static class DOMParser {
113         Document doc = null;
114         boolean validate = false;
115         
116         public DOMParser(boolean validate) {
117             this.validate=validate;
118         }
119         
120         public Document parse(InputSource ins) throws SAXException, IOException, SAMLException {
121             doc = loadDom(ins,true);
122             return doc;
123         }
124         
125         public Document getDocument() {
126             return doc;
127         }
128     }
129     
130     /**
131      * Write a DOM out to a character stream (for debugging and logging)
132      * 
133      * @param dom The DOM tree to write
134      * @return A string containing the XML in character form.
135      */
136     public static String serialize(Node dom) {
137         String ret = null;
138         
139         TransformerFactory factory = TransformerFactory.newInstance();
140         Transformer transformer = null;
141         DOMSource source = new DOMSource(dom);
142         try {
143             transformer = factory.newTransformer();
144             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
145         } catch (TransformerConfigurationException e) {
146             return null;
147         }
148         StringWriter stringWriter = new StringWriter();
149         StreamResult result = new StreamResult(stringWriter);
150         try {
151             transformer.transform(source, result);
152         } catch (TransformerException e1) {
153             return null;
154         }
155         return stringWriter.toString();
156     }
157     
158     /**
159      * Version of loadDom where the file is specified as a resource name
160      * 
161      * @param configFilePath input resource
162      * @param validate if true, use Schema
163      * @return DOM tree
164      */
165     public static Document loadDom(String configFilePath,boolean validate) throws SAMLException, SAXException, IOException 
166     {
167         InputSource insrc;
168         String schemaCannonicalFilePath;
169        try {
170             InputStream resourceAsStream = Parser.class.getResourceAsStream(configFilePath);
171             insrc = new InputSource(resourceAsStream);
172             insrc.setSystemId(configFilePath);
173         } catch (Exception e1) {
174             log.error("Configuration file "+configFilePath+" could not be located.");
175             return null;
176         }
177         
178         return loadDom(insrc,validate); // Now pass on to the main routine
179         
180     }
181     
182     /**
183      * Override the OpenSAML default schema from SAML 1.1 to 
184      * SAML 1.1 plus Shibboleth (and some SAML 2.0).
185      */
186     static {
187         //org.opensaml.XML.parserPool.setDefaultSchema(schema);
188                 org.opensaml.XML.parserPool.setDefaultSchemas(schemaOldSAML,schema);
189     }
190     
191 }