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