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.net.MalformedURLException;
138 import java.security.cert.X509Certificate;
139 import java.util.ArrayList;
140 import java.util.Collection;
141 import java.util.Iterator;
142 import java.util.Map;
143 import java.util.TreeMap;
145 import org.apache.log4j.Logger;
146 import org.apache.xmlbeans.XmlException;
147 import org.apache.xmlbeans.XmlOptions;
148 import org.opensaml.SAMLAssertion;
149 import org.opensaml.SAMLAttribute;
150 import org.opensaml.SAMLAttributeStatement;
151 import org.opensaml.SAMLException;
152 import org.opensaml.SAMLSignedObject;
153 import org.opensaml.artifact.Artifact;
154 import org.w3c.dom.Document;
155 import org.w3c.dom.Element;
156 import org.w3c.dom.Node;
158 import x0.maceShibboleth1.AttributeAcceptancePolicyDocument;
159 import x0.maceShibbolethTargetConfig1.ApplicationDocument;
160 import x0.maceShibbolethTargetConfig1.LocalConfigurationType;
161 import x0.maceShibbolethTargetConfig1.PluggableType;
162 import x0.maceShibbolethTargetConfig1.RequestMapDocument;
163 import x0.maceShibbolethTargetConfig1.SPConfigDocument;
164 import x0.maceShibbolethTargetConfig1.SPConfigType;
165 import x0.maceShibbolethTargetConfig1.ShibbolethTargetConfigDocument;
166 import x0.maceShibbolethTargetConfig1.ApplicationDocument.Application;
167 import x0.maceShibbolethTargetConfig1.ApplicationsDocument.Applications;
168 import x0.maceShibbolethTargetConfig1.HostDocument.Host;
169 import x0.maceShibbolethTargetConfig1.PathDocument.Path;
170 import edu.internet2.middleware.shibboleth.aap.AAP;
171 import edu.internet2.middleware.shibboleth.aap.AttributeRule;
172 import edu.internet2.middleware.shibboleth.common.Credentials;
173 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
174 import edu.internet2.middleware.shibboleth.common.Trust;
175 import edu.internet2.middleware.shibboleth.common.provider.ShibbolethTrust;
176 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
177 import edu.internet2.middleware.shibboleth.metadata.Metadata;
178 import edu.internet2.middleware.shibboleth.metadata.RoleDescriptor;
179 import edu.internet2.middleware.shibboleth.xml.Parser;
182 * Load the configuration files into objects, index them, and return them on request.
184 * <p>A new instance of the ServiceProviderConfig object can be created to
185 * parse a new version of the configuration file. It can then be swapped
186 * into the ServiceProviderContext reference and will be picked up by
187 * subsequent requests.</p>
189 * @author Howard Gilbert
191 public class ServiceProviderConfig {
193 // Map key prefix for inline plugin configuration elements
194 private static final String INLINEURN = "urn:inlineBS:ID";
196 private static Logger log = Logger.getLogger(ServiceProviderConfig.class);
198 private SPConfigType // The XMLBean from the main config file
203 * The following Maps reference objects that implement a plugin
204 * interface indexed by their URI. There are builtin objects
205 * created from inline or external XML files, but external
206 * objects implementing the interfaces may be injected by
207 * calling the addOrReplaceXXX method. Public access to these
208 * Maps is indirect, through methods the ApplicationInfo object
209 * for a given configured or default application.
212 private Map/*<String, Metadata>*/ entityLocators =
213 new TreeMap/*<String, Metadata>*/();
215 public void addOrReplaceMetadataImplementor(String uri, Metadata m) {
216 entityLocators.put(uri, m);
219 public Metadata getMetadataImplementor(String uri) {
220 return (Metadata)entityLocators.get(uri);
223 private Map/*<String, AAP>*/ attributePolicies =
224 new TreeMap/*<String, AAP>*/();
226 public void addOrReplaceAAPImplementor(String uri, AAP a) {
227 attributePolicies.put(uri,a);
230 public AAP getAAPImplementor(String uri) {
231 return (AAP) attributePolicies.get(uri);
234 private Map/*<String, Trust>*/ certificateValidators =
235 new TreeMap/*<String, Trust>*/();
237 public void addOrReplaceTrustImplementor(String uri, Trust t) {
238 certificateValidators.put(uri,t);
241 public Trust getTrustImplementor(String uri) {
242 return (Trust) certificateValidators.get(uri);
245 private Trust[] defaultTrust = {new ShibbolethTrust()};
248 * Objects created from the <Application(s)> elements.
249 * They manage collections of URI-Strings that index the
250 * previous maps to return Metadata, Trust, and AAP info
251 * applicable to this applicationId.
253 private Map/*<String, ApplicationInfo>*/ applications =
254 new TreeMap/*<String, ApplicationInfo>*/();
256 // Default application info from <Applications> element
257 private ApplicationInfo defaultApplicationInfo = null;
259 public ApplicationInfo getApplication(String applicationId) {
260 ApplicationInfo app=null;
261 app = (ApplicationInfo) applications.get(applicationId);
262 if (app==null) // If no specific match, return default
263 return defaultApplicationInfo;
268 // Objects created from single configuration file elements
269 private Credentials credentials = null;
270 private RequestMapDocument.RequestMap requestMap = null;
277 private static final String XMLTRUSTPROVIDERTYPE =
278 "edu.internet2.middleware.shibboleth.common.provider.XMLTrust";
279 private static final String XMLAAPPROVIDERTYPE =
280 "edu.internet2.middleware.shibboleth.serviceprovider.XMLAAP";
281 private static final String XMLFEDERATIONPROVIDERTYPE =
282 "edu.internet2.middleware.shibboleth.common.provider.XMLMetadata";
283 private static final String XMLREVOCATIONPROVIDERTYPE =
284 "edu.internet2.middleware.shibboleth.common.provider.XMLRevocation";
285 private static final String XMLREQUESTMAPPROVIDERTYPE =
286 "edu.internet2.middleware.shibboleth.serviceprovider.XMLRequestMap";
287 private static final String XMLCREDENTIALSPROVIDERTYPE =
288 "edu.internet2.middleware.shibboleth.common.Credentials";
294 * The constructor prepares for, but does not parse the configuration.
296 * @throws ShibbolethConfigurationException
297 * if XML Parser cannot be initialized (Classpath problem)
299 public ServiceProviderConfig() {
303 * loadConfigObjects must be called once to parse the configuration.
305 * <p>To reload a modified configuration file, create and load a second
306 * object and swap the reference in the context object.</p>
308 * @param configFilePath URL or resource name of file
309 * @return the DOM Document
310 * @throws ShibbolethConfigurationException
311 * if there was an error loading the file
313 public synchronized void loadConfigObjects(String configFilePath)
314 throws ShibbolethConfigurationException {
317 log.error("ServiceProviderConfig.loadConfigObjects may not be called twice for the same object.");
318 throw new ShibbolethConfigurationException("Cannot reload configuration into same object.");
323 configDoc = Parser.loadDom(configFilePath, true);
324 } catch (Exception e) {
325 throw new ShibbolethConfigurationException("XML error in "+configFilePath);
327 loadConfigBean(configDoc);
333 * Given a URL, determine its ApplicationId from the RequestMap config.
335 * <p>Note: This is not a full implementation of all RequestMap
336 * configuration options. Other functions will be added as needed.</p>
338 public String mapRequest(String urlreq) {
339 String applicationId = "default";
343 url = new URL(urlreq);
344 } catch (MalformedURLException e) {
345 return applicationId;
348 String urlscheme = url.getProtocol();
349 String urlhostname = url.getHost();
350 String urlpath = url.getPath();
351 int urlport = url.getPort();
353 if (urlscheme.equals("http"))
355 else if (urlscheme.equals("https"))
359 // find Host entry for this virtual server
360 Host[] hostArray = requestMap.getHostArray();
361 for (int ihost=0;ihost<hostArray.length;ihost++) {
362 Host host = hostArray[ihost];
363 String hostScheme = host.getScheme().toString();
364 String hostName = host.getName();
365 String hostApplicationId = host.getApplicationId();
366 long hostport = host.getPort();
368 if (hostScheme.equals("http"))
370 else if (hostScheme.equals("https"))
374 if (!urlscheme.equals(hostScheme) ||
375 !urlhostname.equals(hostName)||
379 // find Path entry for this subdirectory
380 Path[] pathArray = host.getPathArray();
381 if (hostApplicationId!=null)
382 applicationId=hostApplicationId;
383 for (int i=0;i<pathArray.length;i++) {
384 String dirname = pathArray[i].getName();
385 if (urlpath.equals(dirname)||
386 urlpath.startsWith(dirname+"/")){
387 String pthid= pathArray[i].getApplicationId();
394 return applicationId;
398 * <p>Parse the main configuration file DOM into XML Bean</p>
400 * <p>Automatically load secondary configuration files designated
401 * by URLs in the main configuration file</p>
403 * @throws ShibbolethConfigurationException
405 private void loadConfigBean(Document configDoc)
406 throws ShibbolethConfigurationException {
407 boolean anyError=false;
409 Element documentElement = configDoc.getDocumentElement();
410 // reprocess the already validated DOM to create a bean with typed fields
411 // dump the trash (comments, processing instructions, extra whitespace)
414 if (documentElement.getLocalName().equals("ShibbolethTargetConfig")) {
415 ShibbolethTargetConfigDocument configBeanDoc;
416 configBeanDoc = ShibbolethTargetConfigDocument.Factory.parse(configDoc,
417 new XmlOptions().setLoadStripComments().setLoadStripProcinsts().setLoadStripWhitespace());
418 config = configBeanDoc.getShibbolethTargetConfig();
419 } else if (documentElement.getLocalName().equals("SPConfig")) {
420 SPConfigDocument configBeanDoc;
421 configBeanDoc = SPConfigDocument.Factory.parse(configDoc,
422 new XmlOptions().setLoadStripComments().setLoadStripProcinsts().setLoadStripWhitespace());
423 config = configBeanDoc.getSPConfig();
425 throw new XmlException("Root element not ShibbolethTargetConfig or SPConfig");
427 } catch (XmlException e) {
428 // Since the DOM was already validated against the schema, errors will not typically occur here
429 log.error("Error while parsing shibboleth configuration");
430 throw new ShibbolethConfigurationException("Error while parsing shibboleth configuration");
434 Applications apps = config.getApplications(); // <Applications>
439 * Create an <Application> id "default" from <Applications>
441 ApplicationDocument defaultAppDoc =
442 // Create a new XMLBeans "Document" level object
443 ApplicationDocument.Factory.newInstance();
444 ApplicationDocument.Application defaultApp =
445 // Add an XMLBeans "root Element" object to the Document
446 defaultAppDoc.addNewApplication();
447 // set or copy over fields from unrelated Applications object
448 defaultApp.setId("default");
449 defaultApp.setAAPProviderArray(apps.getAAPProviderArray());
450 defaultApp.setAttributeDesignatorArray(apps.getAttributeDesignatorArray());
451 defaultApp.setAudienceArray(apps.getAudienceArray());
452 defaultApp.setCredentialUse(apps.getCredentialUse());
453 defaultApp.setErrors(apps.getErrors());
454 defaultApp.setFederationProviderArray(apps.getFederationProviderArray());
455 defaultApp.setMetadataProviderArray(apps.getMetadataProviderArray());
456 defaultApp.setProviderId(apps.getProviderId());
457 defaultApp.setSessions(apps.getSessions());
458 defaultApp.setTrustProviderArray(apps.getTrustProviderArray());
461 * Now process secondary files configured in the applications
463 anyError |= processApplication(defaultApp);
465 Application[] apparray = apps.getApplicationArray();
466 for (int i=0;i<apparray.length;i++){
467 Application tempapp = apparray[i];
468 applications.put(tempapp.getId(),tempapp);
469 anyError |= processApplication(tempapp);
473 * Now process other secondary files
475 anyError |= processCredentials();
476 anyError |= processPluggableRequestMapProvider();
479 throw new ShibbolethConfigurationException("Errors processing configuration file, see log");
484 * Routine to handle CredentialProvider
486 * <p>Note: This only handles in-line XML.
487 * Also, Credentials was an existing IdP class, so it doesn't
488 * implement the new PluggableConfigurationComponent interface and
489 * can't be loaded by generic plugin support.
492 private boolean processCredentials() {
493 boolean anyError=false;
494 PluggableType[] pluggable = config.getCredentialsProviderArray();
495 for (int i=0;i<pluggable.length;i++) {
496 String pluggabletype = pluggable[i].getType();
497 if (!pluggabletype.equals(
498 "edu.internet2.middleware.shibboleth.common.Credentials")) {
499 log.error("Unsupported CredentialsProvider type "+pluggabletype);
503 PluggableType credentialsProvider = pluggable[i];
504 Node fragment = credentialsProvider.newDomNode();
505 // newDomNode returns the current node wrapped in a Fragment
507 Node credentialsProviderNode = fragment.getFirstChild();
508 Node credentialsNode=credentialsProviderNode.getFirstChild();
509 credentials = new Credentials((Element)credentialsNode);
510 } catch(Exception e) {
511 log.error("Cannot process Credentials element of Shibboleth configuration");
521 * Find and load secondary configuration files referenced in an Application(s)
523 * @param app Application object
524 * @throws ShibbolethConfigurationException
526 private boolean processApplication(Application app)
527 throws ShibbolethConfigurationException {
529 boolean anyError=false;
531 String applicationId = app.getId();
533 ApplicationInfo appinfo = new ApplicationInfo(app);
535 anyError |= processPluggableMetadata(appinfo);
536 anyError |= processPluggableAAPs(appinfo);
537 anyError |= processPluggableTrusts(appinfo);
539 applications.put(applicationId, appinfo);
545 * Generic code to create an object of a Pluggable type that implements
546 * a configuration interface.
548 * <p>The configuration schema defines "PluggableType" as a type of
549 * XML element that has opaque contents and attributes "type" and
550 * "uri". If the uri attribute is omitted, then the configuration
551 * data is inline XML content. The XSD files typically define the
552 * format of pluggable configuration elements, but without binding
553 * them to the PluggableType element that may contain them.</p>
555 * <p>The implimentation of pluggable objects is provided by
556 * external classes. There are "builtin" classes provided with
557 * Shibboleth (XMLMetadataImpl, XMLTrustImpl, XMLAAPImpl) that
558 * provide examples of how this is done. By design, others can
559 * provide their own classes just by putting the class name as
560 * the value of the type attribute.</p>
562 * <p>This routine handles the common setup. It creates objects
563 * of one of the builtin types, or it uses Class.forName() to
564 * access a user specified class. It then locates either the
565 * inline XML elements or the external XML file. It passes the
566 * XML to initialize the object. Finally, a reference to the
567 * object is stored in the appropriate Map.</p>
569 * <p>The objects created implement two interfaces. Mostly they
570 * implement a configuration interface (EntityDescriptor, Trust,
571 * AAP, etc). However, for the purpose of this routine they also
572 * must be declared to implement PluggableConfigurationComponent
573 * and provide an initialize() method that parses a DOM Node
574 * containing their root XML configuration element.</p>
576 * @param pluggable XMLBean for element defined in XSD to be of "PluggableType"
577 * @param implclass java.lang.Class of Builtin implementation class
578 * @param interfaceClass java.lang.Class of Interface
579 * @param builtinName alias type to choose Builtin imlementation
580 * @param uriMap ApplicationInfo Map for this interface
586 PluggableType pluggable,
588 Class interfaceClass,
590 Map /*<String,PluggableConfigurationComponent>*/uriMap
593 String pluggabletype = pluggable.getType();
595 if (!pluggabletype.equals(builtinName)) {
596 // Not the builtin type, try to load user class by name
598 implclass = Class.forName(pluggabletype);
599 } catch (ClassNotFoundException e) {
600 log.error("Type value "+pluggabletype+" not found as supplied Java class");
603 if (!interfaceClass.isAssignableFrom(implclass)||
604 !PluggableConfigurationComponent.class.isAssignableFrom(implclass)) {
605 log.error(pluggabletype+" class does not support required interfaces.");
610 PluggableConfigurationComponent impl;
612 impl = (PluggableConfigurationComponent) implclass.newInstance();
613 } catch (Exception e) {
614 log.error("Unable to instantiate "+pluggabletype);
618 String uri = pluggable.getUri();
619 if (uri==null) { // inline
623 Node fragment = pluggable.newDomNode(); // XML-Fragment node
624 Node pluggableNode = fragment.getFirstChild(); // PluggableType
625 Node contentNode=pluggableNode.getFirstChild();// root element
626 impl.initialize(contentNode);
627 } catch (Exception e) {
628 log.error("XML error " + e);
632 } else { // external file
634 if (uriMap.get(uri)!=null) { // Already parsed this file
639 Document extdoc = Parser.loadDom(uri,true);
642 impl.initialize(extdoc);
643 } catch (Exception e) {
644 log.error("XML error " + e);
649 uriMap.put(uri,impl);
656 * Handle a FederationProvider
658 private boolean processPluggableMetadata(ApplicationInfo appinfo) {
659 boolean anyError = false;
660 PluggableType[] pluggable1 = appinfo.getApplicationConfig().getFederationProviderArray();
661 PluggableType[] pluggable2 = appinfo.getApplicationConfig().getMetadataProviderArray();
662 PluggableType[] pluggable;
663 if (pluggable1.length==0) {
664 pluggable=pluggable2;
665 } else if (pluggable2.length==0) {
666 pluggable=pluggable1;
668 pluggable = new PluggableType[pluggable1.length+pluggable2.length];
669 for (int i=0;i<pluggable2.length;i++) {
670 pluggable[i]=pluggable2[i];
672 for (int i=0;i<pluggable1.length;i++) {
673 pluggable[i+pluggable2.length]=pluggable1[i];
676 for (int i = 0;i<pluggable.length;i++) {
677 String uri = processPluggable(pluggable[i],
678 XMLMetadataImpl.class,
680 XMLFEDERATIONPROVIDERTYPE,
684 else if (uri.length()>0) {
685 appinfo.addGroupUri(uri);
692 * Reload XML Metadata configuration after file changed.
693 * @param uri Path to Metadata XML configuration
694 * @return true if file reloaded.
696 public boolean reloadFederation(String uri) {
697 if (getMetadataImplementor(uri)!=null||
698 uri.startsWith(INLINEURN))
701 Document sitedoc = Parser.loadDom(uri,true);
704 XMLMetadataImpl impl = new XMLMetadataImpl();
705 impl.initialize(sitedoc);
706 addOrReplaceMetadataImplementor(uri,impl);
707 } catch (Exception e) {
708 log.error("Error while parsing Metadata file "+uri);
709 log.error("XML error " + e);
716 * Handle an AAPProvider element with
717 * type="edu.internet2.middleware.shibboleth.common.provider.XMLAAP"
718 * @throws InternalConfigurationException
720 private boolean processPluggableAAPs(ApplicationInfo appinfo){
721 boolean anyError=false;
722 PluggableType[] pluggable = appinfo.getApplicationConfig().getAAPProviderArray();
723 for (int i = 0;i<pluggable.length;i++) {
724 String uri = processPluggable(pluggable[i],
731 else if (uri.length()>0) {
732 appinfo.addAapUri(uri);
739 * Reload XML AAP configuration after file changed.
740 * @param uri AAP to Trust XML configuration
741 * @return true if file reloaded.
743 public boolean reloadAAP(String uri) {
744 if (getAAPImplementor(uri)!=null||
745 uri.startsWith(INLINEURN))
748 Document aapdoc = Parser.loadDom(uri,true);
751 AttributeAcceptancePolicyDocument aap = AttributeAcceptancePolicyDocument.Factory.parse(aapdoc);
752 XMLAAPImpl impl = new XMLAAPImpl();
753 impl.initialize(aapdoc);
754 addOrReplaceAAPImplementor(uri,impl);
755 } catch (Exception e) {
756 log.error("Error while parsing AAP file "+uri);
757 log.error("XML error " + e);
765 * Handle a TrustProvider element with
766 * type="edu.internet2.middleware.shibboleth.common.provider.XMLTrust"
768 * Note: This code builds the semantic structure of trust. That is, it knows
769 * about certificates and keys. The actual logic of Trust (signature generation
770 * and validation) is implemented in a peer object defined in the external
771 * class XMLTrustImpl.
773 * @throws ShibbolethConfigurationException if X.509 certificate cannot be processed
774 * @throws InternalConfigurationException
776 private boolean processPluggableTrusts(ApplicationInfo appinfo){
777 boolean anyError=false;
778 PluggableType[] pluggable = appinfo.getApplicationConfig().getTrustProviderArray();
779 for (int i = 0;i<pluggable.length;i++) {
780 String uri = processPluggable(pluggable[i],
781 ShibbolethTrust.class,
783 XMLTRUSTPROVIDERTYPE,
784 certificateValidators);
787 else if (uri.length()>0) {
788 appinfo.addTrustUri(uri);
796 private boolean processPluggableRequestMapProvider(){
797 LocalConfigurationType shire = config.getSHIRE();
798 PluggableType mapProvider = shire.getRequestMapProvider();
800 String pluggabletype = mapProvider.getType();
801 if (!pluggabletype.equals(XMLREQUESTMAPPROVIDERTYPE)) {
802 log.error("Unsupported RequestMapProvider type "+pluggabletype);
806 RequestMapDocument requestMapDoc = null;
807 Document mapdoc = null;
808 Element maproot = null;
809 String uri = mapProvider.getUri();
811 if (uri==null) { // inline
815 Node fragment = mapProvider.newDomNode();
816 Node pluggableNode = fragment.getFirstChild();
817 Node contentNode=pluggableNode.getFirstChild();
819 requestMapDoc = RequestMapDocument.Factory.parse(contentNode);
820 } catch (Exception e) {
821 log.error("Error while parsing inline RequestMap");
822 log.error("XML error " + e);
826 } else { // external file
828 mapdoc = Parser.loadDom(uri,true);
831 requestMapDoc = RequestMapDocument.Factory.parse(mapdoc);
832 } catch (Exception e) {
833 log.error("Error while parsing RequestMap file "+uri);
834 log.error("XML error " + e);
839 requestMap = requestMapDoc.getRequestMap();
844 // Generate Map keys for inline plugin configuration Elements
845 private int inlinenum = 1;
846 private String genDummyUri() {
847 return INLINEURN+Integer.toString(inlinenum++);
856 * ApplicationInfo represents the <Application(s)> object, its fields,
857 * and the pluggable configuration elements under it.
859 * <p>It can return arrays of Metadata, Trust, or AAP providers, but
860 * it also exposes convenience methods that shop the lookup(),
861 * validate(), and trust() calls to each object in the collection
862 * until success or failure is determined.</p>
864 * <p>For all other parameters, such as Session parameters, you
865 * can fetch the XMLBean by calling getApplicationConf() and
866 * query their value directly.
868 public class ApplicationInfo
869 implements Metadata, Trust {
871 private Application applicationConfig;
872 public Application getApplicationConfig() {
873 return applicationConfig;
877 * Construct this object from the XML Bean.
878 * @param application XMLBean for Application element
880 ApplicationInfo(Application application) {
881 this.applicationConfig=application;
886 * Following the general rule, this object may not keep
887 * direct references to the plugin interface implementors,
888 * but must look them up on every call through their URI keys.
889 * So we keep collections of URI strings instead.
891 ArrayList groupUris = new ArrayList();
892 ArrayList trustUris = new ArrayList();
893 ArrayList aapUris = new ArrayList();
895 void addGroupUri(String uri) {
898 void addTrustUri(String uri) {
901 void addAapUri(String uri) {
906 * Return the current array of objects that implement the
907 * ...metadata.Metadata interface
911 Metadata[] getMetadataProviders() {
912 Iterator iuris = groupUris.iterator();
913 int count = groupUris.size();
914 Metadata[] metadatas = new Metadata[count];
915 for (int i=0;i<count;i++) {
916 String uri =(String) iuris.next();
917 metadatas[i]=getMetadataImplementor(uri);
923 * A convenience function based on the Metadata interface.
925 * <p>Look for an entity ID through each implementor until the
926 * first one finds locates a describing object.</p>
928 * <p>Unfortunately, Metadata.lookup() was originally specified to
929 * return a "Provider". In current SAML 2.0 terms, the object
930 * returned should be an EntityDescriptor. So this is the new
931 * function in the new interface that will use the new term, but
932 * it does the same thing.</p>
934 * @param id ID of the IdP entity
935 * @return EntityDescriptor metadata object for that site.
937 public EntityDescriptor lookup(String id) {
938 Iterator iuris = groupUris.iterator();
939 while (iuris.hasNext()) {
940 String uri =(String) iuris.next();
941 Metadata locator=getMetadataImplementor(uri);
942 EntityDescriptor entity = locator.lookup(id);
949 public EntityDescriptor lookup(Artifact artifact) {
950 Iterator iuris = groupUris.iterator();
951 while (iuris.hasNext()) {
952 String uri =(String) iuris.next();
953 Metadata locator=getMetadataImplementor(uri);
954 EntityDescriptor entity = locator.lookup(artifact);
962 * Return the current array of objects that implement the Trust interface
966 public Trust[] getTrustProviders() {
967 Iterator iuris = trustUris.iterator();
968 int count = trustUris.size();
971 Trust[] trusts = new Trust[count];
972 for (int i=0;i<count;i++) {
973 String uri =(String) iuris.next();
974 trusts[i]=getTrustImplementor(uri);
980 * Return the current array of objects that implement the AAP interface
984 public AAP[] getAAPProviders() {
985 Iterator iuris = aapUris.iterator();
986 int count = aapUris.size();
987 AAP[] aaps = new AAP[count];
988 for (int i=0;i<count;i++) {
989 String uri =(String) iuris.next();
990 aaps[i]=getAAPImplementor(uri);
996 * Convenience function to apply AAP by calling the apply()
997 * method of each AAP implementor.
999 * <p>Any AAP implementor can delete an assertion or value.
1000 * Empty SAML elements get removed from the assertion.
1001 * This can yield an AttributeAssertion with no attributes.
1003 * @param assertion SAML Attribute Assertion
1004 * @param role Role that issued the assertion
1005 * @throws SAMLException Raised if assertion is mangled beyond repair
1007 void applyAAP(SAMLAssertion assertion, RoleDescriptor role) throws SAMLException {
1009 // Foreach AAP in the collection
1010 AAP[] providers = getAAPProviders();
1011 if (providers.length == 0) {
1012 log.info("no filters specified, accepting entire assertion");
1015 for (int i=0;i<providers.length;i++) {
1016 AAP aap = providers[i];
1017 if (aap.anyAttribute()) {
1018 log.info("any attribute enabled, accepting entire assertion");
1023 // Foreach Statement in the Assertion
1024 Iterator statements = assertion.getStatements();
1026 while (statements.hasNext()) {
1027 Object statement = statements.next();
1028 if (statement instanceof SAMLAttributeStatement) {
1029 SAMLAttributeStatement attributeStatement = (SAMLAttributeStatement) statement;
1031 // Check each attribute, applying any matching rules.
1032 Iterator attributes = attributeStatement.getAttributes();
1034 while (attributes.hasNext()) {
1035 SAMLAttribute attribute = (SAMLAttribute) attributes.next();
1036 boolean ruleFound = false;
1037 for (int i=0;i<providers.length;i++) {
1038 AttributeRule rule = providers[i].lookup(attribute.getName(),attribute.getNamespace());
1042 rule.apply(attribute,role);
1044 catch (SAMLException ex) {
1045 log.info("no values remain, removing attribute");
1046 attributeStatement.removeAttribute(iattribute--);
1052 log.warn("no rule found for attribute (" + attribute.getName() + "), filtering it out");
1053 attributeStatement.removeAttribute(iattribute--);
1059 attributeStatement.checkValidity();
1062 catch (SAMLException ex) {
1063 // The statement is now defunct.
1064 log.info("no attributes remain, removing statement");
1065 assertion.removeStatement(istatement);
1070 // Now see if we trashed it irrevocably.
1071 assertion.checkValidity();
1076 * Returns a collection of attribute names to request from the AA.
1078 * @return Collection of attribute Name values
1080 public Collection getAttributeDesignators() {
1081 // TODO Not sure where this should come from
1082 return new ArrayList();
1087 * Convenience method implementing Trust.validate() across
1088 * the collection of implementing objects. Returns true if
1089 * any Trust implementor approves the signatures in the object.
1091 * <p>In the interface, validate() is passed several arguments
1092 * that come from this object. In this function, those
1093 * arguments are ignored "this" is used.
1097 SAMLSignedObject token,
1102 Trust[] trustProviders = getTrustProviders();
1103 for (int i=0;i<trustProviders.length;i++) {
1104 Trust trust = trustProviders[i];
1105 if (trust.validate(token,role))
1113 * A method of Trust that we must declare to claim that
1114 * ApplicationInfo implements Trust. However, no code in the
1115 * ServiceProvider calls this (probably an IdP thing).
1117 * @param revocations
1119 * @return This dummy always returns false.
1121 public boolean attach(Iterator revocations, RoleDescriptor role) {
1126 public boolean validate(X509Certificate certificateEE, X509Certificate[] certificateChain, RoleDescriptor descriptor) {
1127 Trust[] trustProviders = getTrustProviders();
1128 for (int i=0;i<trustProviders.length;i++) {
1129 Trust trust = trustProviders[i];
1130 if (trust.validate(certificateEE,certificateChain,descriptor))
1136 public boolean validate(X509Certificate certificateEE, X509Certificate[] certificateChain, RoleDescriptor descriptor, boolean checkName) {
1137 Trust[] trustProviders = getTrustProviders();
1138 for (int i=0;i<trustProviders.length;i++) {
1139 Trust trust = trustProviders[i];
1140 if (trust.validate(certificateEE,certificateChain,descriptor,checkName))
1149 private static class InternalConfigurationException extends Exception {
1150 InternalConfigurationException() {