Exceptions should be bubbling up from the XML parser.
[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.xmlbeans.XmlException;
147 import org.apache.xmlbeans.XmlOptions;
148 import org.opensaml.SAMLAssertion;
149 import org.opensaml.SAMLAttribute;
150 import org.opensaml.SAMLAttributeStatement;
151 import org.opensaml.SAMLException;
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.SAXException;
157
158 import x0.maceShibboleth1.AttributeAcceptancePolicyDocument;
159 import x0.maceShibbolethTargetConfig1.ApplicationDocument;
160 import x0.maceShibbolethTargetConfig1.PluggableType;
161 import x0.maceShibbolethTargetConfig1.RequestMapDocument;
162 import x0.maceShibbolethTargetConfig1.ShibbolethTargetConfigDocument;
163 import x0.maceShibbolethTargetConfig1.ApplicationDocument.Application;
164 import x0.maceShibbolethTargetConfig1.ApplicationsDocument.Applications;
165 import x0.maceShibbolethTargetConfig1.HostDocument.Host;
166 import x0.maceShibbolethTargetConfig1.PathDocument.Path;
167 import x0.maceShibbolethTargetConfig1.SHIREDocument.SHIRE;
168 import x0.maceShibbolethTargetConfig1.ShibbolethTargetConfigDocument.ShibbolethTargetConfig;
169 import edu.internet2.middleware.shibboleth.common.AAP;
170 import edu.internet2.middleware.shibboleth.common.AttributeRule;
171 import edu.internet2.middleware.shibboleth.common.Credentials;
172 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
173 import edu.internet2.middleware.shibboleth.common.XML;
174 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
175 import edu.internet2.middleware.shibboleth.metadata.EntityLocator;
176 import edu.internet2.middleware.shibboleth.metadata.Metadata;
177 import edu.internet2.middleware.shibboleth.metadata.Provider;
178 import edu.internet2.middleware.shibboleth.metadata.ProviderRole;
179 import edu.internet2.middleware.shibboleth.xml.Parser;
180
181 /**
182  * Load the configuration files into objects, index them, and return them on request.
183  * 
184  * <p>A new instance of the ServiceProviderConfig object can be created to 
185  * parse a new version of the configuration file. It can then be swapped
186  * into the ServiceProviderContext reference and will be picked up by
187  * subsequent requests.</p>
188  * 
189  * @author Howard Gilbert
190  */
191 public class ServiceProviderConfig {
192
193         
194         private static final String INLINEURN = "urn:inlineBS:ID";
195     private static Logger log = Logger.getLogger(ServiceProviderConfig.class);
196
197         private ShibbolethTargetConfig  // The XMLBean from the main config file
198                 config = null;              // (i.e. shibboleth.xml)
199         
200         
201         /*
202          * The following Maps reference objects that implement a plugin
203          * interface indexed by their URI. There are builtin objects
204          * created from inline or external XML files, but external 
205          * objects implementing the interfaces may be injected by 
206          * calling the addOrReplaceXXX method. Public access to these
207          * Maps is indirect, through methods the ApplicationInfo object
208          * for a given configured or default application.
209          */
210         
211         // Note EntityLocator extends and renames the old "Metadata" interface
212         private Map/*<String, EntityLocator>*/ entityLocators = 
213                 new TreeMap/*<String, EntityLocator>*/();
214         
215         public void addOrReplaceMetadataImplementor(String uri, EntityLocator m) {
216             entityLocators.put(uri, m);
217         }
218         
219         public EntityLocator getMetadataImplementor(String uri) {
220             return (EntityLocator) entityLocators.get(uri);
221         }
222         
223         private Map/*<String, AAP>*/ attributePolicies = 
224                 new TreeMap/*<String, AAP>*/();
225         
226         public void addOrReplaceAAPImplementor(String uri, AAP a) {
227             attributePolicies.put(uri,a);
228         }
229         
230         public AAP getAAPImplementor(String uri) {
231             return (AAP) attributePolicies.get(uri);
232         }
233         
234         private Map/*<String, ITrust>*/ certificateValidators = 
235                 new TreeMap/*<String, ITrust>*/();
236         
237         public void addOrReplaceTrustImplementor(String uri, ITrust t) {
238             certificateValidators.put(uri,t);
239         }
240         
241         public ITrust getTrustImplementor(String uri) {
242             return (ITrust) certificateValidators.get(uri);
243         }
244         
245         
246         /*
247          * Objects created from the <Application(s)> elements.
248          * They manage collections of URI-Strings that index the
249          * previous maps to return Metadata, Trust, and AAP info
250          * applicable to this applicationId.
251          */
252         private Map/*<String, ApplicationInfo>*/ applications = 
253                 new TreeMap/*<String, ApplicationInfo>*/();
254         
255         // Default application info from <Applications> element
256         private ApplicationInfo defaultApplicationInfo = null;
257
258     public ApplicationInfo getApplication(String applicationId) {
259         ApplicationInfo app=null;
260         app = (ApplicationInfo) applications.get(applicationId);
261         if (app==null)  // If no specific match, return default
262                 return defaultApplicationInfo;
263         return app;
264     }
265         
266         
267         // Objects created from single configuration file elements
268         private Credentials credentials = null;
269         private RequestMapDocument.RequestMap requestMap = null;
270         
271         
272         /*
273          * A few constants
274          */
275         private final String SCHEMADIR = "/schemas/";
276         private final String MAINSCHEMA = SCHEMADIR + XML.MAIN_SHEMA_ID;
277         private final String METADATASCHEMA = SCHEMADIR + XML.SHIB_SCHEMA_ID;
278         private final String TRUSTSCHEMA = SCHEMADIR + XML.TRUST_SCHEMA_ID;
279         private final String AAPSCHEMA = SCHEMADIR + XML.SHIB_SCHEMA_ID;
280
281         private static final String XMLTRUSTPROVIDERTYPE = 
282                 "edu.internet2.middleware.shibboleth.common.provider.XMLTrust";
283         private static final String XMLAAPPROVIDERTYPE = 
284                 "edu.internet2.middleware.shibboleth.serviceprovider.XMLAAP";
285         private static final String XMLFEDERATIONPROVIDERTYPE = 
286                 "edu.internet2.middleware.shibboleth.common.provider.XMLMetadata";
287         private static final String XMLREVOCATIONPROVIDERTYPE =
288             "edu.internet2.middleware.shibboleth.common.provider.XMLRevocation";
289         private static final String XMLREQUESTMAPPROVIDERTYPE = 
290             "edu.internet2.middleware.shibboleth.serviceprovider.XMLRequestMap";
291         private static final String XMLCREDENTIALSPROVIDERTYPE = 
292             "edu.internet2.middleware.shibboleth.common.Credentials";
293         
294         
295         
296         
297         /**
298          * The constructor prepares for, but does not parse the configuration.
299          * 
300          * @throws ShibbolethConfigurationException
301          *        if XML Parser cannot be initialized (Classpath problem)
302          */
303         public ServiceProviderConfig() {
304         }
305
306         /**
307          * loadConfigObjects must be called once to parse the configuration.
308          * 
309          * <p>To reload a modified configuration file, create and load a second 
310          * object and swap the reference in the context object.</p>
311          * 
312          * @param configFilePath URL or resource name of file
313          * @return the DOM Document
314          * @throws ShibbolethConfigurationException
315          *             if there was an error loading the file
316          */
317         public synchronized void loadConfigObjects(String configFilePath)
318                         throws ShibbolethConfigurationException {
319             
320             if (config!=null) {
321                         log.error("ServiceProviderConfig.loadConfigObjects may not be called twice for the same object.");
322                         throw new ShibbolethConfigurationException("Cannot reload configuration into same object.");
323                 }
324
325                 Document configDoc;
326         try {
327                         configDoc = Parser.loadDom(configFilePath, true);
328                 } catch (Exception e) {
329             throw new ShibbolethConfigurationException("XML error in "+configFilePath);
330         }
331         loadConfigBean(configDoc);
332
333                 return;
334         }
335         
336         /*
337          * Given a URL, determine its ApplicationId from the RequestMap config.
338          * 
339          * <p>Note: This is not a full implementation of all RequestMap
340          * configuration options. Other functions will be added as needed.</p>
341          */
342         public String mapRequest(String urlreq) {
343             String applicationId = "default";
344             URL url;
345             
346             try {
347             url = new URL(urlreq);
348         } catch (MalformedURLException e) {
349             return applicationId;
350         }
351         
352         String urlscheme = url.getProtocol();
353         String urlhostname = url.getHost();
354         String urlpath = url.getPath();
355         int urlport = url.getPort();
356         if (urlport==0) {
357             if (urlscheme.equals("http"))
358                 urlport=80;
359             else if (urlscheme.equals("https"))
360                 urlport=443;
361         }
362         
363         // find Host entry for this virtual server
364         Host[] hostArray = requestMap.getHostArray();
365         for (int ihost=0;ihost<hostArray.length;ihost++) {
366             Host host = hostArray[ihost];
367             String hostScheme = host.getScheme().toString();
368             String hostName = host.getName();
369             String hostApplicationId = host.getApplicationId();
370             long hostport = host.getPort();
371             if (hostport==0) {
372                 if (hostScheme.equals("http"))
373                     hostport=80;
374                 else if (hostScheme.equals("https"))
375                     hostport=443;
376             }
377             
378             if (!urlscheme.equals(hostScheme) ||
379                 !urlhostname.equals(hostName)||
380                 urlport!=hostport)
381                 continue;
382             
383             // find Path entry for this subdirectory
384             Path[] pathArray = host.getPathArray();
385             if (hostApplicationId!=null)
386                 applicationId=hostApplicationId;
387             for (int i=0;i<pathArray.length;i++) {
388                 String dirname = pathArray[i].getName();
389                 if (urlpath.equals(dirname)||
390                     urlpath.startsWith(dirname+"/")){
391                     String pthid= pathArray[i].getApplicationId();
392                     if (pthid!=null)
393                         applicationId=pthid;
394                 }
395             }
396         }
397             
398             return applicationId;
399         }
400
401         /**
402          * <p>Parse the main configuration file DOM into XML Bean</p>
403          * 
404          * <p>Automatically load secondary configuration files designated
405          * by URLs in the main configuration file</p>
406          * 
407          * @throws ShibbolethConfigurationException
408          */
409         private void loadConfigBean(Document configDoc) 
410                 throws ShibbolethConfigurationException {
411             boolean anyError=false;
412                 ShibbolethTargetConfigDocument configBeanDoc;
413         try {
414                         // reprocess the already validated DOM to create a bean with typed fields
415                         // dump the trash (comments, processing instructions, extra whitespace)
416                         configBeanDoc = ShibbolethTargetConfigDocument.Factory.parse(configDoc,
417                                 new XmlOptions().setLoadStripComments().setLoadStripProcinsts().setLoadStripWhitespace());
418                         config=configBeanDoc.getShibbolethTargetConfig();
419                 } catch (XmlException e) {
420                         // Since the DOM was already validated against the schema, errors will not typically occur here
421                         log.error("Error while parsing shibboleth configuration");
422                         throw new ShibbolethConfigurationException("Error while parsing shibboleth configuration");
423                 }
424                 
425                 // Extract the "root Element" object from the "Document" object
426                 ShibbolethTargetConfig config = configBeanDoc.getShibbolethTargetConfig();
427                 
428                 Applications apps = config.getApplications(); // <Applications>
429                 
430                 
431                 
432                 /*
433                  * Create an <Application> id "default" from <Applications>
434                  */
435                 ApplicationDocument defaultAppDoc = 
436                     // Create a new XMLBeans "Document" level object
437                         ApplicationDocument.Factory.newInstance(); 
438                 ApplicationDocument.Application defaultApp = 
439                     // Add an XMLBeans "root Element" object to the Document
440                         defaultAppDoc.addNewApplication();
441                 // set or copy over fields from unrelated Applications object
442                 defaultApp.setId("default");
443                 defaultApp.setAAPProviderArray(apps.getAAPProviderArray());
444                 defaultApp.setAttributeDesignatorArray(apps.getAttributeDesignatorArray());
445                 defaultApp.setAudienceArray(apps.getAudienceArray());
446                 defaultApp.setCredentialUse(apps.getCredentialUse());
447                 defaultApp.setErrors(apps.getErrors());
448                 defaultApp.setFederationProviderArray(apps.getFederationProviderArray());
449                 defaultApp.setProviderId(apps.getProviderId());
450                 defaultApp.setRevocationProviderArray(apps.getRevocationProviderArray());
451                 defaultApp.setSessions(apps.getSessions());
452                 defaultApp.setSignedAssertions(apps.getSignedAssertions());
453                 defaultApp.setSignedResponse(apps.getSignedResponse());
454                 defaultApp.setSignRequest(apps.getSignRequest());
455                 defaultApp.setTrustProviderArray(apps.getTrustProviderArray());
456                 
457                 /*
458                  * Now process secondary files configured in the applications
459                  */
460                 anyError |= processApplication(defaultApp);
461                 
462                 Application[] apparray = apps.getApplicationArray();
463                 for (int i=0;i<apparray.length;i++){
464                         Application tempapp = apparray[i];
465                         applications.put(tempapp.getId(),tempapp);
466                         anyError |= processApplication(tempapp);
467                 }
468                 
469                 /*
470                  * Now process other secondary files
471                  */
472                 anyError |= processCredentials();
473                 anyError |= processPluggableRequestMapProvider();
474                 
475                 if (anyError)
476                     throw new ShibbolethConfigurationException("Errors processing configuration file, see log");
477         }
478
479         
480         /**
481          * Routine to handle CredentialProvider
482          * 
483          * <p>Note: This only handles in-line XML.
484          * Also, Credentials was an existing Origin class, so it doesn't
485          * implement the new PluggableConfigurationComponent interface and
486          * can't be loaded by generic plugin support.
487          * </p>
488          */
489         private boolean processCredentials() {
490             boolean anyError=false;
491             PluggableType[] pluggable = config.getCredentialsProviderArray();
492             for (int i=0;i<pluggable.length;i++) {
493                         String pluggabletype = pluggable[i].getType();
494                 if (!pluggabletype.equals(
495                         "edu.internet2.middleware.shibboleth.common.Credentials")) {
496                                 log.error("Unsupported CredentialsProvider type "+pluggabletype);
497                                 anyError=true;
498                                 continue;
499                 }
500                 PluggableType credentialsProvider = pluggable[i];
501             Node fragment = credentialsProvider.newDomNode();
502             // newDomNode returns the current node wrapped in a Fragment
503             try {
504                 Node credentialsProviderNode = fragment.getFirstChild();
505                 Node credentialsNode=credentialsProviderNode.getFirstChild();
506                 credentials = new Credentials((Element)credentialsNode);
507             } catch(Exception e) {
508                 log.error("Cannot process Credentials element of Shibboleth configuration");
509                 log.error(e);
510                 anyError=true;
511                 continue;
512             }
513             }
514             return anyError;
515         }
516
517     /**
518          * Find and load secondary configuration files referenced in an Application(s) 
519          * 
520          * @param app Application object
521          * @throws ShibbolethConfigurationException
522          */
523         private boolean processApplication(Application app) 
524                 throws ShibbolethConfigurationException {
525             
526             boolean anyError=false;
527             
528             String applicationId = app.getId();
529                 
530                 ApplicationInfo appinfo = new ApplicationInfo(app);
531                 
532                 anyError |= processPluggableMetadata(appinfo);
533                 anyError |= processPluggableAAPs(appinfo);
534                 anyError |= processPluggableTrusts(appinfo);
535                 
536                 applications.put(applicationId, appinfo);
537                 
538                 return anyError;
539         }
540
541     /**
542      * Generic code to create an object of a Pluggable type that implements
543      * a configuration interface.
544      * 
545      * <p>The configuration schema defines "PluggableType" as a type of
546      * XML element that has opaque contents and attributes "type" and 
547      * "uri". If the uri attribute is omitted, then the configuration
548      * data is inline XML content. The XSD files typically define the
549      * format of pluggable configuration elements, but without binding
550      * them to the PluggableType element that may contain them.</p>
551      * 
552      * <p>The implimentation of pluggable objects is provided by 
553      * external classes. There are "builtin" classes provided with
554      * Shibboleth (XMLMetadataImpl, XMLTrustImpl, XMLAAPImpl) that 
555      * provide examples of how this is done. By design, others can
556      * provide their own classes just by putting the class name as
557      * the value of the type attribute.</p>
558      * 
559      * <p>This routine handles the common setup. It creates objects
560      * of one of the builtin types, or it uses Class.forName() to
561      * access a user specified class. It then locates either the
562      * inline XML elements or the external XML file. It passes the
563      * XML to initialize the object. Finally, a reference to the 
564      * object is stored in the appropriate Map.</p>
565      * 
566      * <p>The objects created implement two interfaces. Mostly they
567      * implement a configuration interface (EntityDescriptor, ITrust,
568      * AAP, etc). However, for the purpose of this routine they also
569      * must be declared to implement PluggableConfigurationComponent
570      * and provide an initialize() method that parses a DOM Node 
571      * containing their root XML configuration element.</p>
572      * 
573      * @param pluggable XMLBean for element defined in XSD to be of "PluggableType"
574      * @param implclass java.lang.Class of Builtin implementation class
575      * @param interfaceClass java.lang.Class of Interface
576      * @param builtinName alias type to choose Builtin imlementation
577      * @param uriMap ApplicationInfo Map for this interface
578      * @return
579      */
580     private 
581         String 
582     processPluggable(
583             PluggableType pluggable,
584                 Class implclass,
585                 Class interfaceClass,
586                 String builtinName,
587                 String schemaname,
588                 Map /*<String,PluggableConfigurationComponent>*/uriMap
589                 ) {
590         
591         String pluggabletype = pluggable.getType();
592         
593         if (!pluggabletype.equals(builtinName)) {
594             // Not the builtin type, try to load user class by name
595             try {
596                 implclass = Class.forName(pluggabletype);
597             } catch (ClassNotFoundException e) {
598                         log.error("Type value "+pluggabletype+" not found as supplied Java class");
599                     return null;
600             }
601             if (!interfaceClass.isAssignableFrom(implclass)||
602                 !PluggableConfigurationComponent.class.isAssignableFrom(implclass)) {
603                         log.error(pluggabletype+" class does not support required interfaces.");
604                     return null;
605             }
606         }
607         
608         PluggableConfigurationComponent impl;
609         try {
610             impl = (PluggableConfigurationComponent) implclass.newInstance();
611         } catch (Exception e) {
612             log.error("Unable to instantiate "+pluggabletype);
613             return null;
614         }
615         
616         String uri = pluggable.getUri();
617         if (uri==null) { // inline
618             
619                 uri=genDummyUri();
620                 try {
621                         Node fragment = pluggable.newDomNode();        // XML-Fragment node
622                         Node pluggableNode = fragment.getFirstChild(); // PluggableType 
623                         Node contentNode=pluggableNode.getFirstChild();// root element
624                         impl.initialize(contentNode);
625                 } catch (Exception e) {
626                         log.error("XML error " + e);
627                         return null;
628                 }
629                 
630         } else { // external file
631                 
632                 if (uriMap.get(uri)!=null) { // Already parsed this file
633                     return "";
634                 }
635                 
636             String tempname = impl.getSchemaPathname();
637             if (tempname!=null)
638                 schemaname=tempname;
639             
640                 try {
641                         Document extdoc = Parser.loadDom(uri,true);
642                         if (extdoc==null)
643                             return null;
644                         impl.initialize(extdoc);
645                 } catch (Exception e) {
646                         log.error("XML error " + e);
647                         return null;
648                 }
649         }
650         
651         uriMap.put(uri,impl);
652         return uri;
653     }
654         
655         
656
657         /**
658          * Handle a FederationProvider 
659          */
660         private boolean processPluggableMetadata(ApplicationInfo appinfo) {
661             boolean anyError = false;
662                 PluggableType[] pluggable = appinfo.getApplicationConfig().getFederationProviderArray();
663                 for (int i = 0;i<pluggable.length;i++) {
664                     String uri = processPluggable(pluggable[i],
665                             XMLMetadataImpl.class,
666                             EntityLocator.class,
667                             XMLFEDERATIONPROVIDERTYPE,
668                             METADATASCHEMA,
669                             entityLocators);
670                     if (uri==null)
671                         anyError=true;
672                     else if (uri.length()>0) {
673                                 appinfo.addGroupUri(uri);
674                     }
675                 }
676                 return anyError;
677         }
678         
679         /**
680          * Reload XML Metadata configuration after file changed.
681          * @param uri Path to Metadata XML configuration
682          * @return true if file reloaded.
683          */
684         public boolean reloadFederation(String uri) {
685             if (getMetadataImplementor(uri)!=null||
686                     uri.startsWith(INLINEURN))
687                 return false;
688                 try {
689                         Document sitedoc = Parser.loadDom(uri,true);
690                         if (sitedoc==null)
691                             return false;
692                         XMLMetadataImpl impl = new XMLMetadataImpl();
693                         impl.initialize(sitedoc);
694                         addOrReplaceMetadataImplementor(uri,impl);
695                 } catch (Exception e) {
696                         log.error("Error while parsing Metadata file "+uri);
697                         log.error("XML error " + e);
698                         return false;
699                 }
700             return true;
701         }
702
703         /**
704          * Handle an AAPProvider element with
705          *      type="edu.internet2.middleware.shibboleth.common.provider.XMLAAP"
706          * @throws InternalConfigurationException
707          */
708         private boolean processPluggableAAPs(ApplicationInfo appinfo){
709             boolean anyError=false;
710                 PluggableType[] pluggable = appinfo.getApplicationConfig().getAAPProviderArray();
711                 for (int i = 0;i<pluggable.length;i++) {
712                     String uri = processPluggable(pluggable[i],
713                             XMLAAPImpl.class,
714                             AAP.class,
715                             XMLAAPPROVIDERTYPE,
716                             AAPSCHEMA,
717                             attributePolicies);
718                     if (uri==null)
719                         anyError=true;
720                     else if (uri.length()>0) {
721                                 appinfo.addAapUri(uri);
722                     }
723                 }
724                 return anyError;
725         }
726         
727         /**
728          * Reload XML AAP configuration after file changed.
729          * @param uri AAP to Trust XML configuration
730          * @return true if file reloaded.
731          */
732         public boolean reloadAAP(String uri) {
733             if (getAAPImplementor(uri)!=null||
734                     uri.startsWith(INLINEURN))
735                 return false;
736                 try {
737                         Document aapdoc = Parser.loadDom(uri,true);
738                         if (aapdoc==null)
739                             return false;
740                         AttributeAcceptancePolicyDocument aap = AttributeAcceptancePolicyDocument.Factory.parse(aapdoc);
741                         XMLAAPImpl impl = new XMLAAPImpl();
742                         impl.initialize(aapdoc);
743                         addOrReplaceAAPImplementor(uri,impl);
744                 } catch (Exception e) {
745                         log.error("Error while parsing AAP file "+uri);
746                         log.error("XML error " + e);
747                         return false;
748                 }
749             return true;
750         }
751         
752         
753         /**
754          * Handle a TrustProvider element with
755          *      type="edu.internet2.middleware.shibboleth.common.provider.XMLTrust"
756          * 
757          * Note: This code builds the semantic structure of trust. That is, it knows
758          * about certificates and keys. The actual logic of Trust (signature generation
759          * and validation) is implemented in a peer object defined in the external
760          * class XMLTrustImpl.
761          * 
762          * @throws ShibbolethConfigurationException if X.509 certificate cannot be processed 
763          * @throws InternalConfigurationException
764          */
765         private boolean processPluggableTrusts(ApplicationInfo appinfo){
766             boolean anyError=false;
767                 PluggableType[] pluggable = appinfo.getApplicationConfig().getTrustProviderArray();
768                 for (int i = 0;i<pluggable.length;i++) {
769                     String uri = processPluggable(pluggable[i],
770                             XMLTrustImpl.class,
771                             ITrust.class,
772                             XMLTRUSTPROVIDERTYPE,
773                             TRUSTSCHEMA,
774                             certificateValidators);
775                     if (uri==null)
776                         anyError=true;
777                     else if (uri.length()>0) {
778                                 appinfo.addTrustUri(uri);
779                     }
780                 }
781                 return anyError;
782         }
783
784         /**
785          * Reload XML Trust configuration after file changed.
786          * @param uri Path to Trust XML configuration
787          * @return true if file reloaded.
788          */
789         public boolean reloadTrust(String uri) {
790             if (getTrustImplementor(uri)!=null||
791                     uri.startsWith(INLINEURN))
792                 return false;
793                 try {
794                         Document trustdoc = Parser.loadDom(uri,true);
795                         if (trustdoc==null)
796                             return false;
797                         XMLTrustImpl impl = new XMLTrustImpl();
798                         impl.initialize(trustdoc);
799                         addOrReplaceTrustImplementor(uri,impl);
800                 } catch (Exception e) {
801                         log.error("Error while parsing Trust file "+uri);
802                         log.error("XML error " + e);
803                         return false;
804                 }
805             return true;
806         }
807         
808         
809         private boolean processPluggableRequestMapProvider(){
810             SHIRE shire = config.getSHIRE();
811             PluggableType mapProvider = shire.getRequestMapProvider();
812             
813             String pluggabletype = mapProvider.getType();
814             if (!pluggabletype.equals(XMLREQUESTMAPPROVIDERTYPE)) {
815                 log.error("Unsupported RequestMapProvider type "+pluggabletype);
816                 return true;
817             }
818             
819             RequestMapDocument requestMapDoc = null;
820             Document mapdoc = null;
821             Element maproot = null;
822             String uri = mapProvider.getUri();
823             
824             if (uri==null) { // inline
825                 
826                 uri=genDummyUri();
827                 try {
828                     Node fragment = mapProvider.newDomNode();
829                     Node pluggableNode = fragment.getFirstChild();
830                     Node contentNode=pluggableNode.getFirstChild();
831                     
832                     requestMapDoc = RequestMapDocument.Factory.parse(contentNode);
833                 } catch (Exception e) {
834                     log.error("Error while parsing inline RequestMap");
835                     log.error("XML error " + e);
836                     return true;
837                 }
838                 
839             } else { // external file
840                 try {
841                     mapdoc = Parser.loadDom(uri,true);
842                     if (mapdoc==null)
843                         return true;
844                     requestMapDoc = RequestMapDocument.Factory.parse(mapdoc);
845                 } catch (Exception e) {
846                     log.error("Error while parsing RequestMap file "+uri);
847                     log.error("XML error " + e);
848                     return true;
849                 }
850             }
851             
852             requestMap = requestMapDoc.getRequestMap();
853             return false;
854         }
855
856         
857         
858         private int inlinenum = 1;
859         private String genDummyUri() {
860                 return INLINEURN+Integer.toString(inlinenum++);
861         }
862         
863         
864         
865
866
867
868     /**
869          * ApplicationInfo represents the <Application(s)> object, its fields,
870          * and the pluggable configuration elements under it.
871          * 
872          * <p>It can return arrays of Metadata, Trust, or AAP providers, but
873          * it also exposes convenience methods that shop the lookup(),
874          * validate(), and trust() calls to each object in the collection
875          * until success or failure is determined.</p>
876          * 
877          * <p>For all other parameters, such as Session parameters, you
878          * can fetch the XMLBean by calling getApplicationConf() and 
879          * query their value directly.
880          */
881         public class ApplicationInfo 
882                 implements EntityLocator, ITrust {
883                 
884                 private Application applicationConfig;
885         public Application getApplicationConfig() {
886             return applicationConfig;
887         }
888                 
889                 /**
890                  * Construct this object from the XML Bean.
891                  * @param application XMLBean for Application element
892                  */
893                 ApplicationInfo(Application application) {
894                     this.applicationConfig=application;
895                 }
896                 
897                 
898                 /*
899                  * Following the general rule, this object may not keep 
900                  * direct references to the plugin interface implementors,
901                  * but must look them up on every call through their URI keys.
902                  * So we keep collections of URI strings instead.
903                  */
904                 ArrayList groupUris = new ArrayList();
905                 ArrayList trustUris = new ArrayList();
906                 ArrayList aapUris   =   new ArrayList();
907                 
908         void addGroupUri(String uri) {
909                         groupUris.add(uri);
910                 }
911                 void addTrustUri(String uri) {
912                         trustUris.add(uri);
913                 }
914                 void addAapUri(String uri) {
915                         aapUris.add(uri);
916                 }
917                 
918                 /**
919                  * Return the current array of objects that implement the
920                  * ...metadata.Metadata interface
921                  * 
922                  * @return Metadata[]
923                  */
924                 Metadata[] getMetadataProviders() {
925                         Iterator iuris = groupUris.iterator();
926                         int count = groupUris.size();
927                         Metadata[] metadatas = new Metadata[count];
928                         for (int i=0;i<count;i++) {
929                                 String uri =(String) iuris.next();
930                                 metadatas[i]=getMetadataImplementor(uri);
931                         }
932                         return metadatas;
933                 }
934                 
935                 /**
936                  * A convenience function based on the Metadata interface.
937                  * 
938                  * <p>Look for an entity ID through each implementor until the
939                  * first one finds locates a describing object.</p>
940                  * 
941                  * <p>Unfortunately, Metadata.lookup() was originally specified to
942                  * return a "Provider". In current SAML 2.0 terms, the object
943                  * returned should be an EntityDescriptor. So this is the new 
944                  * function in the new interface that will use the new term, but
945                  * it does the same thing.</p>
946                  *  
947                  * @param id ID of the OriginSite entity
948                  * @return EntityDescriptor metadata object for that site.
949                  */
950                 public EntityDescriptor getEntityDescriptor(String id) {
951                         Iterator iuris = groupUris.iterator();
952                         while (iuris.hasNext()) {
953                                 String uri =(String) iuris.next();
954                                 EntityLocator locator=getMetadataImplementor(uri);
955                                 EntityDescriptor entity = locator.getEntityDescriptor(id);
956                                 if (entity!=null)
957                                         return entity;
958                         }
959                         return null;
960                 }
961                 
962                 /**
963                  * Convenience function to fulfill Metadata interface contract.
964                  * 
965                  * @param id ID of OriginSite
966                  * @return Provider object for that Site.
967                  */
968                 public Provider lookup(String id) {
969                         return getEntityDescriptor(id);
970                 }
971                 
972                 /**
973                  * Return the current array of objects that implement the ITrust interface
974                  * 
975                  * @return ITrust[]
976                  */
977                 public ITrust[] getTrustProviders() {
978                         Iterator iuris = groupUris.iterator();
979                         int count = groupUris.size();
980                         ITrust[] trusts = new ITrust[count];
981                         for (int i=0;i<count;i++) {
982                                 String uri =(String) iuris.next();
983                                 trusts[i]=getTrustImplementor(uri);
984                         }
985                         return trusts;
986                 }
987                 
988                 /**
989                  * Return the current array of objects that implement the AAP interface
990                  * 
991                  * @return AAP[]
992                  */
993                 public AAP[] getAAPProviders() {
994                         Iterator iuris = aapUris.iterator();
995                         int count = aapUris.size();
996                         AAP[] aaps = new AAP[count];
997                         for (int i=0;i<count;i++) {
998                                 String uri =(String) iuris.next();
999                                 aaps[i]=getAAPImplementor(uri);
1000                         }
1001                         return aaps;
1002                 }
1003                 
1004                 /**
1005                  * Convenience function to apply AAP by calling the apply()
1006                  * method of each AAP implementor.
1007                  * 
1008                  * <p>Any AAP implementor can delete an assertion or value.
1009                  * Empty SAML elements get removed from the assertion.
1010                  * This can yield an AttributeAssertion with no attributes. 
1011                  * 
1012                  * @param entity     Origin site that sent the assertion
1013                  * @param assertion  SAML Attribute Assertion
1014                  */
1015                 void applyAAP(EntityDescriptor entity, SAMLAssertion assertion) {
1016                     
1017                     // Foreach AAP in the collection
1018                         AAP[] providers = getAAPProviders();
1019                         for (int i=0;i<providers.length;i++) {
1020                                 AAP aap = providers[i];
1021                                 if (aap.isAnyAttribute())
1022                                         continue;
1023                                 
1024                                 // Foreach Statement in the Assertion
1025                                 Iterator statements = assertion.getStatements();
1026                                 int istatement=0;
1027                                 while (statements.hasNext()) {
1028                                         Object statement = statements.next();
1029                                         if (statement instanceof SAMLAttributeStatement) {
1030                                                 SAMLAttributeStatement attributeStatement = 
1031                                                         (SAMLAttributeStatement) statement;
1032                                                 
1033                                                 // Foreach Attribute in the AttributeStatement
1034                                                 Iterator attributes = attributeStatement.getAttributes();
1035                                                 int iattribute=0;
1036                                                 while (attributes.hasNext()) {
1037                                                         SAMLAttribute attribute = 
1038                                                                 (SAMLAttribute) attributes.next();
1039                                                         String name = attribute.getName();
1040                                                         String namespace = attribute.getNamespace();
1041                                                         AttributeRule rule = aap.lookup(name,namespace);
1042                                                         if (rule==null) {
1043                                                                 // TODO Not sure, but code appears to keep unknown attributes
1044                                                                 log.warn("No rule found for attribute "+name);
1045                                                                 iattribute++;
1046                                                                 continue;
1047                                                         }
1048                                                         rule.apply(entity,attribute);
1049                                                         if (!attribute.getValues().hasNext())
1050                                                                 attributeStatement.removeAttribute(iattribute);
1051                                                         else
1052                                                                 iattribute++;
1053                                                                 
1054                                                 }
1055                                                 if (!attributeStatement.getAttributes().hasNext())
1056                                                         assertion.removeStatement(istatement);
1057                                                 else
1058                                                         istatement++;
1059                                         } else {
1060                                                 istatement++;
1061                                         }
1062                                 }
1063                         }
1064                 }
1065                 
1066                 
1067                 /**
1068                  * Returns a collection of attribute names to request from the AA.
1069                  * 
1070                  * @return Collection of attribute Name values
1071                  */
1072                 public Collection getAttributeDesignators() {
1073                         // TODO Not sure where this should come from
1074                         return new ArrayList();
1075                 }
1076
1077                 
1078                 /**
1079                  * Convenience method implementing ITrust.validate() across 
1080                  * the collection of implementing objects. Returns true if
1081                  * any Trust implementor approves the signatures in the object.
1082                  * 
1083                  * <p>In the interface, validate() is passed several arguments
1084                  * that come from this object. In this function, those
1085                  * arguments are ignored "this" is used. 
1086                  */
1087                 public boolean 
1088                 validate(
1089                                 Iterator revocations,  // Currently unused 
1090                                 ProviderRole role,
1091                                 SAMLObject token, 
1092                                 EntityLocator dummy    // "this" is an EntityLocator 
1093                                         ) {
1094                         
1095                         // TODO If revocations are supported, "this" will provide them
1096                         
1097                         ITrust[] trustProviders = getTrustProviders();
1098                         for (int i=0;i<trustProviders.length;i++) {
1099                                 ITrust trust = trustProviders[i];
1100                                 if (trust.validate(null,role,token,this))
1101                                         return true;
1102                         }
1103                         return false;
1104                 }
1105                 
1106                 /**
1107                  * Simpler version of validate that avoids dummy arguments
1108                  * 
1109                  * @param role  Entity that sent Token (from Metadata)
1110                  * @param token Signed SAMLObject
1111                  * @return
1112                  */
1113                 public boolean validate(ProviderRole role, SAMLObject token) {
1114                         return validate(null,role,token,null);
1115                 }
1116
1117                 /**
1118                  * A method of ITrust that we must declare to claim that 
1119                  * ApplicationInfo implements ITrust. However, no code in the
1120                  * ServiceProvider calls this (probably an Origin thing).
1121                  * 
1122                  * @param revocations
1123                  * @param role
1124                  * @return  This dummy always returns false.
1125                  */
1126                 public boolean attach(Iterator revocations, ProviderRole role) {
1127                         // Unused
1128                         return false;
1129                 }
1130                 
1131         }
1132         
1133
1134         
1135         private static class InternalConfigurationException extends Exception {
1136             InternalConfigurationException() {
1137                 super();
1138             }
1139         }
1140         
1141 }