2 * ServiceProviderConfig.java
4 * A ServiceProviderConfig object holds an instance of the Shibboleth
5 * configuration data from the main configuration file and from all
6 * secondary files referenced by the main file.
8 * The configuration file is typically processed during Context
9 * initialization. In a Servlet environment, this is done from
10 * the ServletContextInitializer, while in JUnit testing it is
11 * done during test setup (unless you are testing configuration
12 * in which case it is part of the test itself). This occurs
13 * during init() processing and is inheritly synchronized.
15 * Initialization is a two step process. First create an
16 * instance of this class, then find a path to the configuration
17 * file and call loadConfigObjects().
19 * In addition to the option of loading external classes that
20 * implement one of the Plugin service interfaces by providing
21 * the name of the class in the type= attribute of the Plugin
22 * configuration XML, there is also a manual wiring interface.
23 * Create an implimenting object yourself, then add it to the
24 * configuration by passing an identifying URI and the object
25 * to a addOrReplaceXXXImplementation() method.
27 * These wiring calls are agressive, while the XML is passive.
28 * If the wiring call is made before loadConfigObject(), then
29 * XML referencing this same URI will find that it has already
30 * been loaded and use it. Alternately, if the call is made
31 * after loadConfigObject() then the XML will have processed
32 * the URI, loaded the file, and built an implimenting object.
33 * However, the wiring call will replace that object with the
34 * one provided in the call. Obviously making these calls
35 * first will be slightly more efficient, and is necessary if
36 * the XML configuration specifies URIs that will be provided
37 * by the wiring call and are not represented by any actual file.
39 * After initialization completes, this object and its arrays
40 * and collections should be structurally immutable. A map or
41 * array should not be changed by adding or removing members.
42 * Thus iteration over the collections can be unsynchronized.
43 * The reference to a value in the map can be swapped for a
44 * new object, because this doesn't effect iteration.
46 * Any method may obtain a copy of the current ServiceProviderConfig
47 * object by calling ServiceProviderContext.getServiceProviderConfig().
48 * This reference should only be held locally (for the duration
49 * of the request). Should the entire Shibboleth configuration file
50 * be reparsed (because of a dynamic update), then a new reference will
51 * be stored in the SPContext. Picking up a new reference for each
52 * request ensures that a program uses the latest configuration.
54 * When a secondary file (Metadata, Trust, AAP, etc.) is reloaded,
55 * a new object is constructed for that interface and the entry in
56 * the corresponding Map of providers of that interface is replaced.
57 * Therefore, non-local variables must only store the URI for such
58 * objects. A method can pass the URI to the Map lookup method and
59 * obtain a local variable reference to the current implementing
60 * object which can be used during the processing of the current
63 * Note: The URI for a secondary file cannot change just by
64 * reloading the file, but it can change if this main configuration
65 * file object is rebuilt. Therefore, if an external object stores
66 * a URI for a plugin object, it must be prepared for the Map lookup
67 * to return null. This would indicate that the main configuration
68 * file has been reloaded and the previously valid URI now no longer
69 * points to any implementing object.
71 * XML configuration data is parsed into two formats. First, it
72 * is processed by an ordinary JAXP XML parser into a W3C DOM format.
73 * The parser may validate the XML against an XSD schema, but the
74 * resulting DOM is "untyped". The XML becomes a tree of Element,
75 * Attribute, and Text nodes. The program must still convert an
76 * attribute or text string to a number, date, boolean, or any other
77 * data type even through the XSD declares that it must be of that
78 * type. The program must also search through the elements of the tree
79 * to find specific names for expected contents.
81 * This module then subjects the DOM to a secondary parse through
82 * some classes generated by compiling the XSD file with tools
83 * provided by the Apache XML Bean project. This turns the valid
84 * XML into strongly typed Java objects. A class is generated to
85 * represent every data type defined in the XSD schemas. Attribute
86 * values and child elements become properties of the objects of
87 * these classes. The XMLBean objects simplify the configuration
90 * If the configuration file directly reflected the program logic,
91 * the XML Bean classes would probably be enough. However, there
92 * are two important considerations:
94 * First, the Metadata is in transition. Currently we support an
95 * ad-hoc format defined by Shibboleth. However, it is expected
96 * that this will change in the next release to support a new
97 * standard accompanying SAML 2.0. The program logic anticipates
98 * this change, and is largely designed around concepts and
99 * structures of the new SAML standard. The actual configuration
100 * file and XML Beans support the old format, which must be mapped
101 * into this new structure.
103 * Second, secondary configuration elements (Credentials, Trust,
104 * Metadata, AAP, etc.) are "Pluggable" components. There is a
105 * built-in implementation of these services based on the XML
106 * configuration described in the Shibboleth documentation.
107 * However, the administrator can provide other classes that
108 * implement the required interfaces by simply coding the class
109 * name in the type= parameter of the XML element referencing the
110 * plugin. In this case we don't know the format of the opaque
111 * XML and simply pass it to the plugin.
114 * Dependencies: Requires XML Beans and the generated classes.
115 * Requires access to XSD schema files for configuration file formats.
116 * Logic depends on the order of enums in the XSD files.
118 * Error Strategy: A failure parsing the main configuration file
119 * prevents further processing. However, a problem parsing a plug-in
120 * configuration file should be noted while processing continues.
121 * This strategy reports all the errors in all the files to the log
122 * rather than stopping at the first error.
124 * --------------------
125 * Copyright 2002, 2004
126 * University Corporation for Advanced Internet Development, Inc.
127 * All rights reserved
128 * [Thats all we have to say to protect ourselves]
129 * Your permission to use this code is governed by "The Shibboleth License".
130 * A copy may be found at http://shibboleth.internet2.edu/license.html
131 * [Nothing in copyright law requires license text in every file.]
134 package edu.internet2.middleware.shibboleth.serviceprovider;
136 import java.io.IOException;
137 import java.net.MalformedURLException;
139 import java.util.ArrayList;
140 import java.util.Collection;
141 import java.util.Iterator;
142 import java.util.Map;
143 import java.util.TreeMap;
145 import org.apache.log4j.Logger;
146 import org.apache.xerces.parsers.DOMParser;
147 import org.apache.xmlbeans.XmlException;
148 import org.apache.xmlbeans.XmlOptions;
149 import org.opensaml.SAMLAssertion;
150 import org.opensaml.SAMLAttribute;
151 import org.opensaml.SAMLAttributeStatement;
152 import org.opensaml.SAMLObject;
153 import org.w3c.dom.Document;
154 import org.w3c.dom.Element;
155 import org.w3c.dom.Node;
156 import org.xml.sax.ErrorHandler;
157 import org.xml.sax.InputSource;
158 import org.xml.sax.SAXException;
159 import org.xml.sax.SAXParseException;
161 import x0.maceShibboleth1.AttributeAcceptancePolicyDocument;
162 import x0.maceShibbolethTargetConfig1.ApplicationDocument;
163 import x0.maceShibbolethTargetConfig1.PluggableType;
164 import x0.maceShibbolethTargetConfig1.RequestMapDocument;
165 import x0.maceShibbolethTargetConfig1.ShibbolethTargetConfigDocument;
166 import x0.maceShibbolethTargetConfig1.ApplicationDocument.Application;
167 import x0.maceShibbolethTargetConfig1.ApplicationsDocument.Applications;
168 import x0.maceShibbolethTargetConfig1.HostDocument.Host;
169 import x0.maceShibbolethTargetConfig1.PathDocument.Path;
170 import x0.maceShibbolethTargetConfig1.SHIREDocument.SHIRE;
171 import x0.maceShibbolethTargetConfig1.ShibbolethTargetConfigDocument.ShibbolethTargetConfig;
172 import edu.internet2.middleware.shibboleth.common.AAP;
173 import edu.internet2.middleware.shibboleth.common.AttributeRule;
174 import edu.internet2.middleware.shibboleth.common.Credentials;
175 import edu.internet2.middleware.shibboleth.common.ShibResource;
176 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
177 import edu.internet2.middleware.shibboleth.common.XML;
178 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
179 import edu.internet2.middleware.shibboleth.metadata.EntityLocator;
180 import edu.internet2.middleware.shibboleth.metadata.Metadata;
181 import edu.internet2.middleware.shibboleth.metadata.Provider;
182 import edu.internet2.middleware.shibboleth.metadata.ProviderRole;
185 * Load the configuration files into objects, index them, and return them on request.
187 * <p>A new instance of the ServiceProviderConfig object can be created to
188 * parse a new version of the configuration file. It can then be swapped
189 * into the ServiceProviderContext reference and will be picked up by
190 * subsequent requests.</p>
192 * @author Howard Gilbert
194 public class ServiceProviderConfig {
197 private static final String INLINEURN = "urn:inlineBS:ID";
198 private static Logger log = Logger.getLogger(ServiceProviderConfig.class);
200 private ShibbolethTargetConfig // The XMLBean from the main config file
201 config = null; // (i.e. shibboleth.xml)
205 * The following Maps reference objects that implement a plugin
206 * interface indexed by their URI. There are builtin objects
207 * created from inline or external XML files, but external
208 * objects implementing the interfaces may be injected by
209 * calling the addOrReplaceXXX method. Public access to these
210 * Maps is indirect, through methods the ApplicationInfo object
211 * for a given configured or default application.
214 // Note EntityLocator extends and renames the old "Metadata" interface
215 private Map/*<String, EntityLocator>*/ entityLocators =
216 new TreeMap/*<String, EntityLocator>*/();
218 public void addOrReplaceMetadataImplementor(String uri, EntityLocator m) {
219 entityLocators.put(uri, m);
222 public EntityLocator getMetadataImplementor(String uri) {
223 return (EntityLocator) entityLocators.get(uri);
226 private Map/*<String, AAP>*/ attributePolicies =
227 new TreeMap/*<String, AAP>*/();
229 public void addOrReplaceAAPImplementor(String uri, AAP a) {
230 attributePolicies.put(uri,a);
233 public AAP getAAPImplementor(String uri) {
234 return (AAP) attributePolicies.get(uri);
237 private Map/*<String, ITrust>*/ certificateValidators =
238 new TreeMap/*<String, ITrust>*/();
240 public void addOrReplaceTrustImplementor(String uri, ITrust t) {
241 certificateValidators.put(uri,t);
244 public ITrust getTrustImplementor(String uri) {
245 return (ITrust) certificateValidators.get(uri);
250 * Objects created from the <Application(s)> elements.
251 * They manage collections of URI-Strings that index the
252 * previous maps to return Metadata, Trust, and AAP info
253 * applicable to this applicationId.
255 private Map/*<String, ApplicationInfo>*/ applications =
256 new TreeMap/*<String, ApplicationInfo>*/();
258 // Default application info from <Applications> element
259 private ApplicationInfo defaultApplicationInfo = null;
261 public ApplicationInfo getApplication(String applicationId) {
262 ApplicationInfo app=null;
263 app = (ApplicationInfo) applications.get(applicationId);
264 if (app==null) // If no specific match, return default
265 return defaultApplicationInfo;
270 // Objects created from single configuration file elements
271 private Credentials credentials = null;
272 private RequestMapDocument.RequestMap requestMap = null;
278 private final String SCHEMADIR = "/schemas/";
279 private final String MAINSCHEMA = SCHEMADIR + XML.MAIN_SHEMA_ID;
280 private final String METADATASCHEMA = SCHEMADIR + XML.SHIB_SCHEMA_ID;
281 private final String TRUSTSCHEMA = SCHEMADIR + XML.TRUST_SCHEMA_ID;
282 private final String AAPSCHEMA = SCHEMADIR + XML.SHIB_SCHEMA_ID;
284 private static final String XMLTRUSTPROVIDERTYPE =
285 "edu.internet2.middleware.shibboleth.common.provider.XMLTrust";
286 private static final String XMLAAPPROVIDERTYPE =
287 "edu.internet2.middleware.shibboleth.serviceprovider.XMLAAP";
288 private static final String XMLFEDERATIONPROVIDERTYPE =
289 "edu.internet2.middleware.shibboleth.common.provider.XMLMetadata";
290 private static final String XMLREVOCATIONPROVIDERTYPE =
291 "edu.internet2.middleware.shibboleth.common.provider.XMLRevocation";
292 private static final String XMLREQUESTMAPPROVIDERTYPE =
293 "edu.internet2.middleware.shibboleth.serviceprovider.XMLRequestMap";
294 private static final String XMLCREDENTIALSPROVIDERTYPE =
295 "edu.internet2.middleware.shibboleth.common.Credentials";
299 private DOMParser parser = new DOMParser();
302 * The constructor prepares for, but does not parse the configuration.
304 * @throws ShibbolethConfigurationException
305 * if XML Parser cannot be initialized (Classpath problem)
307 public ServiceProviderConfig()
308 throws ShibbolethConfigurationException {
310 parser.setFeature("http://xml.org/sax/features/validation", true);
311 parser.setFeature("http://apache.org/xml/features/validation/schema", true);
314 parser.setErrorHandler(new ErrorHandler() {
316 public void error(SAXParseException arg0) throws SAXException {
317 throw new SAXException("Error parsing xml file: " + arg0);
320 public void fatalError(SAXParseException arg0) throws SAXException {
321 throw new SAXException("Error parsing xml file: " + arg0);
324 public void warning(SAXParseException arg0) throws SAXException {
325 throw new SAXException("Error parsing xml file: " + arg0);
329 } catch (SAXException e) {
330 log.error("Unable to setup a workable XML parser: " + e);
331 throw new ShibbolethConfigurationException("Unable to setup a workable XML parser.");
336 * loadConfigObjects must be called once to parse the configuration.
338 * <p>To reload a modified configuration file, create and load a second
339 * object and swap the reference in the context object.</p>
341 * @param configFilePath URL or resource name of file
342 * @return the DOM Document
343 * @throws ShibbolethConfigurationException
344 * if there was an error loading the file
346 public synchronized void loadConfigObjects(String configFilePath)
347 throws ShibbolethConfigurationException {
350 log.error("ServiceProviderConfig.loadConfigObjects may not be called twice for the same object.");
351 throw new ShibbolethConfigurationException("Cannot reload configuration into same object.");
356 configDoc = loadDom(configFilePath, MAINSCHEMA);
357 } catch (InternalConfigurationException e) {
358 throw new ShibbolethConfigurationException("XML error in "+configFilePath);
360 loadConfigBean(configDoc);
366 * Given a URL, determine its ApplicationId from the RequestMap config.
368 * <p>Note: This is not a full implementation of all RequestMap
369 * configuration options. Other functions will be added as needed.</p>
371 public String mapRequest(String urlreq) {
372 String applicationId = "default";
376 url = new URL(urlreq);
377 } catch (MalformedURLException e) {
378 return applicationId;
381 String urlscheme = url.getProtocol();
382 String urlhostname = url.getHost();
383 String urlpath = url.getPath();
384 int urlport = url.getPort();
386 if (urlscheme.equals("http"))
388 else if (urlscheme.equals("https"))
392 // find Host entry for this virtual server
393 Host[] hostArray = requestMap.getHostArray();
394 for (int ihost=0;ihost<hostArray.length;ihost++) {
395 Host host = hostArray[ihost];
396 String hostScheme = host.getScheme().toString();
397 String hostName = host.getName();
398 String hostApplicationId = host.getApplicationId();
399 long hostport = host.getPort();
401 if (hostScheme.equals("http"))
403 else if (hostScheme.equals("https"))
407 if (!urlscheme.equals(hostScheme) ||
408 !urlhostname.equals(hostName)||
412 // find Path entry for this subdirectory
413 Path[] pathArray = host.getPathArray();
414 if (hostApplicationId!=null)
415 applicationId=hostApplicationId;
416 for (int i=0;i<pathArray.length;i++) {
417 String dirname = pathArray[i].getName();
418 if (urlpath.equals(dirname)||
419 urlpath.startsWith(dirname+"/")){
420 String pthid= pathArray[i].getApplicationId();
427 return applicationId;
431 * <p>Parse the main configuration file DOM into XML Bean</p>
433 * <p>Automatically load secondary configuration files designated
434 * by URLs in the main configuration file</p>
436 * @throws ShibbolethConfigurationException
438 private void loadConfigBean(Document configDoc)
439 throws ShibbolethConfigurationException {
440 boolean anyError=false;
441 ShibbolethTargetConfigDocument configBeanDoc;
443 // reprocess the already validated DOM to create a bean with typed fields
444 // dump the trash (comments, processing instructions, extra whitespace)
445 configBeanDoc = ShibbolethTargetConfigDocument.Factory.parse(configDoc,
446 new XmlOptions().setLoadStripComments().setLoadStripProcinsts().setLoadStripWhitespace());
447 config=configBeanDoc.getShibbolethTargetConfig();
448 } catch (XmlException e) {
449 // Since the DOM was already validated against the schema, errors will not typically occur here
450 log.error("Error while parsing shibboleth configuration");
451 throw new ShibbolethConfigurationException("Error while parsing shibboleth configuration");
454 // Extract the "root Element" object from the "Document" object
455 ShibbolethTargetConfig config = configBeanDoc.getShibbolethTargetConfig();
457 Applications apps = config.getApplications(); // <Applications>
462 * Create an <Application> id "default" from <Applications>
464 ApplicationDocument defaultAppDoc =
465 // Create a new XMLBeans "Document" level object
466 ApplicationDocument.Factory.newInstance();
467 ApplicationDocument.Application defaultApp =
468 // Add an XMLBeans "root Element" object to the Document
469 defaultAppDoc.addNewApplication();
470 // set or copy over fields from unrelated Applications object
471 defaultApp.setId("default");
472 defaultApp.setAAPProviderArray(apps.getAAPProviderArray());
473 defaultApp.setAttributeDesignatorArray(apps.getAttributeDesignatorArray());
474 defaultApp.setAudienceArray(apps.getAudienceArray());
475 defaultApp.setCredentialUse(apps.getCredentialUse());
476 defaultApp.setErrors(apps.getErrors());
477 defaultApp.setFederationProviderArray(apps.getFederationProviderArray());
478 defaultApp.setProviderId(apps.getProviderId());
479 defaultApp.setRevocationProviderArray(apps.getRevocationProviderArray());
480 defaultApp.setSessions(apps.getSessions());
481 defaultApp.setSignedAssertions(apps.getSignedAssertions());
482 defaultApp.setSignedResponse(apps.getSignedResponse());
483 defaultApp.setSignRequest(apps.getSignRequest());
484 defaultApp.setTrustProviderArray(apps.getTrustProviderArray());
487 * Now process secondary files configured in the applications
489 anyError |= processApplication(defaultApp);
491 Application[] apparray = apps.getApplicationArray();
492 for (int i=0;i<apparray.length;i++){
493 Application tempapp = apparray[i];
494 applications.put(tempapp.getId(),tempapp);
495 anyError |= processApplication(tempapp);
499 * Now process other secondary files
501 anyError |= processCredentials();
502 anyError |= processPluggableRequestMapProvider();
505 throw new ShibbolethConfigurationException("Errors processing configuration file, see log");
510 * Routine to handle CredentialProvider
512 * <p>Note: This only handles in-line XML.
513 * Also, Credentials was an existing Origin class, so it doesn't
514 * implement the new PluggableConfigurationComponent interface and
515 * can't be loaded by generic plugin support.
518 private boolean processCredentials() {
519 boolean anyError=false;
520 PluggableType[] pluggable = config.getCredentialsProviderArray();
521 for (int i=0;i<pluggable.length;i++) {
522 String pluggabletype = pluggable[i].getType();
523 if (!pluggabletype.equals(
524 "edu.internet2.middleware.shibboleth.common.Credentials")) {
525 log.error("Unsupported CredentialsProvider type "+pluggabletype);
529 PluggableType credentialsProvider = pluggable[i];
530 Node fragment = credentialsProvider.newDomNode();
531 // newDomNode returns the current node wrapped in a Fragment
533 Node credentialsProviderNode = fragment.getFirstChild();
534 Node credentialsNode=credentialsProviderNode.getFirstChild();
535 credentials = new Credentials((Element)credentialsNode);
536 } catch(Exception e) {
537 log.error("Cannot process Credentials element of Shibboleth configuration");
547 * Find and load secondary configuration files referenced in an Application(s)
549 * @param app Application object
550 * @throws ShibbolethConfigurationException
552 private boolean processApplication(Application app)
553 throws ShibbolethConfigurationException {
555 boolean anyError=false;
557 String applicationId = app.getId();
559 ApplicationInfo appinfo = new ApplicationInfo(app);
561 anyError |= processPluggableMetadata(appinfo);
562 anyError |= processPluggableAAPs(appinfo);
563 anyError |= processPluggableTrusts(appinfo);
565 applications.put(applicationId, appinfo);
571 * Generic code to create an object of a Pluggable type that implements
572 * a configuration interface.
574 * <p>The configuration schema defines "PluggableType" as a type of
575 * XML element that has opaque contents and attributes "type" and
576 * "uri". If the uri attribute is omitted, then the configuration
577 * data is inline XML content. The XSD files typically define the
578 * format of pluggable configuration elements, but without binding
579 * them to the PluggableType element that may contain them.</p>
581 * <p>The implimentation of pluggable objects is provided by
582 * external classes. There are "builtin" classes provided with
583 * Shibboleth (XMLMetadataImpl, XMLTrustImpl, XMLAAPImpl) that
584 * provide examples of how this is done. By design, others can
585 * provide their own classes just by putting the class name as
586 * the value of the type attribute.</p>
588 * <p>This routine handles the common setup. It creates objects
589 * of one of the builtin types, or it uses Class.forName() to
590 * access a user specified class. It then locates either the
591 * inline XML elements or the external XML file. It passes the
592 * XML to initialize the object. Finally, a reference to the
593 * object is stored in the appropriate Map.</p>
595 * <p>The objects created implement two interfaces. Mostly they
596 * implement a configuration interface (EntityDescriptor, ITrust,
597 * AAP, etc). However, for the purpose of this routine they also
598 * must be declared to implement PluggableConfigurationComponent
599 * and provide an initialize() method that parses a DOM Node
600 * containing their root XML configuration element.</p>
602 * @param pluggable XMLBean for element defined in XSD to be of "PluggableType"
603 * @param implclass java.lang.Class of Builtin implementation class
604 * @param interfaceClass java.lang.Class of Interface
605 * @param builtinName alias type to choose Builtin imlementation
606 * @param uriMap ApplicationInfo Map for this interface
612 PluggableType pluggable,
614 Class interfaceClass,
617 Map /*<String,PluggableConfigurationComponent>*/uriMap
620 String pluggabletype = pluggable.getType();
622 if (!pluggabletype.equals(builtinName)) {
623 // Not the builtin type, try to load user class by name
625 implclass = Class.forName(pluggabletype);
626 } catch (ClassNotFoundException e) {
627 log.error("Type value "+pluggabletype+" not found as supplied Java class");
630 if (!interfaceClass.isAssignableFrom(implclass)||
631 !PluggableConfigurationComponent.class.isAssignableFrom(implclass)) {
632 log.error(pluggabletype+" class does not support required interfaces.");
637 PluggableConfigurationComponent impl;
639 impl = (PluggableConfigurationComponent) implclass.newInstance();
640 } catch (Exception e) {
641 log.error("Unable to instantiate "+pluggabletype);
645 String uri = pluggable.getUri();
646 if (uri==null) { // inline
650 Node fragment = pluggable.newDomNode(); // XML-Fragment node
651 Node pluggableNode = fragment.getFirstChild(); // PluggableType
652 Node contentNode=pluggableNode.getFirstChild();// root element
653 impl.initialize(contentNode);
654 } catch (Exception e) {
655 log.error("XML error " + e);
659 } else { // external file
661 if (uriMap.get(uri)!=null) { // Already parsed this file
665 String tempname = impl.getSchemaPathname();
670 Document extdoc = loadDom(uri,schemaname);
671 impl.initialize(extdoc);
672 } catch (Exception e) {
673 log.error("XML error " + e);
678 uriMap.put(uri,impl);
685 * Handle a FederationProvider
687 private boolean processPluggableMetadata(ApplicationInfo appinfo) {
688 boolean anyError = false;
689 PluggableType[] pluggable = appinfo.getApplicationConfig().getFederationProviderArray();
690 for (int i = 0;i<pluggable.length;i++) {
691 String uri = processPluggable(pluggable[i],
692 XMLMetadataImpl.class,
694 XMLFEDERATIONPROVIDERTYPE,
699 else if (uri.length()>0) {
700 appinfo.addGroupUri(uri);
707 * Reload XML Metadata configuration after file changed.
708 * @param uri Path to Metadata XML configuration
709 * @return true if file reloaded.
711 public boolean reloadFederation(String uri) {
712 if (getMetadataImplementor(uri)!=null||
713 uri.startsWith(INLINEURN))
716 Document sitedoc = loadDom(uri,METADATASCHEMA);
717 XMLMetadataImpl impl = new XMLMetadataImpl();
718 impl.initialize(sitedoc);
719 addOrReplaceMetadataImplementor(uri,impl);
720 } catch (Exception e) {
721 log.error("Error while parsing Metadata file "+uri);
722 log.error("XML error " + e);
729 * Handle an AAPProvider element with
730 * type="edu.internet2.middleware.shibboleth.common.provider.XMLAAP"
731 * @throws InternalConfigurationException
733 private boolean processPluggableAAPs(ApplicationInfo appinfo){
734 boolean anyError=false;
735 PluggableType[] pluggable = appinfo.getApplicationConfig().getAAPProviderArray();
736 for (int i = 0;i<pluggable.length;i++) {
737 String uri = processPluggable(pluggable[i],
745 else if (uri.length()>0) {
746 appinfo.addAapUri(uri);
753 * Reload XML AAP configuration after file changed.
754 * @param uri AAP to Trust XML configuration
755 * @return true if file reloaded.
757 public boolean reloadAAP(String uri) {
758 if (getAAPImplementor(uri)!=null||
759 uri.startsWith(INLINEURN))
762 Document aapdoc = loadDom(uri,AAPSCHEMA);
763 AttributeAcceptancePolicyDocument aap = AttributeAcceptancePolicyDocument.Factory.parse(aapdoc);
764 XMLAAPImpl impl = new XMLAAPImpl();
765 impl.initialize(aapdoc);
766 addOrReplaceAAPImplementor(uri,impl);
767 } catch (Exception e) {
768 log.error("Error while parsing AAP file "+uri);
769 log.error("XML error " + e);
777 * Handle a TrustProvider element with
778 * type="edu.internet2.middleware.shibboleth.common.provider.XMLTrust"
780 * Note: This code builds the semantic structure of trust. That is, it knows
781 * about certificates and keys. The actual logic of Trust (signature generation
782 * and validation) is implemented in a peer object defined in the external
783 * class XMLTrustImpl.
785 * @throws ShibbolethConfigurationException if X.509 certificate cannot be processed
786 * @throws InternalConfigurationException
788 private boolean processPluggableTrusts(ApplicationInfo appinfo){
789 boolean anyError=false;
790 PluggableType[] pluggable = appinfo.getApplicationConfig().getTrustProviderArray();
791 for (int i = 0;i<pluggable.length;i++) {
792 String uri = processPluggable(pluggable[i],
795 XMLTRUSTPROVIDERTYPE,
797 certificateValidators);
800 else if (uri.length()>0) {
801 appinfo.addTrustUri(uri);
808 * Reload XML Trust configuration after file changed.
809 * @param uri Path to Trust XML configuration
810 * @return true if file reloaded.
812 public boolean reloadTrust(String uri) {
813 if (getTrustImplementor(uri)!=null||
814 uri.startsWith(INLINEURN))
817 Document trustdoc = loadDom(uri,TRUSTSCHEMA);
818 XMLTrustImpl impl = new XMLTrustImpl();
819 impl.initialize(trustdoc);
820 addOrReplaceTrustImplementor(uri,impl);
821 } catch (Exception e) {
822 log.error("Error while parsing Trust file "+uri);
823 log.error("XML error " + e);
830 private boolean processPluggableRequestMapProvider(){
831 SHIRE shire = config.getSHIRE();
832 PluggableType mapProvider = shire.getRequestMapProvider();
834 String pluggabletype = mapProvider.getType();
835 if (!pluggabletype.equals(XMLREQUESTMAPPROVIDERTYPE)) {
836 log.error("Unsupported RequestMapProvider type "+pluggabletype);
840 RequestMapDocument requestMapDoc = null;
841 Document mapdoc = null;
842 Element maproot = null;
843 String uri = mapProvider.getUri();
845 if (uri==null) { // inline
849 Node fragment = mapProvider.newDomNode();
850 Node pluggableNode = fragment.getFirstChild();
851 Node contentNode=pluggableNode.getFirstChild();
853 requestMapDoc = RequestMapDocument.Factory.parse(contentNode);
854 } catch (Exception e) {
855 log.error("Error while parsing inline RequestMap");
856 log.error("XML error " + e);
860 } else { // external file
862 mapdoc = loadDom(uri,METADATASCHEMA);
863 requestMapDoc = RequestMapDocument.Factory.parse(mapdoc);
864 } catch (Exception e) {
865 log.error("Error while parsing RequestMap file "+uri);
866 log.error("XML error " + e);
871 requestMap = requestMapDoc.getRequestMap();
878 * Given a file URI, parse it (validated against a schema) to a DOM.
880 * <p>Note: The exact format of the URI (absolute file path, resource
881 * path, URL) is interpreted here. Changes to URI conventions can be
882 * implemented here and apply throughout the configuration system.</p>
884 * @param configFilePath Path to the particular configuration file
885 * @param schemaFilePath Path to the schema for this type of file
886 * @return DOM Document object created from the file
887 * @throws ShibbolethConfigurationException if file not found or invalid
889 private Document loadDom(String configFilePath, String schemaFilePath)
890 throws InternalConfigurationException {
892 log.debug("Loading Configuration from (" + configFilePath + ").");
895 String schemaCannonicalFilePath;
896 ShibResource configResource;
897 ShibResource schemaResource;
899 configResource = new ShibResource(configFilePath,this.getClass());
900 insrc = new InputSource(configResource.getInputStream());
901 } catch (Exception e1) {
902 log.error("Configuration file "+configFilePath+" could not be located.");
903 throw new InternalConfigurationException();
907 schemaResource = new ShibResource(schemaFilePath,this.getClass());
908 schemaCannonicalFilePath = schemaResource.getFile().getCanonicalPath();
909 } catch (Exception e) {
910 log.error("Schema file "+schemaFilePath+" could not be located.");
911 throw new InternalConfigurationException();
915 return loadDom(insrc,schemaCannonicalFilePath);
921 * Parse an InputSource (maybe file, maybe buffer) into a DOM
922 * @param insrc XML InputSource object
923 * @param schemaFilePath
924 * @return DOM Document
925 * @throws ShibbolethConfigurationException
927 private Document loadDom(InputSource insrc, String schemaFilePath)
928 throws InternalConfigurationException {
931 parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource",
936 } catch (SAXException e) {
937 log.error("Error while parsing shibboleth configuration: " + e);
938 throw new InternalConfigurationException();
939 } catch (IOException e) {
940 log.error("Could not load shibboleth configuration file: " + e);
941 throw new InternalConfigurationException();
944 return parser.getDocument();
951 private int inlinenum = 1;
952 private String genDummyUri() {
953 return INLINEURN+Integer.toString(inlinenum++);
962 * ApplicationInfo represents the <Application(s)> object, its fields,
963 * and the pluggable configuration elements under it.
965 * <p>It can return arrays of Metadata, Trust, or AAP providers, but
966 * it also exposes convenience methods that shop the lookup(),
967 * validate(), and trust() calls to each object in the collection
968 * until success or failure is determined.</p>
970 * <p>For all other parameters, such as Session parameters, you
971 * can fetch the XMLBean by calling getApplicationConf() and
972 * query their value directly.
974 class ApplicationInfo
975 implements EntityLocator, ITrust {
977 private Application applicationConfig;
978 public Application getApplicationConfig() {
979 return applicationConfig;
983 * Construct this object from the XML Bean.
984 * @param application XMLBean for Application element
986 ApplicationInfo(Application application) {
987 this.applicationConfig=application;
992 * Following the general rule, this object may not keep
993 * direct references to the plugin interface implementors,
994 * but must look them up on every call through their URI keys.
995 * So we keep collections of URI strings instead.
997 ArrayList groupUris = new ArrayList();
998 ArrayList trustUris = new ArrayList();
999 ArrayList aapUris = new ArrayList();
1001 void addGroupUri(String uri) {
1004 void addTrustUri(String uri) {
1007 void addAapUri(String uri) {
1012 * Return the current array of objects that implement the
1013 * ...metadata.Metadata interface
1015 * @return Metadata[]
1017 Metadata[] getMetadataProviders() {
1018 Iterator iuris = groupUris.iterator();
1019 int count = groupUris.size();
1020 Metadata[] metadatas = new Metadata[count];
1021 for (int i=0;i<count;i++) {
1022 String uri =(String) iuris.next();
1023 metadatas[i]=getMetadataImplementor(uri);
1029 * A convenience function based on the Metadata interface.
1031 * <p>Look for an entity ID through each implementor until the
1032 * first one finds locates a describing object.</p>
1034 * <p>Unfortunately, Metadata.lookup() was originally specified to
1035 * return a "Provider". In current SAML 2.0 terms, the object
1036 * returned should be an EntityDescriptor. So this is the new
1037 * function in the new interface that will use the new term, but
1038 * it does the same thing.</p>
1040 * @param id ID of the OriginSite entity
1041 * @return EntityDescriptor metadata object for that site.
1043 public EntityDescriptor getEntityDescriptor(String id) {
1044 Iterator iuris = groupUris.iterator();
1045 while (iuris.hasNext()) {
1046 String uri =(String) iuris.next();
1047 EntityLocator locator=getMetadataImplementor(uri);
1048 EntityDescriptor entity = locator.getEntityDescriptor(id);
1056 * Convenience function to fulfill Metadata interface contract.
1058 * @param id ID of OriginSite
1059 * @return Provider object for that Site.
1061 public Provider lookup(String id) {
1062 return getEntityDescriptor(id);
1066 * Return the current array of objects that implement the ITrust interface
1070 public ITrust[] getTrustProviders() {
1071 Iterator iuris = groupUris.iterator();
1072 int count = groupUris.size();
1073 ITrust[] trusts = new ITrust[count];
1074 for (int i=0;i<count;i++) {
1075 String uri =(String) iuris.next();
1076 trusts[i]=getTrustImplementor(uri);
1082 * Return the current array of objects that implement the AAP interface
1086 public AAP[] getAAPProviders() {
1087 Iterator iuris = aapUris.iterator();
1088 int count = aapUris.size();
1089 AAP[] aaps = new AAP[count];
1090 for (int i=0;i<count;i++) {
1091 String uri =(String) iuris.next();
1092 aaps[i]=getAAPImplementor(uri);
1098 * Convenience function to apply AAP by calling the apply()
1099 * method of each AAP implementor.
1101 * <p>Any AAP implementor can delete an assertion or value.
1102 * Empty SAML elements get removed from the assertion.
1103 * This can yield an AttributeAssertion with no attributes.
1105 * @param entity Origin site that sent the assertion
1106 * @param assertion SAML Attribute Assertion
1108 void applyAAP(EntityDescriptor entity, SAMLAssertion assertion) {
1110 // Foreach AAP in the collection
1111 AAP[] providers = getAAPProviders();
1112 for (int i=0;i<providers.length;i++) {
1113 AAP aap = providers[i];
1114 if (aap.isAnyAttribute())
1117 // Foreach Statement in the Assertion
1118 Iterator statements = assertion.getStatements();
1120 while (statements.hasNext()) {
1121 Object statement = statements.next();
1122 if (statement instanceof SAMLAttributeStatement) {
1123 SAMLAttributeStatement attributeStatement =
1124 (SAMLAttributeStatement) statement;
1126 // Foreach Attribute in the AttributeStatement
1127 Iterator attributes = attributeStatement.getAttributes();
1129 while (attributes.hasNext()) {
1130 SAMLAttribute attribute =
1131 (SAMLAttribute) attributes.next();
1132 String name = attribute.getName();
1133 String namespace = attribute.getNamespace();
1134 AttributeRule rule = aap.lookup(name,namespace);
1136 // TODO Not sure, but code appears to keep unknown attributes
1137 log.warn("No rule found for attribute "+name);
1141 rule.apply(entity,attribute);
1142 if (!attribute.getValues().hasNext())
1143 attributeStatement.removeAttribute(iattribute);
1148 if (!attributeStatement.getAttributes().hasNext())
1149 assertion.removeStatement(istatement);
1161 * Returns a collection of attribute names to request from the AA.
1163 * @return Collection of attribute Name values
1165 public Collection getAttributeDesignators() {
1166 // TODO Not sure where this should come from
1167 return new ArrayList();
1172 * Convenience method implementing ITrust.validate() across
1173 * the collection of implementing objects. Returns true if
1174 * any Trust implementor approves the signatures in the object.
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.
1182 Iterator revocations, // Currently unused
1185 EntityLocator dummy // "this" is an EntityLocator
1188 // TODO If revocations are supported, "this" will provide them
1190 ITrust[] trustProviders = getTrustProviders();
1191 for (int i=0;i<trustProviders.length;i++) {
1192 ITrust trust = trustProviders[i];
1193 if (trust.validate(null,role,token,this))
1200 * Simpler version of validate that avoids dummy arguments
1202 * @param role Entity that sent Token (from Metadata)
1203 * @param token Signed SAMLObject
1206 public boolean validate(ProviderRole role, SAMLObject token) {
1207 return validate(null,role,token,null);
1211 * A method of ITrust that we must declare to claim that
1212 * ApplicationInfo implements ITrust. However, no code in the
1213 * ServiceProvider calls this (probably an Origin thing).
1215 * @param revocations
1217 * @return This dummy always returns false.
1219 public boolean attach(Iterator revocations, ProviderRole role) {
1228 private static class InternalConfigurationException extends Exception {
1229 InternalConfigurationException() {