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