View Javadoc

1   /*
2    * Copyright [2007] [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  package org.opensaml.common.xml;
18  
19  import java.lang.ref.SoftReference;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.List;
23  
24  import javax.xml.XMLConstants;
25  import javax.xml.transform.Source;
26  import javax.xml.transform.stream.StreamSource;
27  import javax.xml.validation.Schema;
28  import javax.xml.validation.SchemaFactory;
29  
30  import org.opensaml.xml.parse.ClasspathResolver;
31  import org.opensaml.xml.parse.LoggingErrorHandler;
32  import org.slf4j.LoggerFactory;
33  import org.xml.sax.SAXException;
34  
35  /**
36   * A convenience builder for creating {@link Schema}s for validating SAML 1_0, 1_1, and 2_0.
37   * 
38   * Additional schema may be registered by {@link #addExtensionSchema(String)} with the given argument a relative or
39   * absolute path that will be resolved against the classpath. Note that relative paths are relative to <strong>this</strong>
40   * class. Also, schema files must be provided in the order they are referenced, that is if schema B depends on schema A
41   * then schema A must appear first in the list of registered extension schemas.
42   * 
43   * Schemas may use a schema location attribute. These schema locations will be resolved by the {@link ClasspathResolver}.
44   * If schema locations are used they will be resolved and will meet the aformentioned schema ordering requirement.
45   * 
46   * The schema objects produced here are thread safe and should be re-used, to that end the schema builder will cache
47   * created schema using {@link SoftReference}s, allowing the VM to reclaim the memory used by schemas if necessary.
48   */
49  public final class SAMLSchemaBuilder {
50  
51      /** SAML 1_0 Schema with SAML 2_0 schemas and extensions. */
52      private static SoftReference<Schema> saml10Schema;
53  
54      /** SAML 1_0 Schema with SAML 2_0 schemas and extensions. */
55      private static SoftReference<Schema> saml11Schema;
56  
57      /** Classpath relative location of basic XML schemas. */
58      private static String[] baseXMLSchemas = { "/schema/xml.xsd", "/schema/XMLSchema.xsd",
59              "/schema/xmldsig-core-schema.xsd", "/schema/xenc-schema.xsd", };
60  
61      /** Classpath relative location of SOAP 1_1 schemas. */
62      private static String[] soapSchemas = { "/schema/soap-envelope.xsd", };
63  
64      /** Classpath relative location of SAML 1_0 schemas. */
65      private static String[] saml10Schemas = { "/schema/cs-sstc-schema-assertion-01.xsd",
66              "/schema/cs-sstc-schema-protocol-01.xsd", };
67  
68      /** Classpath relative location of SAML 1_1 schemas. */
69      private static String[] saml11Schemas = { "/schema/cs-sstc-schema-assertion-1.1.xsd",
70              "/schema/cs-sstc-schema-protocol-1.1.xsd", };
71  
72      /** Classpath relative location of SAML 2_0 schemas. */
73      private static String[] saml20Schemas = { 
74          "/schema/saml-schema-assertion-2.0.xsd",
75          "/schema/saml-schema-authn-context-2.0.xsd",
76          "/schema/saml-schema-authn-context-auth-telephony-2.0.xsd",
77          "/schema/saml-schema-authn-context-ip-2.0.xsd",
78          "/schema/saml-schema-authn-context-ippword-2.0.xsd",
79          "/schema/saml-schema-authn-context-kerberos-2.0.xsd",
80          "/schema/saml-schema-authn-context-mobileonefactor-reg-2.0.xsd",
81          "/schema/saml-schema-authn-context-mobileonefactor-unreg-2.0.xsd",
82          "/schema/saml-schema-authn-context-mobiletwofactor-reg-2.0.xsd",
83          "/schema/saml-schema-authn-context-mobiletwofactor-unreg-2.0.xsd",
84          "/schema/saml-schema-authn-context-nomad-telephony-2.0.xsd",
85          "/schema/saml-schema-authn-context-personal-telephony-2.0.xsd",
86          "/schema/saml-schema-authn-context-pgp-2.0.xsd",
87          "/schema/saml-schema-authn-context-ppt-2.0.xsd",
88          "/schema/saml-schema-authn-context-pword-2.0.xsd",
89          "/schema/saml-schema-authn-context-session-2.0.xsd",
90          "/schema/saml-schema-authn-context-smartcard-2.0.xsd",
91          "/schema/saml-schema-authn-context-smartcardpki-2.0.xsd",
92          "/schema/saml-schema-authn-context-softwarepki-2.0.xsd",
93          "/schema/saml-schema-authn-context-spki-2.0.xsd",
94          "/schema/saml-schema-authn-context-srp-2.0.xsd",
95          "/schema/saml-schema-authn-context-sslcert-2.0.xsd",
96          "/schema/saml-schema-authn-context-telephony-2.0.xsd",
97          "/schema/saml-schema-authn-context-timesync-2.0.xsd",
98          "/schema/saml-schema-authn-context-types-2.0.xsd",
99          "/schema/saml-schema-authn-context-x509-2.0.xsd",
100         "/schema/saml-schema-authn-context-xmldsig-2.0.xsd",
101         "/schema/saml-schema-dce-2.0.xsd",
102         "/schema/saml-schema-ecp-2.0.xsd",
103         "/schema/saml-schema-metadata-2.0.xsd",
104         "/schema/saml-schema-protocol-2.0.xsd",
105         "/schema/saml-schema-x500-2.0.xsd",
106         "/schema/saml-schema-xacml-2.0.xsd",
107         "/schema/sstc-saml-delegation.xsd",
108         "/schema/sstc-saml-idp-discovery.xsd",
109         "/schema/sstc-saml-metadata-ext-query.xsd",
110         "/schema/sstc-saml-protocol-ext-thirdparty.xsd",
111         "/schema/sstc-saml1x-metadata.xsd",
112     };
113 
114     /** Classpath relative location of SAML extension schemas. */
115     private static String[] baseExtSchemas = { "/schema/sstc-saml-protocol-ext-thirdparty.xsd",
116             "/schema/sstc-saml-metadata-ext-query.xsd", "/schema/sstc-saml1x-metadata.xsd", };
117 
118     /** Additional schema locations relative to classpath. */
119     private static List<String> extensionSchema = new ArrayList<String>();
120 
121     /** Constructor. */
122     private SAMLSchemaBuilder() {
123 
124     }
125 
126     /**
127      * Gets a schema that can validate SAML 1.0, 2.0, and all registered extensions.
128      * 
129      * @return schema that can validate SAML 1.0, 2.0, and all registered extensions
130      * 
131      * @throws SAXException thrown if a schema object can not be created
132      */
133     public static synchronized Schema getSAML10Schema() throws SAXException {
134         if (saml10Schema == null || saml10Schema.get() == null) {
135             saml10Schema = new SoftReference<Schema>(buildSchema(saml10Schemas));
136         }
137 
138         return saml10Schema.get();
139     }
140 
141     /**
142      * Gets a schema that can validate SAML 1.1, 2.0, and all registered extensions.
143      * 
144      * @return schema that can validate SAML 1.1, 2.0, and all registered extensions
145      * 
146      * @throws SAXException thrown if a schema object can not be created
147      */
148     public static synchronized Schema getSAML11Schema() throws SAXException {
149         if (saml11Schema == null || saml11Schema.get() == null) {
150             saml11Schema = new SoftReference<Schema>(buildSchema(saml11Schemas));
151         }
152 
153         return saml11Schema.get();
154     }
155 
156     /**
157      * Gets an unmodifiable list of currently registered schema extension.
158      * 
159      * @return unmodifiable list of currently registered schema extension
160      */
161     public static List<String> getExtensionSchema() {
162         return Collections.unmodifiableList(extensionSchema);
163     }
164 
165     /**
166      * Registers a new schema extension. The schema location will be searched for on the classpath.
167      * 
168      * @param schema new schema extension
169      */
170     public static void addExtensionSchema(String schema) {
171         extensionSchema.add(schema);
172 
173         saml10Schema = null;
174 
175         saml11Schema = null;
176     }
177 
178     /**
179      * Removes a currently registered schema.
180      * 
181      * @param schema currently registered schema
182      */
183     public static void removeSchema(String schema) {
184         extensionSchema.remove(schema);
185 
186         synchronized (saml10Schema) {
187             saml10Schema = null;
188         }
189 
190         synchronized (saml11Schema) {
191             saml11Schema = null;
192         }
193     }
194 
195     /**
196      * Builds a schema object based on the given SAML 1_X schema set.
197      * 
198      * @param saml1Schema SAML 1_X schema set
199      * 
200      * @return constructed schema
201      * 
202      * @throws SAXException thrown if a schema object can not be created
203      */
204     private static Schema buildSchema(String[] saml1Schema) throws SAXException {
205         Class<SAMLSchemaBuilder> clazz = SAMLSchemaBuilder.class;
206         List<Source> schemaSources = new ArrayList<Source>();
207 
208         for (String source : baseXMLSchemas) {
209             schemaSources.add(new StreamSource(clazz.getResourceAsStream(source)));
210         }
211 
212         for (String source : soapSchemas) {
213             schemaSources.add(new StreamSource(clazz.getResourceAsStream(source)));
214         }
215 
216         for (String source : saml1Schema) {
217             schemaSources.add(new StreamSource(clazz.getResourceAsStream(source)));
218         }
219 
220         for (String source : saml20Schemas) {
221             schemaSources.add(new StreamSource(clazz.getResourceAsStream(source)));
222         }
223 
224         for (String source : baseExtSchemas) {
225             schemaSources.add(new StreamSource(clazz.getResourceAsStream(source)));
226         }
227 
228         for (String source : extensionSchema) {
229             schemaSources.add(new StreamSource(clazz.getResourceAsStream(source)));
230         }
231 
232         SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
233         schemaFactory.setResourceResolver(new ClasspathResolver());
234         schemaFactory.setErrorHandler(new LoggingErrorHandler(LoggerFactory.getLogger(clazz)));
235         return schemaFactory.newSchema(schemaSources.toArray(new StreamSource[0]));
236     }
237 }