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