Use Cannonicalized serialization for the compare tests
[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  * Copyright 2002, 2004 
24  * University Corporation for Advanced Internet Development, Inc. 
25  * All rights reserved
26  * [Thats all we have to say to protect ourselves]
27  * Your permission to use this code is governed by "The Shibboleth License".
28  * A copy may be found at http://shibboleth.internet2.edu/license.html
29  * [Nothing in copyright law requires license text in every file.]
30  */
31 package edu.internet2.middleware.shibboleth.xml;
32
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.io.StringWriter;
36 import java.util.Iterator;
37 import java.util.Map;
38
39 import javax.xml.transform.OutputKeys;
40 import javax.xml.transform.Transformer;
41 import javax.xml.transform.TransformerConfigurationException;
42 import javax.xml.transform.TransformerException;
43 import javax.xml.transform.TransformerFactory;
44 import javax.xml.transform.dom.DOMSource;
45 import javax.xml.transform.stream.StreamResult;
46 import javax.xml.validation.Schema;
47
48 import org.apache.log4j.Logger;
49 import org.apache.xml.security.c14n.CanonicalizationException;
50 import org.apache.xml.security.c14n.Canonicalizer;
51 import org.apache.xml.security.c14n.InvalidCanonicalizerException;
52 import org.opensaml.SAMLException;
53 import org.w3c.dom.Document;
54 import org.w3c.dom.Node;
55 import org.xml.sax.InputSource;
56 import org.xml.sax.SAXException;
57
58 /**
59  * Obtain schema validating and non-validating XML parsers.
60  * 
61  * @author Howard Gilbert
62  */
63 public class Parser {
64     private static Logger log = Logger.getLogger(Parser.class);
65     
66     
67     /**
68      * All the namespaces used by any part of Shibboleth
69      * 
70      * Note: The current Schema compiler requires that dependencies
71      * (imports) be listed before the namespace of the schema
72      * that imports them.
73      */
74     private static String[] namespaces = new String[]{
75             "http://www.w3.org/2000/09/xmldsig#",  
76             "http://www.w3.org/2001/04/xmlenc#",
77             "urn:oasis:names:tc:SAML:1.0:assertion",
78             "urn:oasis:names:tc:SAML:2.0:assertion",
79             "http://www.w3.org/XML/1998/namespace",
80             "http://schemas.xmlsoap.org/soap/envelope/",
81             "urn:mace:shibboleth:credentials:1.0",
82             "urn:oasis:names:tc:SAML:1.0:protocol",
83             "urn:mace:shibboleth:namemapper:1.0",
84             "urn:mace:shibboleth:idp:config:1.0",
85             "urn:mace:shibboleth:arp:1.0",
86             "urn:mace:shibboleth:resolver:1.0",
87             "urn:oasis:names:tc:SAML:2.0:metadata",
88             "urn:mace:shibboleth:target:config:1.0",
89             "urn:mace:shibboleth:trust:1.0",
90             "urn:mace:shibboleth:metadata:1.0",
91             "urn:mace:shibboleth:1.0",
92             "http://schemas.xmlsoap.org/soap/envelope/"
93       };
94     
95     private static String[] resources = new String[]{
96             "credentials.xsd",
97             "cs-sstc-schema-assertion-1.1.xsd",
98             "cs-sstc-schema-protocol-1.1.xsd",
99             "namemapper.xsd",
100             "saml-schema-assertion-2.0.xsd",
101             "saml-schema-metadata-2.0.xsd",
102             "shibboleth-arp-1.0.xsd",
103             "shibboleth-idpconfig-1.0.xsd",
104             "shibboleth-metadata-1.0.xsd",
105             "shibboleth-resolver-1.0.xsd",
106             "shibboleth-targetconfig-1.0.xsd",
107             "shibboleth-trust-1.0.xsd",
108             "shibboleth.xsd",
109             "soap-envelope.xsd",
110             "wayfconfig.xsd",
111             "xenc-schema.xsd",
112             "xml.xsd",
113             "xmldsig-core-schema.xsd"
114        };
115     private static String[] oldResources = new String[]{
116             "cs-sstc-schema-assertion-01.xsd",
117             "cs-sstc-schema-protocol-01.xsd"
118        };
119     
120     // If there were a real Framework here (like Spring) then
121     // the schemaBuilder would be inserted 
122     private static String defaultDirectory = "/schemas/";
123     private static String oldSchemasDir = "/schemas/saml-1.0/";
124     private static final boolean useResourceBuilder=true;
125     
126     private static SchemaStore schemaBuilder = 
127         (useResourceBuilder?
128             (SchemaStore)
129                 new SchemasResourceListImpl(defaultDirectory,resources):
130             (SchemaStore)
131                 new SchemasDirectoryImpl(defaultDirectory));
132     private static SchemaStore oldSchemasBuilder = 
133         (useResourceBuilder?
134                 (SchemaStore)
135                     new SchemasResourceListImpl(oldSchemasDir,oldResources):
136                 (SchemaStore)
137                         new SchemasDirectoryImpl(oldSchemasDir));
138     
139     private static Schema schema = schemaBuilder.compileSchema(namespaces);
140     static {
141         // Merge in the XSDs defining non-conflicting namespaces
142         // A non-replacing putAll()
143         Map/*<String,Document>*/ source = schemaBuilder.getSchemaMap();
144         Map/*<String,Document>*/ sink   = oldSchemasBuilder.getSchemaMap();
145         Iterator/*<String>*/ nsi = source.keySet().iterator();
146         while (nsi.hasNext()) {
147             String namespace = (String) nsi.next();
148             if (!sink.containsKey(namespace)) {
149                 sink.put(namespace,source.get(namespace));
150             }
151         }
152     }
153         private static Schema schemaOldSAML= 
154             oldSchemasBuilder.compileSchema(namespaces);
155     
156     /**
157      * Load a DOM from a wrapped byte stream.
158      * 
159      * @param ins InputSource The XML document
160      * @param validate If true, use Schema. Otherwise, its raw XML.
161      * @return A DOM 3 tree
162      */
163     public static Document loadDom(InputSource ins, boolean validate) throws SAMLException, SAXException, IOException {
164
165                 Document doc = null;
166                 log.debug("Loading XML from (" + ins.getSystemId() + ")" + (validate ? " with Schema validation" : ""));
167                 if (validate) {
168                         doc = org.opensaml.XML.parserPool.parse(ins, schema);
169                 } else {
170                         doc = org.opensaml.XML.parserPool.parse(ins, null);
171                 }
172                 return doc;
173         }
174     
175     
176     /**
177          * A dummy class that pretends to be an old Xerces DOMParser to simplify conversion of existing code.
178          */
179     public static class DOMParser {
180         Document doc = null;
181         boolean validate = false;
182         
183         public DOMParser(boolean validate) {
184             this.validate=validate;
185         }
186         
187         public Document parse(InputSource ins) throws SAXException, IOException, SAMLException {
188             doc = loadDom(ins,true);
189             return doc;
190         }
191         
192         public Document getDocument() {
193             return doc;
194         }
195     }
196     
197     /**
198      * Write a DOM out to a character stream (for debugging and logging)
199      * 
200      * @param dom The DOM tree to write
201      * @return A string containing the XML in character form.
202      */
203     public static String serialize(Node dom) {
204         String ret = null;
205         
206         TransformerFactory factory = TransformerFactory.newInstance();
207         Transformer transformer = null;
208         DOMSource source = new DOMSource(dom);
209         try {
210             transformer = factory.newTransformer();
211             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
212         } catch (TransformerConfigurationException e) {
213             return null;
214         }
215         StringWriter stringWriter = new StringWriter();
216         StreamResult result = new StreamResult(stringWriter);
217         try {
218             transformer.transform(source, result);
219         } catch (TransformerException e1) {
220             return null;
221         }
222         return stringWriter.toString();
223     }
224     
225     /**
226      *  Serializes the XML representation of the SAML object to a stream
227      *
228      * @param  out                      Stream to use for output
229      * @exception  java.io.IOException  Raised if an I/O problem is detected
230      * @exception  SAMLException Raised if the object is incompletely defined 
231      */
232     public static String serializeC14N(Node root){
233         byte[] bs = null;
234         try
235         {
236             Canonicalizer c = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
237             bs = c.canonicalizeSubtree(root, "#default saml samlp ds xsd xsi code kind rw typens");
238         }
239         catch (InvalidCanonicalizerException e)
240         {
241             return null;
242         }
243         catch (CanonicalizationException e)
244         {
245             return null;
246         }
247         return new String(bs);
248     }
249
250     
251     /**
252      * Version of loadDom where the file is specified as a resource name
253      * 
254      * @param configFilePath input resource
255      * @param validate if true, use Schema
256      * @return DOM tree
257      */
258     public static Document loadDom(String configFilePath,boolean validate) throws SAMLException, SAXException, IOException 
259     {
260         InputSource insrc;
261         String schemaCannonicalFilePath;
262        try {
263             InputStream resourceAsStream = Parser.class.getResourceAsStream(configFilePath);
264             insrc = new InputSource(resourceAsStream);
265             insrc.setSystemId(configFilePath);
266         } catch (Exception e1) {
267             log.error("Configuration file "+configFilePath+" could not be located.");
268             return null;
269         }
270         
271         return loadDom(insrc,validate); // Now pass on to the main routine
272         
273     }
274     
275     /**
276      * Override the OpenSAML default schema from SAML 1.1 to 
277      * SAML 1.1 plus Shibboleth (and some SAML 2.0).
278      */
279     static {
280         //org.opensaml.XML.parserPool.setDefaultSchema(schema);
281                 org.opensaml.XML.parserPool.setDefaultSchemas(schemaOldSAML,schema);
282     }
283     
284 }