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