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