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