385e238f4e3674bd19ae39aaefb49dbf284df870
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / serviceprovider / ServiceProviderConfig.java
1 /*
2  * ServiceProviderConfig.java
3  * 
4  * A ServiceProviderConfig object holds an instance of the Shibboleth
5  * configuration data from the main configuration file and from all
6  * secondary files referenced by the main file.
7  * 
8  * The configuration file is typically processed during Context 
9  * initialization. In a Servlet environment, this is done from
10  * the ServletContextInitializer, while in JUnit testing it is 
11  * done during test setup (unless you are testing configuration
12  * in which case it is part of the test itself). This occurs 
13  * during init() processing and is inheritly synchronized.
14  * 
15  * Initialization is a two step process. First create an
16  * instance of this class, then find a path to the configuration
17  * file and call loadConfigObjects().
18  * 
19  * In addition to the option of loading external classes that
20  * implement one of the Plugin service interfaces by providing
21  * the name of the class in the type= attribute of the Plugin
22  * configuration XML, there is also a manual wiring interface.
23  * Create an implimenting object yourself, then add it to the
24  * configuration by passing an identifying URI and the object 
25  * to a addOrReplaceXXXImplementation() method.
26  * 
27  * These wiring calls are agressive, while the XML is passive.
28  * If the wiring call is made before loadConfigObject(), then
29  * XML referencing this same URI will find that it has already
30  * been loaded and use it. Alternately, if the call is made
31  * after loadConfigObject() then the XML will have processed
32  * the URI, loaded the file, and built an implimenting object.
33  * However, the wiring call will replace that object with the
34  * one provided in the call. Obviously making these calls 
35  * first will be slightly more efficient, and is necessary if
36  * the XML configuration specifies URIs that will be provided
37  * by the wiring call and are not represented by any actual file.
38  * 
39  * After initialization completes, this object and its arrays
40  * and collections should be structurally immutable. A map or
41  * array should not be changed by adding or removing members.
42  * Thus iteration over the collections can be unsynchronized.
43  * The reference to a value in the map can be swapped for a 
44  * new object, because this doesn't effect iteration.
45  * 
46  * Any method may obtain a copy of the current ServiceProviderConfig
47  * object by calling ServiceProviderContext.getServiceProviderConfig().
48  * This reference should only be held locally (for the duration
49  * of the request). Should the entire Shibboleth configuration file 
50  * be reparsed (because of a dynamic update), then a new reference will
51  * be stored in the SPContext. Picking up a new reference for each
52  * request ensures that a program uses the latest configuration.
53  * 
54  * When a secondary file (Metadata, Trust, AAP, etc.) is reloaded,
55  * a new object is constructed for that interface and the entry in
56  * the corresponding Map of providers of that interface is replaced.
57  * Therefore, non-local variables must only store the URI for such
58  * objects. A method can pass the URI to the Map lookup method and
59  * obtain a local variable reference to the current implementing
60  * object which can be used during the processing of the current
61  * request. 
62  * 
63  * Note: The URI for a secondary file cannot change just by 
64  * reloading the file, but it can change if this main configuration
65  * file object is rebuilt. Therefore, if an external object stores
66  * a URI for a plugin object, it must be prepared for the Map lookup
67  * to return null. This would indicate that the main configuration 
68  * file has been reloaded and the previously valid URI now no longer
69  * points to any implementing object.
70  * 
71  * XML configuration data is parsed into two formats. First, it 
72  * is processed by an ordinary JAXP XML parser into a W3C DOM format.
73  * The parser may validate the XML against an XSD schema, but the 
74  * resulting DOM is "untyped". The XML becomes a tree of Element,
75  * Attribute, and Text nodes. The program must still convert an
76  * attribute or text string to a number, date, boolean, or any other
77  * data type even through the XSD declares that it must be of that 
78  * type. The program must also search through the elements of the tree
79  * to find specific names for expected contents.
80  * 
81  * This module then subjects the DOM to a secondary parse through
82  * some classes generated by compiling the XSD file with tools 
83  * provided by the Apache XML Bean project. This turns the valid
84  * XML into strongly typed Java objects. A class is generated to
85  * represent every data type defined in the XSD schemas. Attribute
86  * values and child elements become properties of the objects of
87  * these classes. The XMLBean objects simplify the configuration 
88  * logic.
89  * 
90  * If the configuration file directly reflected the program logic,
91  * the XML Bean classes would probably be enough. However, there
92  * are two important considerations:
93  * 
94  * First, the Metadata is in transition. Currently we support an
95  * ad-hoc format defined by Shibboleth. However, it is expected
96  * that this will change in the next release to support a new
97  * standard accompanying SAML 2.0. The program logic anticipates
98  * this change, and is largely designed around concepts and 
99  * structures of the new SAML standard. The actual configuration 
100  * file and XML Beans support the old format, which must be mapped
101  * into this new structure.
102  * 
103  * Second, secondary configuration elements (Credentials, Trust,
104  * Metadata, AAP, etc.) are "Pluggable" components. There is a 
105  * built-in implementation of these services based on the XML
106  * configuration described in the Shibboleth documentation. 
107  * However, the administrator can provide other classes that 
108  * implement the required interfaces by simply coding the class
109  * name in the type= parameter of the XML element referencing the
110  * plugin. In this case we don't know the format of the opaque
111  * XML and simply pass it to the plugin. 
112  * 
113  * 
114  * Dependencies: Requires XML Beans and the generated classes.
115  * Requires access to XSD schema files for configuration file formats.
116  * Logic depends on the order of enums in the XSD files.
117  * 
118  * Error Strategy: A failure parsing the main configuration file
119  * prevents further processing. However, a problem parsing a plug-in
120  * configuration file should be noted while processing continues.
121  * This strategy reports all the errors in all the files to the log
122  * rather than stopping at the first error.
123  * 
124  * --------------------
125  * Copyright 2002, 2004 
126  * University Corporation for Advanced Internet Development, Inc. 
127  * All rights reserved
128  * [Thats all we have to say to protect ourselves]
129  * Your permission to use this code is governed by "The Shibboleth License".
130  * A copy may be found at http://shibboleth.internet2.edu/license.html
131  * [Nothing in copyright law requires license text in every file.]
132  */
133
134 package edu.internet2.middleware.shibboleth.serviceprovider;
135
136 import java.io.IOException;
137 import java.net.MalformedURLException;
138 import java.net.URL;
139 import java.util.ArrayList;
140 import java.util.Collection;
141 import java.util.Iterator;
142 import java.util.Map;
143 import java.util.TreeMap;
144
145 import org.apache.log4j.Logger;
146 import org.apache.xerces.parsers.DOMParser;
147 import org.apache.xmlbeans.XmlException;
148 import org.apache.xmlbeans.XmlOptions;
149 import org.opensaml.SAMLAssertion;
150 import org.opensaml.SAMLAttribute;
151 import org.opensaml.SAMLAttributeStatement;
152 import org.opensaml.SAMLObject;
153 import org.w3c.dom.Document;
154 import org.w3c.dom.Element;
155 import org.w3c.dom.Node;
156 import org.xml.sax.ErrorHandler;
157 import org.xml.sax.InputSource;
158 import org.xml.sax.SAXException;
159 import org.xml.sax.SAXParseException;
160
161 import x0.maceShibboleth1.AttributeAcceptancePolicyDocument;
162 import x0.maceShibbolethTargetConfig1.ApplicationDocument;
163 import x0.maceShibbolethTargetConfig1.PluggableType;
164 import x0.maceShibbolethTargetConfig1.RequestMapDocument;
165 import x0.maceShibbolethTargetConfig1.ShibbolethTargetConfigDocument;
166 import x0.maceShibbolethTargetConfig1.ApplicationDocument.Application;
167 import x0.maceShibbolethTargetConfig1.ApplicationsDocument.Applications;
168 import x0.maceShibbolethTargetConfig1.HostDocument.Host;
169 import x0.maceShibbolethTargetConfig1.PathDocument.Path;
170 import x0.maceShibbolethTargetConfig1.SHIREDocument.SHIRE;
171 import x0.maceShibbolethTargetConfig1.ShibbolethTargetConfigDocument.ShibbolethTargetConfig;
172 import edu.internet2.middleware.shibboleth.common.AAP;
173 import edu.internet2.middleware.shibboleth.common.AttributeRule;
174 import edu.internet2.middleware.shibboleth.common.Credentials;
175 import edu.internet2.middleware.shibboleth.common.ShibResource;
176 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
177 import edu.internet2.middleware.shibboleth.common.XML;
178 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
179 import edu.internet2.middleware.shibboleth.metadata.EntityLocator;
180 import edu.internet2.middleware.shibboleth.metadata.Metadata;
181 import edu.internet2.middleware.shibboleth.metadata.Provider;
182 import edu.internet2.middleware.shibboleth.metadata.ProviderRole;
183
184 /**
185  * Load the configuration files into objects, index them, and return them on request.
186  * 
187  * <p>A new instance of the ServiceProviderConfig object can be created to 
188  * parse a new version of the configuration file. It can then be swapped
189  * into the ServiceProviderContext reference and will be picked up by
190  * subsequent requests.</p>
191  * 
192  * @author Howard Gilbert
193  */
194 public class ServiceProviderConfig {
195
196         
197         private static final String INLINEURN = "urn:inlineBS:ID";
198     private static Logger log = Logger.getLogger(ServiceProviderConfig.class);
199
200         private ShibbolethTargetConfig  // The XMLBean from the main config file
201                 config = null;              // (i.e. shibboleth.xml)
202         
203         
204         /*
205          * The following Maps reference objects that implement a plugin
206          * interface indexed by their URI. There are builtin objects
207          * created from inline or external XML files, but external 
208          * objects implementing the interfaces may be injected by 
209          * calling the addOrReplaceXXX method. Public access to these
210          * Maps is indirect, through methods the ApplicationInfo object
211          * for a given configured or default application.
212          */
213         
214         // Note EntityLocator extends and renames the old "Metadata" interface
215         private Map/*<String, EntityLocator>*/ entityLocators = 
216                 new TreeMap/*<String, EntityLocator>*/();
217         
218         public void addOrReplaceMetadataImplementor(String uri, EntityLocator m) {
219             entityLocators.put(uri, m);
220         }
221         
222         public EntityLocator getMetadataImplementor(String uri) {
223             return (EntityLocator) entityLocators.get(uri);
224         }
225         
226         private Map/*<String, AAP>*/ attributePolicies = 
227                 new TreeMap/*<String, AAP>*/();
228         
229         public void addOrReplaceAAPImplementor(String uri, AAP a) {
230             attributePolicies.put(uri,a);
231         }
232         
233         public AAP getAAPImplementor(String uri) {
234             return (AAP) attributePolicies.get(uri);
235         }
236         
237         private Map/*<String, ITrust>*/ certificateValidators = 
238                 new TreeMap/*<String, ITrust>*/();
239         
240         public void addOrReplaceTrustImplementor(String uri, ITrust t) {
241             certificateValidators.put(uri,t);
242         }
243         
244         public ITrust getTrustImplementor(String uri) {
245             return (ITrust) certificateValidators.get(uri);
246         }
247         
248         
249         /*
250          * Objects created from the <Application(s)> elements.
251          * They manage collections of URI-Strings that index the
252          * previous maps to return Metadata, Trust, and AAP info
253          * applicable to this applicationId.
254          */
255         private Map/*<String, ApplicationInfo>*/ applications = 
256                 new TreeMap/*<String, ApplicationInfo>*/();
257         
258         // Default application info from <Applications> element
259         private ApplicationInfo defaultApplicationInfo = null;
260
261     public ApplicationInfo getApplication(String applicationId) {
262         ApplicationInfo app=null;
263         app = (ApplicationInfo) applications.get(applicationId);
264         if (app==null)  // If no specific match, return default
265                 return defaultApplicationInfo;
266         return app;
267     }
268         
269         
270         // Objects created from single configuration file elements
271         private Credentials credentials = null;
272         private RequestMapDocument.RequestMap requestMap = null;
273         
274         
275         /*
276          * A few constants
277          */
278         private final String SCHEMADIR = "/schemas/";
279         private final String MAINSCHEMA = SCHEMADIR + XML.MAIN_SHEMA_ID;
280         private final String METADATASCHEMA = SCHEMADIR + XML.SHIB_SCHEMA_ID;
281         private final String TRUSTSCHEMA = SCHEMADIR + XML.TRUST_SCHEMA_ID;
282         private final String AAPSCHEMA = SCHEMADIR + XML.SHIB_SCHEMA_ID;
283
284         private static final String XMLTRUSTPROVIDERTYPE = 
285                 "edu.internet2.middleware.shibboleth.common.provider.XMLTrust";
286         private static final String XMLAAPPROVIDERTYPE = 
287                 "edu.internet2.middleware.shibboleth.serviceprovider.XMLAAP";
288         private static final String XMLFEDERATIONPROVIDERTYPE = 
289                 "edu.internet2.middleware.shibboleth.common.provider.XMLMetadata";
290         private static final String XMLREVOCATIONPROVIDERTYPE =
291             "edu.internet2.middleware.shibboleth.common.provider.XMLRevocation";
292         private static final String XMLREQUESTMAPPROVIDERTYPE = 
293             "edu.internet2.middleware.shibboleth.serviceprovider.XMLRequestMap";
294         private static final String XMLCREDENTIALSPROVIDERTYPE = 
295             "edu.internet2.middleware.shibboleth.common.Credentials";
296         
297         
298         
299         private DOMParser parser = new DOMParser();
300         
301         /**
302          * The constructor prepares for, but does not parse the configuration.
303          * 
304          * @throws ShibbolethConfigurationException
305          *        if XML Parser cannot be initialized (Classpath problem)
306          */
307         public ServiceProviderConfig() 
308                 throws ShibbolethConfigurationException {
309                 try {
310                 parser.setFeature("http://xml.org/sax/features/validation", true);
311                 parser.setFeature("http://apache.org/xml/features/validation/schema", true);
312         
313         
314                 parser.setErrorHandler(new ErrorHandler() {
315         
316                         public void error(SAXParseException arg0) throws SAXException {
317                                 throw new SAXException("Error parsing xml file: " + arg0);
318                         }
319         
320                         public void fatalError(SAXParseException arg0) throws SAXException {
321                                 throw new SAXException("Error parsing xml file: " + arg0);
322                         }
323         
324                         public void warning(SAXParseException arg0) throws SAXException {
325                                 throw new SAXException("Error parsing xml file: " + arg0);
326                         }
327                 });
328         
329         } catch (SAXException e) {
330                 log.error("Unable to setup a workable XML parser: " + e);
331                 throw new ShibbolethConfigurationException("Unable to setup a workable XML parser.");
332         }
333         }
334
335         /**
336          * loadConfigObjects must be called once to parse the configuration.
337          * 
338          * <p>To reload a modified configuration file, create and load a second 
339          * object and swap the reference in the context object.</p>
340          * 
341          * @param configFilePath URL or resource name of file
342          * @return the DOM Document
343          * @throws ShibbolethConfigurationException
344          *             if there was an error loading the file
345          */
346         public synchronized void loadConfigObjects(String configFilePath)
347                         throws ShibbolethConfigurationException {
348             
349             if (config!=null) {
350                         log.error("ServiceProviderConfig.loadConfigObjects may not be called twice for the same object.");
351                         throw new ShibbolethConfigurationException("Cannot reload configuration into same object.");
352                 }
353
354                 Document configDoc;
355         try {
356             configDoc = loadDom(configFilePath, MAINSCHEMA);
357         } catch (InternalConfigurationException e) {
358             throw new ShibbolethConfigurationException("XML error in "+configFilePath);
359         }
360         loadConfigBean(configDoc);
361
362                 return;
363         }
364         
365         /*
366          * Given a URL, determine its ApplicationId from the RequestMap config.
367          * 
368          * <p>Note: This is not a full implementation of all RequestMap
369          * configuration options. Other functions will be added as needed.</p>
370          */
371         public String mapRequest(String urlreq) {
372             String applicationId = "default";
373             URL url;
374             
375             try {
376             url = new URL(urlreq);
377         } catch (MalformedURLException e) {
378             return applicationId;
379         }
380         
381         String urlscheme = url.getProtocol();
382         String urlhostname = url.getHost();
383         String urlpath = url.getPath();
384         int urlport = url.getPort();
385         if (urlport==0) {
386             if (urlscheme.equals("http"))
387                 urlport=80;
388             else if (urlscheme.equals("https"))
389                 urlport=443;
390         }
391         
392         // find Host entry for this virtual server
393         Host[] hostArray = requestMap.getHostArray();
394         for (int ihost=0;ihost<hostArray.length;ihost++) {
395             Host host = hostArray[ihost];
396             String hostScheme = host.getScheme().toString();
397             String hostName = host.getName();
398             String hostApplicationId = host.getApplicationId();
399             long hostport = host.getPort();
400             if (hostport==0) {
401                 if (hostScheme.equals("http"))
402                     hostport=80;
403                 else if (hostScheme.equals("https"))
404                     hostport=443;
405             }
406             
407             if (!urlscheme.equals(hostScheme) ||
408                 !urlhostname.equals(hostName)||
409                 urlport!=hostport)
410                 continue;
411             
412             // find Path entry for this subdirectory
413             Path[] pathArray = host.getPathArray();
414             if (hostApplicationId!=null)
415                 applicationId=hostApplicationId;
416             for (int i=0;i<pathArray.length;i++) {
417                 String dirname = pathArray[i].getName();
418                 if (urlpath.equals(dirname)||
419                     urlpath.startsWith(dirname+"/")){
420                     String pthid= pathArray[i].getApplicationId();
421                     if (pthid!=null)
422                         applicationId=pthid;
423                 }
424             }
425         }
426             
427             return applicationId;
428         }
429
430         /**
431          * <p>Parse the main configuration file DOM into XML Bean</p>
432          * 
433          * <p>Automatically load secondary configuration files designated
434          * by URLs in the main configuration file</p>
435          * 
436          * @throws ShibbolethConfigurationException
437          */
438         private void loadConfigBean(Document configDoc) 
439                 throws ShibbolethConfigurationException {
440             boolean anyError=false;
441                 ShibbolethTargetConfigDocument configBeanDoc;
442         try {
443                         // reprocess the already validated DOM to create a bean with typed fields
444                         // dump the trash (comments, processing instructions, extra whitespace)
445                         configBeanDoc = ShibbolethTargetConfigDocument.Factory.parse(configDoc,
446                                 new XmlOptions().setLoadStripComments().setLoadStripProcinsts().setLoadStripWhitespace());
447                         config=configBeanDoc.getShibbolethTargetConfig();
448                 } catch (XmlException e) {
449                         // Since the DOM was already validated against the schema, errors will not typically occur here
450                         log.error("Error while parsing shibboleth configuration");
451                         throw new ShibbolethConfigurationException("Error while parsing shibboleth configuration");
452                 }
453                 
454                 // Extract the "root Element" object from the "Document" object
455                 ShibbolethTargetConfig config = configBeanDoc.getShibbolethTargetConfig();
456                 
457                 Applications apps = config.getApplications(); // <Applications>
458                 
459                 
460                 
461                 /*
462                  * Create an <Application> id "default" from <Applications>
463                  */
464                 ApplicationDocument defaultAppDoc = 
465                     // Create a new XMLBeans "Document" level object
466                         ApplicationDocument.Factory.newInstance(); 
467                 ApplicationDocument.Application defaultApp = 
468                     // Add an XMLBeans "root Element" object to the Document
469                         defaultAppDoc.addNewApplication();
470                 // set or copy over fields from unrelated Applications object
471                 defaultApp.setId("default");
472                 defaultApp.setAAPProviderArray(apps.getAAPProviderArray());
473                 defaultApp.setAttributeDesignatorArray(apps.getAttributeDesignatorArray());
474                 defaultApp.setAudienceArray(apps.getAudienceArray());
475                 defaultApp.setCredentialUse(apps.getCredentialUse());
476                 defaultApp.setErrors(apps.getErrors());
477                 defaultApp.setFederationProviderArray(apps.getFederationProviderArray());
478                 defaultApp.setProviderId(apps.getProviderId());
479                 defaultApp.setRevocationProviderArray(apps.getRevocationProviderArray());
480                 defaultApp.setSessions(apps.getSessions());
481                 defaultApp.setSignedAssertions(apps.getSignedAssertions());
482                 defaultApp.setSignedResponse(apps.getSignedResponse());
483                 defaultApp.setSignRequest(apps.getSignRequest());
484                 defaultApp.setTrustProviderArray(apps.getTrustProviderArray());
485                 
486                 /*
487                  * Now process secondary files configured in the applications
488                  */
489                 anyError |= processApplication(defaultApp);
490                 
491                 Application[] apparray = apps.getApplicationArray();
492                 for (int i=0;i<apparray.length;i++){
493                         Application tempapp = apparray[i];
494                         applications.put(tempapp.getId(),tempapp);
495                         anyError |= processApplication(tempapp);
496                 }
497                 
498                 /*
499                  * Now process other secondary files
500                  */
501                 anyError |= processCredentials();
502                 anyError |= processPluggableRequestMapProvider();
503                 
504                 if (anyError)
505                     throw new ShibbolethConfigurationException("Errors processing configuration file, see log");
506         }
507
508         
509         /**
510          * Routine to handle CredentialProvider
511          * 
512          * <p>Note: This only handles in-line XML.
513          * Also, Credentials was an existing Origin class, so it doesn't
514          * implement the new PluggableConfigurationComponent interface and
515          * can't be loaded by generic plugin support.
516          * </p>
517          */
518         private boolean processCredentials() {
519             boolean anyError=false;
520             PluggableType[] pluggable = config.getCredentialsProviderArray();
521             for (int i=0;i<pluggable.length;i++) {
522                         String pluggabletype = pluggable[i].getType();
523                 if (!pluggabletype.equals(
524                         "edu.internet2.middleware.shibboleth.common.Credentials")) {
525                                 log.error("Unsupported CredentialsProvider type "+pluggabletype);
526                                 anyError=true;
527                                 continue;
528                 }
529                 PluggableType credentialsProvider = pluggable[i];
530             Node fragment = credentialsProvider.newDomNode();
531             // newDomNode returns the current node wrapped in a Fragment
532             try {
533                 Node credentialsProviderNode = fragment.getFirstChild();
534                 Node credentialsNode=credentialsProviderNode.getFirstChild();
535                 credentials = new Credentials((Element)credentialsNode);
536             } catch(Exception e) {
537                 log.error("Cannot process Credentials element of Shibboleth configuration");
538                 log.error(e);
539                 anyError=true;
540                 continue;
541             }
542             }
543             return anyError;
544         }
545
546     /**
547          * Find and load secondary configuration files referenced in an Application(s) 
548          * 
549          * @param app Application object
550          * @throws ShibbolethConfigurationException
551          */
552         private boolean processApplication(Application app) 
553                 throws ShibbolethConfigurationException {
554             
555             boolean anyError=false;
556             
557             String applicationId = app.getId();
558                 
559                 ApplicationInfo appinfo = new ApplicationInfo(app);
560                 
561                 anyError |= processPluggableMetadata(appinfo);
562                 anyError |= processPluggableAAPs(appinfo);
563                 anyError |= processPluggableTrusts(appinfo);
564                 
565                 applications.put(applicationId, appinfo);
566                 
567                 return anyError;
568         }
569
570     /**
571      * Generic code to create an object of a Pluggable type that implements
572      * a configuration interface.
573      * 
574      * <p>The configuration schema defines "PluggableType" as a type of
575      * XML element that has opaque contents and attributes "type" and 
576      * "uri". If the uri attribute is omitted, then the configuration
577      * data is inline XML content. The XSD files typically define the
578      * format of pluggable configuration elements, but without binding
579      * them to the PluggableType element that may contain them.</p>
580      * 
581      * <p>The implimentation of pluggable objects is provided by 
582      * external classes. There are "builtin" classes provided with
583      * Shibboleth (XMLMetadataImpl, XMLTrustImpl, XMLAAPImpl) that 
584      * provide examples of how this is done. By design, others can
585      * provide their own classes just by putting the class name as
586      * the value of the type attribute.</p>
587      * 
588      * <p>This routine handles the common setup. It creates objects
589      * of one of the builtin types, or it uses Class.forName() to
590      * access a user specified class. It then locates either the
591      * inline XML elements or the external XML file. It passes the
592      * XML to initialize the object. Finally, a reference to the 
593      * object is stored in the appropriate Map.</p>
594      * 
595      * <p>The objects created implement two interfaces. Mostly they
596      * implement a configuration interface (EntityDescriptor, ITrust,
597      * AAP, etc). However, for the purpose of this routine they also
598      * must be declared to implement PluggableConfigurationComponent
599      * and provide an initialize() method that parses a DOM Node 
600      * containing their root XML configuration element.</p>
601      * 
602      * @param pluggable XMLBean for element defined in XSD to be of "PluggableType"
603      * @param implclass java.lang.Class of Builtin implementation class
604      * @param interfaceClass java.lang.Class of Interface
605      * @param builtinName alias type to choose Builtin imlementation
606      * @param uriMap ApplicationInfo Map for this interface
607      * @return
608      */
609     private 
610         String 
611     processPluggable(
612             PluggableType pluggable,
613                 Class implclass,
614                 Class interfaceClass,
615                 String builtinName,
616                 String schemaname,
617                 Map /*<String,PluggableConfigurationComponent>*/uriMap
618                 ) {
619         
620         String pluggabletype = pluggable.getType();
621         
622         if (!pluggabletype.equals(builtinName)) {
623             // Not the builtin type, try to load user class by name
624             try {
625                 implclass = Class.forName(pluggabletype);
626             } catch (ClassNotFoundException e) {
627                         log.error("Type value "+pluggabletype+" not found as supplied Java class");
628                     return null;
629             }
630             if (!interfaceClass.isAssignableFrom(implclass)||
631                 !PluggableConfigurationComponent.class.isAssignableFrom(implclass)) {
632                         log.error(pluggabletype+" class does not support required interfaces.");
633                     return null;
634             }
635         }
636         
637         PluggableConfigurationComponent impl;
638         try {
639             impl = (PluggableConfigurationComponent) implclass.newInstance();
640         } catch (Exception e) {
641             log.error("Unable to instantiate "+pluggabletype);
642             return null;
643         }
644         
645         String uri = pluggable.getUri();
646         if (uri==null) { // inline
647             
648                 uri=genDummyUri();
649                 try {
650                         Node fragment = pluggable.newDomNode();        // XML-Fragment node
651                         Node pluggableNode = fragment.getFirstChild(); // PluggableType 
652                         Node contentNode=pluggableNode.getFirstChild();// root element
653                         impl.initialize(contentNode);
654                 } catch (Exception e) {
655                         log.error("XML error " + e);
656                         return null;
657                 }
658                 
659         } else { // external file
660                 
661                 if (uriMap.get(uri)!=null) { // Already parsed this file
662                     return "";
663                 }
664                 
665             String tempname = impl.getSchemaPathname();
666             if (tempname!=null)
667                 schemaname=tempname;
668             
669                 try {
670                         Document extdoc = loadDom(uri,schemaname);
671                         impl.initialize(extdoc);
672                 } catch (Exception e) {
673                         log.error("XML error " + e);
674                         return null;
675                 }
676         }
677         
678         uriMap.put(uri,impl);
679         return uri;
680     }
681         
682         
683
684         /**
685          * Handle a FederationProvider 
686          */
687         private boolean processPluggableMetadata(ApplicationInfo appinfo) {
688             boolean anyError = false;
689                 PluggableType[] pluggable = appinfo.getApplicationConfig().getFederationProviderArray();
690                 for (int i = 0;i<pluggable.length;i++) {
691                     String uri = processPluggable(pluggable[i],
692                             XMLMetadataImpl.class,
693                             EntityLocator.class,
694                             XMLFEDERATIONPROVIDERTYPE,
695                             METADATASCHEMA,
696                             entityLocators);
697                     if (uri==null)
698                         anyError=true;
699                     else if (uri.length()>0) {
700                                 appinfo.addGroupUri(uri);
701                     }
702                 }
703                 return anyError;
704         }
705         
706         /**
707          * Reload XML Metadata configuration after file changed.
708          * @param uri Path to Metadata XML configuration
709          * @return true if file reloaded.
710          */
711         public boolean reloadFederation(String uri) {
712             if (getMetadataImplementor(uri)!=null||
713                     uri.startsWith(INLINEURN))
714                 return false;
715                 try {
716                         Document sitedoc = loadDom(uri,METADATASCHEMA);
717                         XMLMetadataImpl impl = new XMLMetadataImpl();
718                         impl.initialize(sitedoc);
719                         addOrReplaceMetadataImplementor(uri,impl);
720                 } catch (Exception e) {
721                         log.error("Error while parsing Metadata file "+uri);
722                         log.error("XML error " + e);
723                         return false;
724                 }
725             return true;
726         }
727
728         /**
729          * Handle an AAPProvider element with
730          *      type="edu.internet2.middleware.shibboleth.common.provider.XMLAAP"
731          * @throws InternalConfigurationException
732          */
733         private boolean processPluggableAAPs(ApplicationInfo appinfo){
734             boolean anyError=false;
735                 PluggableType[] pluggable = appinfo.getApplicationConfig().getAAPProviderArray();
736                 for (int i = 0;i<pluggable.length;i++) {
737                     String uri = processPluggable(pluggable[i],
738                             XMLAAPImpl.class,
739                             AAP.class,
740                             XMLAAPPROVIDERTYPE,
741                             AAPSCHEMA,
742                             attributePolicies);
743                     if (uri==null)
744                         anyError=true;
745                     else if (uri.length()>0) {
746                                 appinfo.addAapUri(uri);
747                     }
748                 }
749                 return anyError;
750         }
751         
752         /**
753          * Reload XML AAP configuration after file changed.
754          * @param uri AAP to Trust XML configuration
755          * @return true if file reloaded.
756          */
757         public boolean reloadAAP(String uri) {
758             if (getAAPImplementor(uri)!=null||
759                     uri.startsWith(INLINEURN))
760                 return false;
761                 try {
762                         Document aapdoc = loadDom(uri,AAPSCHEMA);
763                         AttributeAcceptancePolicyDocument aap = AttributeAcceptancePolicyDocument.Factory.parse(aapdoc);
764                         XMLAAPImpl impl = new XMLAAPImpl();
765                         impl.initialize(aapdoc);
766                         addOrReplaceAAPImplementor(uri,impl);
767                 } catch (Exception e) {
768                         log.error("Error while parsing AAP file "+uri);
769                         log.error("XML error " + e);
770                         return false;
771                 }
772             return true;
773         }
774         
775         
776         /**
777          * Handle a TrustProvider element with
778          *      type="edu.internet2.middleware.shibboleth.common.provider.XMLTrust"
779          * 
780          * Note: This code builds the semantic structure of trust. That is, it knows
781          * about certificates and keys. The actual logic of Trust (signature generation
782          * and validation) is implemented in a peer object defined in the external
783          * class XMLTrustImpl.
784          * 
785          * @throws ShibbolethConfigurationException if X.509 certificate cannot be processed 
786          * @throws InternalConfigurationException
787          */
788         private boolean processPluggableTrusts(ApplicationInfo appinfo){
789             boolean anyError=false;
790                 PluggableType[] pluggable = appinfo.getApplicationConfig().getTrustProviderArray();
791                 for (int i = 0;i<pluggable.length;i++) {
792                     String uri = processPluggable(pluggable[i],
793                             XMLTrustImpl.class,
794                             ITrust.class,
795                             XMLTRUSTPROVIDERTYPE,
796                             TRUSTSCHEMA,
797                             certificateValidators);
798                     if (uri==null)
799                         anyError=true;
800                     else if (uri.length()>0) {
801                                 appinfo.addTrustUri(uri);
802                     }
803                 }
804                 return anyError;
805         }
806
807         /**
808          * Reload XML Trust configuration after file changed.
809          * @param uri Path to Trust XML configuration
810          * @return true if file reloaded.
811          */
812         public boolean reloadTrust(String uri) {
813             if (getTrustImplementor(uri)!=null||
814                     uri.startsWith(INLINEURN))
815                 return false;
816                 try {
817                         Document trustdoc = loadDom(uri,TRUSTSCHEMA);
818                         XMLTrustImpl impl = new XMLTrustImpl();
819                         impl.initialize(trustdoc);
820                         addOrReplaceTrustImplementor(uri,impl);
821                 } catch (Exception e) {
822                         log.error("Error while parsing Trust file "+uri);
823                         log.error("XML error " + e);
824                         return false;
825                 }
826             return true;
827         }
828         
829         
830         private boolean processPluggableRequestMapProvider(){
831             SHIRE shire = config.getSHIRE();
832             PluggableType mapProvider = shire.getRequestMapProvider();
833             
834             String pluggabletype = mapProvider.getType();
835             if (!pluggabletype.equals(XMLREQUESTMAPPROVIDERTYPE)) {
836                 log.error("Unsupported RequestMapProvider type "+pluggabletype);
837                 return true;
838             }
839             
840             RequestMapDocument requestMapDoc = null;
841             Document mapdoc = null;
842             Element maproot = null;
843             String uri = mapProvider.getUri();
844             
845             if (uri==null) { // inline
846                 
847                 uri=genDummyUri();
848                 try {
849                     Node fragment = mapProvider.newDomNode();
850                     Node pluggableNode = fragment.getFirstChild();
851                     Node contentNode=pluggableNode.getFirstChild();
852                     
853                     requestMapDoc = RequestMapDocument.Factory.parse(contentNode);
854                 } catch (Exception e) {
855                     log.error("Error while parsing inline RequestMap");
856                     log.error("XML error " + e);
857                     return true;
858                 }
859                 
860             } else { // external file
861                 try {
862                     mapdoc = loadDom(uri,METADATASCHEMA);
863                     requestMapDoc = RequestMapDocument.Factory.parse(mapdoc);
864                 } catch (Exception e) {
865                     log.error("Error while parsing RequestMap file "+uri);
866                     log.error("XML error " + e);
867                     return true;
868                 }
869             }
870             
871             requestMap = requestMapDoc.getRequestMap();
872             return false;
873         }
874
875         
876         
877         /**
878          * Given a file URI, parse it (validated against a schema) to a DOM.
879          * 
880          * <p>Note: The exact format of the URI (absolute file path, resource
881          * path, URL) is interpreted here. Changes to URI conventions can be
882          * implemented here and apply throughout the configuration system.</p>
883          *
884          * @param configFilePath Path to the particular configuration file
885          * @param schemaFilePath Path to the schema for this type of file 
886          * @return DOM Document object created from the file
887          * @throws ShibbolethConfigurationException if file not found or invalid
888          */
889         private Document loadDom(String configFilePath, String schemaFilePath) 
890                 throws InternalConfigurationException {
891                 
892                 log.debug("Loading Configuration from (" + configFilePath + ").");
893
894                         InputSource insrc;
895                         String schemaCannonicalFilePath;
896                         ShibResource configResource;
897                         ShibResource schemaResource;
898                         try {
899                                 configResource = new ShibResource(configFilePath,this.getClass());
900                                 insrc = new InputSource(configResource.getInputStream());
901                         } catch (Exception e1) {
902                                 log.error("Configuration file "+configFilePath+" could not be located.");
903                                 throw new InternalConfigurationException();
904                         }
905                         
906                         try {
907                                 schemaResource = new ShibResource(schemaFilePath,this.getClass());
908                 schemaCannonicalFilePath = schemaResource.getFile().getCanonicalPath();
909                         } catch (Exception e) {
910                                 log.error("Schema file "+schemaFilePath+" could not be located.");
911                                 throw new InternalConfigurationException();
912                         }
913                         
914                         
915                 return loadDom(insrc,schemaCannonicalFilePath);
916                 
917         }
918
919         
920         /**
921          * Parse an InputSource (maybe file, maybe buffer) into a DOM
922          * @param insrc XML InputSource object
923          * @param schemaFilePath
924          * @return DOM Document
925          * @throws ShibbolethConfigurationException
926          */
927         private Document loadDom(InputSource insrc, String schemaFilePath) 
928                 throws InternalConfigurationException {
929         
930                 try {   
931                         parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource",
932                                         schemaFilePath);
933                         
934                         parser.parse(insrc);
935                         
936                 } catch (SAXException e) {
937                         log.error("Error while parsing shibboleth configuration: " + e);
938                         throw new InternalConfigurationException();
939                 } catch (IOException e) {
940                         log.error("Could not load shibboleth configuration file: " + e);
941                         throw new InternalConfigurationException();
942                 }
943         
944                 return parser.getDocument();
945                 
946         }
947
948         
949         
950
951         private int inlinenum = 1;
952         private String genDummyUri() {
953                 return INLINEURN+Integer.toString(inlinenum++);
954         }
955         
956         
957         
958
959
960
961     /**
962          * ApplicationInfo represents the <Application(s)> object, its fields,
963          * and the pluggable configuration elements under it.
964          * 
965          * <p>It can return arrays of Metadata, Trust, or AAP providers, but
966          * it also exposes convenience methods that shop the lookup(),
967          * validate(), and trust() calls to each object in the collection
968          * until success or failure is determined.</p>
969          * 
970          * <p>For all other parameters, such as Session parameters, you
971          * can fetch the XMLBean by calling getApplicationConf() and 
972          * query their value directly.
973          */
974         class ApplicationInfo 
975                 implements EntityLocator, ITrust {
976                 
977                 private Application applicationConfig;
978         public Application getApplicationConfig() {
979             return applicationConfig;
980         }
981                 
982                 /**
983                  * Construct this object from the XML Bean.
984                  * @param application XMLBean for Application element
985                  */
986                 ApplicationInfo(Application application) {
987                     this.applicationConfig=application;
988                 }
989                 
990                 
991                 /*
992                  * Following the general rule, this object may not keep 
993                  * direct references to the plugin interface implementors,
994                  * but must look them up on every call through their URI keys.
995                  * So we keep collections of URI strings instead.
996                  */
997                 ArrayList groupUris = new ArrayList();
998                 ArrayList trustUris = new ArrayList();
999                 ArrayList aapUris   =   new ArrayList();
1000                 
1001         void addGroupUri(String uri) {
1002                         groupUris.add(uri);
1003                 }
1004                 void addTrustUri(String uri) {
1005                         trustUris.add(uri);
1006                 }
1007                 void addAapUri(String uri) {
1008                         aapUris.add(uri);
1009                 }
1010                 
1011                 /**
1012                  * Return the current array of objects that implement the
1013                  * ...metadata.Metadata interface
1014                  * 
1015                  * @return Metadata[]
1016                  */
1017                 Metadata[] getMetadataProviders() {
1018                         Iterator iuris = groupUris.iterator();
1019                         int count = groupUris.size();
1020                         Metadata[] metadatas = new Metadata[count];
1021                         for (int i=0;i<count;i++) {
1022                                 String uri =(String) iuris.next();
1023                                 metadatas[i]=getMetadataImplementor(uri);
1024                         }
1025                         return metadatas;
1026                 }
1027                 
1028                 /**
1029                  * A convenience function based on the Metadata interface.
1030                  * 
1031                  * <p>Look for an entity ID through each implementor until the
1032                  * first one finds locates a describing object.</p>
1033                  * 
1034                  * <p>Unfortunately, Metadata.lookup() was originally specified to
1035                  * return a "Provider". In current SAML 2.0 terms, the object
1036                  * returned should be an EntityDescriptor. So this is the new 
1037                  * function in the new interface that will use the new term, but
1038                  * it does the same thing.</p>
1039                  *  
1040                  * @param id ID of the OriginSite entity
1041                  * @return EntityDescriptor metadata object for that site.
1042                  */
1043                 public EntityDescriptor getEntityDescriptor(String id) {
1044                         Iterator iuris = groupUris.iterator();
1045                         while (iuris.hasNext()) {
1046                                 String uri =(String) iuris.next();
1047                                 EntityLocator locator=getMetadataImplementor(uri);
1048                                 EntityDescriptor entity = locator.getEntityDescriptor(id);
1049                                 if (entity!=null)
1050                                         return entity;
1051                         }
1052                         return null;
1053                 }
1054                 
1055                 /**
1056                  * Convenience function to fulfill Metadata interface contract.
1057                  * 
1058                  * @param id ID of OriginSite
1059                  * @return Provider object for that Site.
1060                  */
1061                 public Provider lookup(String id) {
1062                         return getEntityDescriptor(id);
1063                 }
1064                 
1065                 /**
1066                  * Return the current array of objects that implement the ITrust interface
1067                  * 
1068                  * @return ITrust[]
1069                  */
1070                 public ITrust[] getTrustProviders() {
1071                         Iterator iuris = groupUris.iterator();
1072                         int count = groupUris.size();
1073                         ITrust[] trusts = new ITrust[count];
1074                         for (int i=0;i<count;i++) {
1075                                 String uri =(String) iuris.next();
1076                                 trusts[i]=getTrustImplementor(uri);
1077                         }
1078                         return trusts;
1079                 }
1080                 
1081                 /**
1082                  * Return the current array of objects that implement the AAP interface
1083                  * 
1084                  * @return AAP[]
1085                  */
1086                 public AAP[] getAAPProviders() {
1087                         Iterator iuris = aapUris.iterator();
1088                         int count = aapUris.size();
1089                         AAP[] aaps = new AAP[count];
1090                         for (int i=0;i<count;i++) {
1091                                 String uri =(String) iuris.next();
1092                                 aaps[i]=getAAPImplementor(uri);
1093                         }
1094                         return aaps;
1095                 }
1096                 
1097                 /**
1098                  * Convenience function to apply AAP by calling the apply()
1099                  * method of each AAP implementor.
1100                  * 
1101                  * <p>Any AAP implementor can delete an assertion or value.
1102                  * Empty SAML elements get removed from the assertion.
1103                  * This can yield an AttributeAssertion with no attributes. 
1104                  * 
1105                  * @param entity     Origin site that sent the assertion
1106                  * @param assertion  SAML Attribute Assertion
1107                  */
1108                 void applyAAP(EntityDescriptor entity, SAMLAssertion assertion) {
1109                     
1110                     // Foreach AAP in the collection
1111                         AAP[] providers = getAAPProviders();
1112                         for (int i=0;i<providers.length;i++) {
1113                                 AAP aap = providers[i];
1114                                 if (aap.isAnyAttribute())
1115                                         continue;
1116                                 
1117                                 // Foreach Statement in the Assertion
1118                                 Iterator statements = assertion.getStatements();
1119                                 int istatement=0;
1120                                 while (statements.hasNext()) {
1121                                         Object statement = statements.next();
1122                                         if (statement instanceof SAMLAttributeStatement) {
1123                                                 SAMLAttributeStatement attributeStatement = 
1124                                                         (SAMLAttributeStatement) statement;
1125                                                 
1126                                                 // Foreach Attribute in the AttributeStatement
1127                                                 Iterator attributes = attributeStatement.getAttributes();
1128                                                 int iattribute=0;
1129                                                 while (attributes.hasNext()) {
1130                                                         SAMLAttribute attribute = 
1131                                                                 (SAMLAttribute) attributes.next();
1132                                                         String name = attribute.getName();
1133                                                         String namespace = attribute.getNamespace();
1134                                                         AttributeRule rule = aap.lookup(name,namespace);
1135                                                         if (rule==null) {
1136                                                                 // TODO Not sure, but code appears to keep unknown attributes
1137                                                                 log.warn("No rule found for attribute "+name);
1138                                                                 iattribute++;
1139                                                                 continue;
1140                                                         }
1141                                                         rule.apply(entity,attribute);
1142                                                         if (!attribute.getValues().hasNext())
1143                                                                 attributeStatement.removeAttribute(iattribute);
1144                                                         else
1145                                                                 iattribute++;
1146                                                                 
1147                                                 }
1148                                                 if (!attributeStatement.getAttributes().hasNext())
1149                                                         assertion.removeStatement(istatement);
1150                                                 else
1151                                                         istatement++;
1152                                         } else {
1153                                                 istatement++;
1154                                         }
1155                                 }
1156                         }
1157                 }
1158                 
1159                 
1160                 /**
1161                  * Returns a collection of attribute names to request from the AA.
1162                  * 
1163                  * @return Collection of attribute Name values
1164                  */
1165                 public Collection getAttributeDesignators() {
1166                         // TODO Not sure where this should come from
1167                         return new ArrayList();
1168                 }
1169
1170                 
1171                 /**
1172                  * Convenience method implementing ITrust.validate() across 
1173                  * the collection of implementing objects. Returns true if
1174                  * any Trust implementor approves the signatures in the object.
1175                  * 
1176                  * <p>In the interface, validate() is passed several arguments
1177                  * that come from this object. In this function, those
1178                  * arguments are ignored "this" is used. 
1179                  */
1180                 public boolean 
1181                 validate(
1182                                 Iterator revocations,  // Currently unused 
1183                                 ProviderRole role,
1184                                 SAMLObject token, 
1185                                 EntityLocator dummy    // "this" is an EntityLocator 
1186                                         ) {
1187                         
1188                         // TODO If revocations are supported, "this" will provide them
1189                         
1190                         ITrust[] trustProviders = getTrustProviders();
1191                         for (int i=0;i<trustProviders.length;i++) {
1192                                 ITrust trust = trustProviders[i];
1193                                 if (trust.validate(null,role,token,this))
1194                                         return true;
1195                         }
1196                         return false;
1197                 }
1198                 
1199                 /**
1200                  * Simpler version of validate that avoids dummy arguments
1201                  * 
1202                  * @param role  Entity that sent Token (from Metadata)
1203                  * @param token Signed SAMLObject
1204                  * @return
1205                  */
1206                 public boolean validate(ProviderRole role, SAMLObject token) {
1207                         return validate(null,role,token,null);
1208                 }
1209
1210                 /**
1211                  * A method of ITrust that we must declare to claim that 
1212                  * ApplicationInfo implements ITrust. However, no code in the
1213                  * ServiceProvider calls this (probably an Origin thing).
1214                  * 
1215                  * @param revocations
1216                  * @param role
1217                  * @return  This dummy always returns false.
1218                  */
1219                 public boolean attach(Iterator revocations, ProviderRole role) {
1220                         // Unused
1221                         return false;
1222                 }
1223                 
1224         }
1225         
1226
1227         
1228         private static class InternalConfigurationException extends Exception {
1229             InternalConfigurationException() {
1230                 super();
1231             }
1232         }
1233         
1234 }