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