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