c437a64c2b076e780c6b5e8a2de1b1561d148282
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / xml / Parser.java
1 /*
2  * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * Parser.java
19  * 
20  * Validating and non-validating XML parsing using JAXP 1.3.
21  * 
22  * Previous versions of the code directly used the Xerces DOMParser
23  * class. This class has been hidden in the Sun XML stack, and the
24  * public interface is to use DocumentBuilderFactory. This module 
25  * requires the DOM 3 and JAXP 1.3 support built into J2SE 5.0 and
26  * distributed separately for earlier releases of Java from
27  * https://jaxp.dev.java.net/. It should also work with Xerces 2.7.0
28  * when that release becomes available.
29  * 
30  * The org.opensaml.XML class already has most of the parsing code,
31  * but it uses a subset of the required Schemas. Here we build a
32  * wider Schema object, set it as the default SAML schema (because
33  * some Shibboleth namespace fields appear in SAML statements), and
34  * demand that Schema for every parser (DocumentBuilder) we request.
35  * 
36  * Currently, this class exposes static methods. Should a real 
37  * framework be installed, it would become a singleton object.
38  */
39 package edu.internet2.middleware.shibboleth.xml;
40
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.io.StringWriter;
44 import java.net.URL;
45 import java.util.Iterator;
46 import java.util.Map;
47
48 import javax.xml.transform.OutputKeys;
49 import javax.xml.transform.Transformer;
50 import javax.xml.transform.TransformerConfigurationException;
51 import javax.xml.transform.TransformerException;
52 import javax.xml.transform.TransformerFactory;
53 import javax.xml.transform.dom.DOMSource;
54 import javax.xml.transform.stream.StreamResult;
55 import javax.xml.validation.Schema;
56
57 import org.apache.log4j.Logger;
58 import org.apache.xml.security.c14n.CanonicalizationException;
59 import org.apache.xml.security.c14n.Canonicalizer;
60 import org.apache.xml.security.c14n.InvalidCanonicalizerException;
61 import org.opensaml.SAMLException;
62 import org.w3c.dom.Document;
63 import org.w3c.dom.Node;
64 import org.xml.sax.InputSource;
65 import org.xml.sax.SAXException;
66
67 import edu.internet2.middleware.shibboleth.common.ShibResource;
68
69 /**
70  * Obtain schema validating and non-validating XML parsers.
71  * 
72  * @author Howard Gilbert
73  */
74 public class Parser {
75     private static Logger log = Logger.getLogger(Parser.class);
76     
77     
78     /**
79      * All the namespaces used by any part of Shibboleth
80      * 
81      * Note: The current Schema compiler requires that dependencies
82      * (imports) be listed before the namespace of the schema
83      * that imports them.
84      */
85     private static String[] namespaces = new String[]{
86             "http://www.w3.org/2000/09/xmldsig#",  
87             "http://www.w3.org/2001/04/xmlenc#",
88             "urn:oasis:names:tc:SAML:1.0:assertion",
89             "urn:oasis:names:tc:SAML:2.0:assertion",
90             "http://www.w3.org/XML/1998/namespace",
91             "http://schemas.xmlsoap.org/soap/envelope/",
92             "urn:mace:shibboleth:credentials:1.0",
93             "urn:oasis:names:tc:SAML:1.0:protocol",
94             "urn:mace:shibboleth:namemapper:1.0",
95             "urn:mace:shibboleth:idp:config:1.0",
96             "urn:mace:shibboleth:arp:1.0",
97             "urn:mace:shibboleth:resolver:1.0",
98             "urn:oasis:names:tc:SAML:2.0:metadata",
99             "urn:oasis:names:tc:SAML:metadata:extension",
100             "urn:mace:shibboleth:target:config:1.0",
101             "urn:mace:shibboleth:trust:1.0",
102             "urn:mace:shibboleth:metadata:1.0",
103             "urn:mace:shibboleth:1.0",
104             "http://schemas.xmlsoap.org/soap/envelope/"
105       };
106     
107     private static String[] resources = new String[]{
108             "credentials.xsd",
109             "cs-sstc-schema-assertion-1.1.xsd",
110             "cs-sstc-schema-protocol-1.1.xsd",
111             "namemapper.xsd",
112             "saml-schema-assertion-2.0.xsd",
113             "saml-schema-metadata-2.0.xsd",
114             "saml-schema-metadata-ext.xsd",
115             "shibboleth-arp-1.0.xsd",
116             "shibboleth-idpconfig-1.0.xsd",
117             "shibboleth-metadata-1.0.xsd",
118             "shibboleth-resolver-1.0.xsd",
119             "shibboleth-targetconfig-1.0.xsd",
120             "shibboleth-trust-1.0.xsd",
121             "shibboleth.xsd",
122             "soap-envelope.xsd",
123             "wayfconfig.xsd",
124             "xenc-schema.xsd",
125             "xml.xsd",
126             "xmldsig-core-schema.xsd"
127        };
128     private static String[] oldResources = new String[]{
129             "cs-sstc-schema-assertion-01.xsd",
130             "cs-sstc-schema-protocol-01.xsd"
131        };
132     
133     // If there were a real Framework here (like Spring) then
134     // the schemaBuilder would be inserted 
135     private static String defaultDirectory = "/schemas/";
136     private static String oldSchemasDir = "/schemas/saml-1.0/";
137     private static final boolean useResourceBuilder=true;
138     
139     private static SchemaStore schemaBuilder = 
140         (useResourceBuilder?
141             (SchemaStore)
142                 new SchemasResourceListImpl(defaultDirectory,resources):
143             (SchemaStore)
144                 new SchemasDirectoryImpl(defaultDirectory));
145     private static SchemaStore oldSchemasBuilder = 
146         (useResourceBuilder?
147                 (SchemaStore)
148                     new SchemasResourceListImpl(oldSchemasDir,oldResources):
149                 (SchemaStore)
150                         new SchemasDirectoryImpl(oldSchemasDir));
151     
152     private static Schema schema = schemaBuilder.compileSchema(namespaces);
153     static {
154         // Merge in the XSDs defining non-conflicting namespaces
155         // A non-replacing putAll()
156         Map/*<String,Document>*/ source = schemaBuilder.getSchemaMap();
157         Map/*<String,Document>*/ sink   = oldSchemasBuilder.getSchemaMap();
158         Iterator/*<String>*/ nsi = source.keySet().iterator();
159         while (nsi.hasNext()) {
160             String namespace = (String) nsi.next();
161             if (!sink.containsKey(namespace)) {
162                 sink.put(namespace,source.get(namespace));
163             }
164         }
165     }
166         private static Schema schemaOldSAML= 
167             oldSchemasBuilder.compileSchema(namespaces);
168     
169     /**
170      * Load a DOM from a wrapped byte stream.
171      * 
172      * @param ins InputSource The XML document
173      * @param validate If true, use Schema. Otherwise, its raw XML.
174      * @return A DOM 3 tree
175      */
176     public static Document loadDom(InputSource ins, boolean validate) throws SAMLException, SAXException, IOException {
177
178                 Document doc = null;
179                 log.debug("Loading XML from (" + ins.getSystemId() + ")" + (validate ? " with Schema validation" : ""));
180                 if (validate) {
181                         doc = org.opensaml.XML.parserPool.parse(ins, schema);
182                 } else {
183                         doc = org.opensaml.XML.parserPool.parse(ins, null);
184                 }
185                 return doc;
186         }
187     
188     
189     /**
190          * A dummy class that pretends to be an old Xerces DOMParser to simplify conversion of existing code.
191          */
192     public static class DOMParser {
193         Document doc = null;
194         boolean validate = false;
195         
196         public DOMParser(boolean validate) {
197             this.validate=validate;
198         }
199         
200         public Document parse(InputSource ins) throws SAXException, IOException, SAMLException {
201             doc = loadDom(ins,validate);
202             return doc;
203         }
204         
205         public Document getDocument() {
206             return doc;
207         }
208     }
209     
210     /**
211      * Write a DOM out to a character stream (for debugging and logging)
212      * 
213      * @param dom The DOM tree to write
214      * @return A string containing the XML in character form.
215      */
216     public static String jaxpSerialize(Node dom) {
217         String ret = null;
218         
219         TransformerFactory factory = TransformerFactory.newInstance();
220         Transformer transformer = null;
221         DOMSource source = new DOMSource(dom);
222         try {
223             transformer = factory.newTransformer();
224             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
225         } catch (TransformerConfigurationException e) {
226             return null;
227         }
228         StringWriter stringWriter = new StringWriter();
229         StreamResult result = new StreamResult(stringWriter);
230         try {
231             transformer.transform(source, result);
232         } catch (TransformerException e1) {
233             return null;
234         }
235         return stringWriter.toString();
236     }
237     
238     /**
239      *  Serializes the XML representation of the SAML object to a stream
240      *
241      * @param  out                      Stream to use for output
242      * @exception  java.io.IOException  Raised if an I/O problem is detected
243      * @exception  SAMLException Raised if the object is incompletely defined 
244      */
245     public static String serialize(Node root){
246         byte[] bs = null;
247         try
248         {
249             Canonicalizer c = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
250             bs = c.canonicalizeSubtree(root, "#default saml samlp ds xsd xsi code kind rw typens");
251         }
252         catch (InvalidCanonicalizerException e)
253         {
254             return null;
255         }
256         catch (CanonicalizationException e)
257         {
258             return null;
259         }
260         return new String(bs);
261     }
262
263     
264     /**
265      * Version of loadDom where the file is specified as a resource name
266      * 
267      * @param configFilePath input resource
268      * @param validate if true, use Schema
269      * @return DOM tree
270      */
271     public static Document loadDom(String configFilePath,boolean validate) throws SAMLException, SAXException, IOException 
272     {
273        InputSource insrc;
274        try {
275             InputStream resourceAsStream = 
276  //               Parser.class.getResourceAsStream(configFilePath);
277                 new ShibResource(configFilePath).getInputStream();
278             insrc = new InputSource(resourceAsStream);
279             insrc.setSystemId(configFilePath);
280         } catch (Exception e1) {
281             log.error("Configuration file "+configFilePath+" could not be located.");
282             return null;
283         }
284         
285         return loadDom(insrc,validate); // Now pass on to the main routine
286         
287     }
288
289     /**
290      * Version of loadDom where the file is specified as a URL.
291      * 
292      * @param configURL input resource
293      * @param validate if true, use Schema
294      * @return DOM tree
295      */
296     public static Document loadDom(URL configURL, boolean validate) throws SAMLException, SAXException, IOException 
297     {
298        InputSource insrc;
299        try {
300             InputStream resourceAsStream = configURL.openStream();
301             insrc = new InputSource(resourceAsStream);
302             insrc.setSystemId(configURL.toString());
303         } catch (Exception e1) {
304             log.error("Configuration URL "+configURL+" could not be accessed.");
305             return null;
306         }
307         
308         return loadDom(insrc,validate); // Now pass on to the main routine
309         
310     }
311     
312     /**
313      * Override the OpenSAML default schema from SAML 1.1 to 
314      * SAML 1.1 plus Shibboleth (and some SAML 2.0).
315      */
316     static {
317         //org.opensaml.XML.parserPool.setDefaultSchema(schema);
318                 org.opensaml.XML.parserPool.setDefaultSchemas(schemaOldSAML,schema);
319     }
320     
321 }