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