8ae7d5be6c03447368865efc853039ad9d7f845b
[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:oasis:names:tc:SAML:metadata:extension",
89             "urn:mace:shibboleth:target:config:1.0",
90             "urn:mace:shibboleth:trust:1.0",
91             "urn:mace:shibboleth:metadata:1.0",
92             "urn:mace:shibboleth:1.0",
93             "http://schemas.xmlsoap.org/soap/envelope/"
94       };
95     
96     private static String[] resources = new String[]{
97             "credentials.xsd",
98             "cs-sstc-schema-assertion-1.1.xsd",
99             "cs-sstc-schema-protocol-1.1.xsd",
100             "namemapper.xsd",
101             "saml-schema-assertion-2.0.xsd",
102             "saml-schema-metadata-2.0.xsd",
103             "saml-schema-metadata-ext.xsd",
104             "shibboleth-arp-1.0.xsd",
105             "shibboleth-idpconfig-1.0.xsd",
106             "shibboleth-metadata-1.0.xsd",
107             "shibboleth-resolver-1.0.xsd",
108             "shibboleth-targetconfig-1.0.xsd",
109             "shibboleth-trust-1.0.xsd",
110             "shibboleth.xsd",
111             "soap-envelope.xsd",
112             "wayfconfig.xsd",
113             "xenc-schema.xsd",
114             "xml.xsd",
115             "xmldsig-core-schema.xsd"
116        };
117     private static String[] oldResources = new String[]{
118             "cs-sstc-schema-assertion-01.xsd",
119             "cs-sstc-schema-protocol-01.xsd"
120        };
121     
122     // If there were a real Framework here (like Spring) then
123     // the schemaBuilder would be inserted 
124     private static String defaultDirectory = "/schemas/";
125     private static String oldSchemasDir = "/schemas/saml-1.0/";
126     private static final boolean useResourceBuilder=true;
127     
128     private static SchemaStore schemaBuilder = 
129         (useResourceBuilder?
130             (SchemaStore)
131                 new SchemasResourceListImpl(defaultDirectory,resources):
132             (SchemaStore)
133                 new SchemasDirectoryImpl(defaultDirectory));
134     private static SchemaStore oldSchemasBuilder = 
135         (useResourceBuilder?
136                 (SchemaStore)
137                     new SchemasResourceListImpl(oldSchemasDir,oldResources):
138                 (SchemaStore)
139                         new SchemasDirectoryImpl(oldSchemasDir));
140     
141     private static Schema schema = schemaBuilder.compileSchema(namespaces);
142     static {
143         // Merge in the XSDs defining non-conflicting namespaces
144         // A non-replacing putAll()
145         Map/*<String,Document>*/ source = schemaBuilder.getSchemaMap();
146         Map/*<String,Document>*/ sink   = oldSchemasBuilder.getSchemaMap();
147         Iterator/*<String>*/ nsi = source.keySet().iterator();
148         while (nsi.hasNext()) {
149             String namespace = (String) nsi.next();
150             if (!sink.containsKey(namespace)) {
151                 sink.put(namespace,source.get(namespace));
152             }
153         }
154     }
155         private static Schema schemaOldSAML= 
156             oldSchemasBuilder.compileSchema(namespaces);
157     
158     /**
159      * Load a DOM from a wrapped byte stream.
160      * 
161      * @param ins InputSource The XML document
162      * @param validate If true, use Schema. Otherwise, its raw XML.
163      * @return A DOM 3 tree
164      */
165     public static Document loadDom(InputSource ins, boolean validate) throws SAMLException, SAXException, IOException {
166
167                 Document doc = null;
168                 log.debug("Loading XML from (" + ins.getSystemId() + ")" + (validate ? " with Schema validation" : ""));
169                 if (validate) {
170                         doc = org.opensaml.XML.parserPool.parse(ins, schema);
171                 } else {
172                         doc = org.opensaml.XML.parserPool.parse(ins, null);
173                 }
174                 return doc;
175         }
176     
177     
178     /**
179          * A dummy class that pretends to be an old Xerces DOMParser to simplify conversion of existing code.
180          */
181     public static class DOMParser {
182         Document doc = null;
183         boolean validate = false;
184         
185         public DOMParser(boolean validate) {
186             this.validate=validate;
187         }
188         
189         public Document parse(InputSource ins) throws SAXException, IOException, SAMLException {
190             doc = loadDom(ins,true);
191             return doc;
192         }
193         
194         public Document getDocument() {
195             return doc;
196         }
197     }
198     
199     /**
200      * Write a DOM out to a character stream (for debugging and logging)
201      * 
202      * @param dom The DOM tree to write
203      * @return A string containing the XML in character form.
204      */
205     public static String serialize(Node dom) {
206         String ret = null;
207         
208         TransformerFactory factory = TransformerFactory.newInstance();
209         Transformer transformer = null;
210         DOMSource source = new DOMSource(dom);
211         try {
212             transformer = factory.newTransformer();
213             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
214         } catch (TransformerConfigurationException e) {
215             return null;
216         }
217         StringWriter stringWriter = new StringWriter();
218         StreamResult result = new StreamResult(stringWriter);
219         try {
220             transformer.transform(source, result);
221         } catch (TransformerException e1) {
222             return null;
223         }
224         return stringWriter.toString();
225     }
226     
227     /**
228      *  Serializes the XML representation of the SAML object to a stream
229      *
230      * @param  out                      Stream to use for output
231      * @exception  java.io.IOException  Raised if an I/O problem is detected
232      * @exception  SAMLException Raised if the object is incompletely defined 
233      */
234     public static String serializeC14N(Node root){
235         byte[] bs = null;
236         try
237         {
238             Canonicalizer c = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
239             bs = c.canonicalizeSubtree(root, "#default saml samlp ds xsd xsi code kind rw typens");
240         }
241         catch (InvalidCanonicalizerException e)
242         {
243             return null;
244         }
245         catch (CanonicalizationException e)
246         {
247             return null;
248         }
249         return new String(bs);
250     }
251
252     
253     /**
254      * Version of loadDom where the file is specified as a resource name
255      * 
256      * @param configFilePath input resource
257      * @param validate if true, use Schema
258      * @return DOM tree
259      */
260     public static Document loadDom(String configFilePath,boolean validate) throws SAMLException, SAXException, IOException 
261     {
262         InputSource insrc;
263         String schemaCannonicalFilePath;
264        try {
265             InputStream resourceAsStream = Parser.class.getResourceAsStream(configFilePath);
266             insrc = new InputSource(resourceAsStream);
267             insrc.setSystemId(configFilePath);
268         } catch (Exception e1) {
269             log.error("Configuration file "+configFilePath+" could not be located.");
270             return null;
271         }
272         
273         return loadDom(insrc,validate); // Now pass on to the main routine
274         
275     }
276     
277     /**
278      * Override the OpenSAML default schema from SAML 1.1 to 
279      * SAML 1.1 plus Shibboleth (and some SAML 2.0).
280      */
281     static {
282         //org.opensaml.XML.parserPool.setDefaultSchema(schema);
283                 org.opensaml.XML.parserPool.setDefaultSchemas(schemaOldSAML,schema);
284     }
285     
286 }