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