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