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