2 * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * ServiceProviderConfig.java
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.
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.
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().
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.
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.
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.
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.
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
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.
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.
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
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:
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.
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.
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.
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.
141 package edu.internet2.middleware.shibboleth.serviceprovider;
143 import java.net.MalformedURLException;
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;
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;
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.aap.provider.XMLAAPProvider;
182 import edu.internet2.middleware.shibboleth.common.Credentials;
183 import edu.internet2.middleware.shibboleth.common.PluggableConfigurationComponent;
184 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
185 import edu.internet2.middleware.shibboleth.common.Trust;
186 import edu.internet2.middleware.shibboleth.common.provider.ShibbolethTrust;
187 import edu.internet2.middleware.shibboleth.metadata.EntitiesDescriptor;
188 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
189 import edu.internet2.middleware.shibboleth.metadata.Metadata;
190 import edu.internet2.middleware.shibboleth.metadata.RoleDescriptor;
191 import edu.internet2.middleware.shibboleth.metadata.provider.XMLMetadataProvider;
192 import edu.internet2.middleware.shibboleth.xml.Parser;
195 * Load the configuration files into objects, index them, and return them on request.
197 * <p>A new instance of the ServiceProviderConfig object can be created to
198 * parse a new version of the configuration file. It can then be swapped
199 * into the ServiceProviderContext reference and will be picked up by
200 * subsequent requests.</p>
202 * @author Howard Gilbert
204 public class ServiceProviderConfig {
206 // Map key prefix for inline plugin configuration elements
207 private static final String INLINEURN = "urn:inlineBS:ID";
209 private static Logger initlog = Logger.getLogger(ContextListener.SHIBBOLETH_INIT+".Config");
210 private static Logger reqlog = Logger.getLogger(ServiceProviderConfig.class);
212 private SPConfigType // The XMLBean from the main config file
217 * The following Maps reference objects that implement a plugin
218 * interface indexed by their URI. There are builtin objects
219 * created from inline or external XML files, but external
220 * objects implementing the interfaces may be injected by
221 * calling the addOrReplaceXXX method. Public access to these
222 * Maps is indirect, through methods the ApplicationInfo object
223 * for a given configured or default application.
226 private Map/*<String, Metadata>*/ entityLocators =
227 new TreeMap/*<String, Metadata>*/();
229 public void addOrReplaceMetadataImplementor(String uri, Metadata m) {
230 initlog.info("addOrReplaceMetadataImplementor " + uri+ " as "+m.getClass());
231 entityLocators.put(uri, m);
234 public Metadata getMetadataImplementor(String uri) {
235 return (Metadata)entityLocators.get(uri);
238 private Map/*<String, AAP>*/ attributePolicies =
239 new TreeMap/*<String, AAP>*/();
241 public void addOrReplaceAAPImplementor(String uri, AAP a) {
242 initlog.info("addOrReplaceAAPImplementor " + uri+ " as "+a.getClass());
243 attributePolicies.put(uri,a);
246 public AAP getAAPImplementor(String uri) {
247 return (AAP) attributePolicies.get(uri);
250 private Map/*<String, Trust>*/ certificateValidators =
251 new TreeMap/*<String, Trust>*/();
253 public void addOrReplaceTrustImplementor(String uri, Trust t) {
254 initlog.info("addOrReplaceTrustImplementor " + uri+ " as "+t.getClass());
255 certificateValidators.put(uri,t);
258 public Trust getTrustImplementor(String uri) {
259 return (Trust) certificateValidators.get(uri);
262 private Trust[] defaultTrust = {new ShibbolethTrust()};
265 * Objects created from the <Application(s)> elements.
266 * They manage collections of URI-Strings that index the
267 * previous maps to return Metadata, Trust, and AAP info
268 * applicable to this applicationId.
270 private Map/*<String, ApplicationInfo>*/ applications =
271 new TreeMap/*<String, ApplicationInfo>*/();
273 // Default application info from <Applications> element
274 private ApplicationInfo defaultApplicationInfo = null;
276 public ApplicationInfo getApplication(String applicationId) {
277 ApplicationInfo app=null;
278 app = (ApplicationInfo) applications.get(applicationId);
279 if (app==null) // If no specific match, return default
280 return defaultApplicationInfo;
285 // Objects created from single configuration file elements
286 private Credentials credentials = null;
287 private RequestMapDocument.RequestMap requestMap = null;
294 private static final String XMLTRUSTPROVIDERTYPE =
295 "edu.internet2.middleware.shibboleth.common.provider.ShibbolethTrust";
296 private static final String XMLAAPPROVIDERTYPE =
297 "edu.internet2.middleware.shibboleth.aap.provider.XMLAAP";
298 private static final String XMLFEDERATIONPROVIDERTYPE =
299 "edu.internet2.middleware.shibboleth.metadata.provider.XMLMetadata";
300 private static final String XMLREQUESTMAPPROVIDERTYPE =
301 "edu.internet2.middleware.shibboleth.sp.provider.NativeRequestMapProvider";
302 private static final String XMLCREDENTIALSPROVIDERTYPE =
303 "edu.internet2.middleware.shibboleth.common.Credentials";
309 * The constructor prepares for, but does not parse the configuration.
311 * @throws ShibbolethConfigurationException
312 * if XML Parser cannot be initialized (Classpath problem)
314 public ServiceProviderConfig() {
318 * loadConfigObjects must be called once to parse the configuration.
320 * <p>To reload a modified configuration file, create and load a second
321 * object and swap the reference in the context object.</p>
323 * @param configFilePath URL or resource name of file
324 * @return the DOM Document
325 * @throws ShibbolethConfigurationException
326 * if there was an error loading the file
328 public synchronized void loadConfigObjects(String configFilePath)
329 throws ShibbolethConfigurationException {
332 initlog.error("ServiceProviderConfig.loadConfigObjects may not be called twice for the same object.");
333 throw new ShibbolethConfigurationException("Cannot reload configuration into same object.");
336 initlog.info("Loading SP configuration from "+configFilePath);
340 configDoc = Parser.loadDom(configFilePath, true);
341 } catch (Exception e) {
342 initlog.error("XML Parser error "+e.toString());
343 throw new ShibbolethConfigurationException("XML error in "+configFilePath);
345 loadConfigBean(configDoc);
351 * Given a URL, determine its ApplicationId from the RequestMap config.
353 * <p>Note: This is not a full implementation of all RequestMap
354 * configuration options. Other functions will be added as needed.</p>
356 public String mapRequest(String urlreq) {
357 String applicationId = "default";
361 url = new URL(urlreq);
362 } catch (MalformedURLException e) {
363 return applicationId;
366 String urlscheme = url.getProtocol();
367 String urlhostname = url.getHost();
368 String urlpath = url.getPath();
369 int urlport = url.getPort();
371 // find Host entry for this virtual server
372 Host[] hostArray = requestMap.getHostArray();
373 for (int ihost=0;ihost<hostArray.length;ihost++) {
374 Host host = hostArray[ihost];
375 Enum scheme = host.getScheme();
376 String hostName = host.getName();
377 String hostApplicationId = host.getApplicationId();
378 long hostport = host.getPort();
380 if (scheme != null &&
381 !urlscheme.equals(scheme.toString()))
383 if (!urlhostname.equals(hostName))
390 // find Path entry for this subdirectory
391 Path[] pathArray = host.getPathArray();
392 if (hostApplicationId!=null)
393 applicationId=hostApplicationId;
394 for (int i=0;i<pathArray.length;i++) {
395 String dirname = pathArray[i].getName();
396 if (urlpath.equals(dirname)||
397 urlpath.startsWith(dirname+"/")){
398 String pthid= pathArray[i].getApplicationId();
405 reqlog.debug("mapRequest mapped "+urlreq+" into "+applicationId);
406 return applicationId;
410 * <p>Parse the main configuration file DOM into XML Bean</p>
412 * <p>Automatically load secondary configuration files designated
413 * by URLs in the main configuration file</p>
415 * @throws ShibbolethConfigurationException
417 private void loadConfigBean(Document configDoc)
418 throws ShibbolethConfigurationException {
419 boolean anyError=false;
421 Element documentElement = configDoc.getDocumentElement();
422 // reprocess the already validated DOM to create a bean with typed fields
423 // dump the trash (comments, processing instructions, extra whitespace)
425 if (documentElement.getLocalName().equals("ShibbolethTargetConfig")) {
426 initlog.debug("SP Configuration file is in 1.2 syntax.");
427 ShibbolethTargetConfigDocument configBeanDoc;
428 configBeanDoc = ShibbolethTargetConfigDocument.Factory.parse(configDoc,
429 new XmlOptions().setLoadStripComments().setLoadStripProcinsts().setLoadStripWhitespace());
430 config = configBeanDoc.getShibbolethTargetConfig();
431 } else if (documentElement.getLocalName().equals("SPConfig")) {
432 initlog.debug("SP Configuration file is in 1.3 syntax.");
433 SPConfigDocument configBeanDoc;
434 configBeanDoc = SPConfigDocument.Factory.parse(configDoc,
435 new XmlOptions().setLoadStripComments().setLoadStripProcinsts().setLoadStripWhitespace());
436 config = configBeanDoc.getSPConfig();
438 initlog.error("Root element not ShibbolethTargetConfig or SPConfig");
439 throw new XmlException("Root element not ShibbolethTargetConfig or SPConfig");
441 } catch (XmlException e) {
442 // Since the DOM was already validated against the schema, errors will not typically occur here
443 initlog.error("Error while parsing shibboleth configuration");
444 throw new ShibbolethConfigurationException("Error while parsing shibboleth configuration");
447 String loggerUrlString = config.getLogger();
448 if (loggerUrlString!=null) {
450 URL loggerURL = new URL(loggerUrlString);
451 initlog.warn("logging is being reconfigured by "+ loggerUrlString);
452 PropertyConfigurator.configure(loggerURL);
453 } catch (MalformedURLException e) {
454 // This error is not serious enough to prevent initialization
455 initlog.error("Ignoring invalid value for logger attribute "+loggerUrlString );
459 Applications apps = config.getApplications(); // <Applications>
464 * Create an <Application> id "default" from <Applications>
466 ApplicationDocument defaultAppDoc =
467 // Create a new XMLBeans "Document" level object
468 ApplicationDocument.Factory.newInstance();
469 ApplicationDocument.Application defaultApp =
470 // Add an XMLBeans "root Element" object to the Document
471 defaultAppDoc.addNewApplication();
472 // set or copy over fields from unrelated Applications object
473 defaultApp.setId("default");
474 defaultApp.setAAPProviderArray(apps.getAAPProviderArray());
475 defaultApp.setAttributeDesignatorArray(apps.getAttributeDesignatorArray());
476 defaultApp.setAudienceArray(apps.getAudienceArray());
477 defaultApp.setCredentialUse(apps.getCredentialUse());
478 defaultApp.setErrors(apps.getErrors());
479 defaultApp.setFederationProviderArray(apps.getFederationProviderArray());
480 defaultApp.setMetadataProviderArray(apps.getMetadataProviderArray());
481 defaultApp.setProviderId(apps.getProviderId());
482 defaultApp.setSessions(apps.getSessions());
483 defaultApp.setTrustProviderArray(apps.getTrustProviderArray());
486 * Now process secondary files configured in the applications
488 anyError |= processApplication(defaultApp);
490 Application[] apparray = apps.getApplicationArray();
491 for (int i=0;i<apparray.length;i++){
492 Application tempapp = apparray[i];
493 applications.put(tempapp.getId(),tempapp);
494 anyError |= processApplication(tempapp);
498 * Now process other secondary files
500 anyError |= processCredentials();
501 anyError |= processPluggableRequestMapProvider();
504 initlog.error("SP Initialization terminated due to configuration errors");
505 throw new ShibbolethConfigurationException("Errors processing configuration file, see log");
511 * Routine to handle CredentialProvider
513 * <p>Note: This only handles in-line XML.
514 * Also, Credentials was an existing IdP class, so it doesn't
515 * implement the new PluggableConfigurationComponent interface and
516 * can't be loaded by generic plugin support.
519 private boolean processCredentials() {
520 boolean anyError=false;
521 PluggableType[] pluggable = config.getCredentialsProviderArray();
522 for (int i=0;i<pluggable.length;i++) {
523 String pluggabletype = pluggable[i].getType();
524 if (!pluggabletype.equals(
525 "edu.internet2.middleware.shibboleth.common.Credentials")) {
526 initlog.error("Unsupported CredentialsProvider type "+pluggabletype);
530 PluggableType credentialsProvider = pluggable[i];
531 Node fragment = credentialsProvider.newDomNode();
532 // newDomNode returns the current node wrapped in a Fragment
534 Node credentialsProviderNode = fragment.getFirstChild();
535 Node credentialsNode=credentialsProviderNode.getFirstChild();
536 credentials = new Credentials((Element)credentialsNode);
537 } catch(Exception e) {
538 initlog.error("Cannot process Credentials element of Shibboleth configuration",e);
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, Trust,
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,
616 Map /*<String,PluggableConfigurationComponent>*/uriMap
619 String pluggabletype = pluggable.getType();
621 if (!pluggabletype.equals(builtinName)) {
622 // Not the builtin type, try to load user class by name
623 initlog.info("loading user-specified pluggable class "+pluggabletype);
625 implclass = Class.forName(pluggabletype);
626 } catch (ClassNotFoundException e) {
627 initlog.error("Type value "+pluggabletype+" not found as supplied Java class");
630 if (!interfaceClass.isAssignableFrom(implclass)||
631 !PluggableConfigurationComponent.class.isAssignableFrom(implclass)) {
632 initlog.error(pluggabletype+" class does not support required interfaces.");
637 PluggableConfigurationComponent impl;
639 impl = (PluggableConfigurationComponent) implclass.newInstance();
640 } catch (Exception e) {
641 initlog.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 Element contentNode=(Element) pluggableNode.getFirstChild();// root element
653 impl.initialize(contentNode);
654 } catch (Exception e) {
655 initlog.error("XML error " + e);
659 } else { // external file
661 if (uriMap.get(uri)!=null) { // Already parsed this file
666 Document extdoc = Parser.loadDom(uri,true);
669 impl.initialize(extdoc.getDocumentElement());
670 } catch (Exception e) {
671 initlog.error("XML error " + e);
676 uriMap.put(uri,impl);
683 * Handle a FederationProvider
685 private boolean processPluggableMetadata(ApplicationInfo appinfo) {
686 boolean anyError = false;
687 PluggableType[] pluggable1 = appinfo.getApplicationConfig().getFederationProviderArray();
688 PluggableType[] pluggable2 = appinfo.getApplicationConfig().getMetadataProviderArray();
689 PluggableType[] pluggable;
690 if (pluggable1.length==0) {
691 pluggable=pluggable2;
692 } else if (pluggable2.length==0) {
693 pluggable=pluggable1;
695 pluggable = new PluggableType[pluggable1.length+pluggable2.length];
696 for (int i=0;i<pluggable2.length;i++) {
697 pluggable[i]=pluggable2[i];
699 for (int i=0;i<pluggable1.length;i++) {
700 pluggable[i+pluggable2.length]=pluggable1[i];
703 for (int i = 0;i<pluggable.length;i++) {
704 String uri = processPluggable(pluggable[i],
705 XMLMetadataProvider.class,
707 XMLFEDERATIONPROVIDERTYPE,
711 else if (uri.length()>0) {
712 appinfo.addGroupUri(uri);
719 * Reload XML Metadata configuration after file changed.
720 * @param uri Path to Metadata XML configuration
721 * @return true if file reloaded.
723 public boolean reloadFederation(String uri) {
724 if (getMetadataImplementor(uri)!=null||
725 uri.startsWith(INLINEURN))
728 Document sitedoc = Parser.loadDom(uri,true);
731 XMLMetadataProvider impl = new XMLMetadataProvider();
732 impl.initialize(sitedoc.getDocumentElement());
733 addOrReplaceMetadataImplementor(uri,impl);
734 } catch (Exception e) {
735 initlog.error("Error while parsing Metadata file "+uri);
736 initlog.error("XML error " + e);
743 * Handle an AAPProvider element with
744 * type="edu.internet2.middleware.shibboleth.common.provider.XMLAAP"
745 * @throws InternalConfigurationException
747 private boolean processPluggableAAPs(ApplicationInfo appinfo){
748 boolean anyError=false;
749 PluggableType[] pluggable = appinfo.getApplicationConfig().getAAPProviderArray();
750 for (int i = 0;i<pluggable.length;i++) {
751 String uri = processPluggable(pluggable[i],
752 XMLAAPProvider.class,
758 else if (uri.length()>0) {
759 appinfo.addAapUri(uri);
766 * Reload XML AAP configuration after file changed.
767 * @param uri AAP to Trust XML configuration
768 * @return true if file reloaded.
770 public boolean reloadAAP(String uri) {
771 if (getAAPImplementor(uri)!=null||
772 uri.startsWith(INLINEURN))
775 Document aapdoc = Parser.loadDom(uri,true);
778 AttributeAcceptancePolicyDocument aap = AttributeAcceptancePolicyDocument.Factory.parse(aapdoc);
779 XMLAAPProvider impl = new XMLAAPProvider();
780 impl.initialize(aapdoc.getDocumentElement());
781 addOrReplaceAAPImplementor(uri,impl);
782 } catch (Exception e) {
783 initlog.error("Error while parsing AAP file "+uri);
784 initlog.error("XML error " + e);
792 * Handle a TrustProvider element with
793 * type="edu.internet2.middleware.shibboleth.common.provider.XMLTrust"
795 * Note: This code builds the semantic structure of trust. That is, it knows
796 * about certificates and keys. The actual logic of Trust (signature generation
797 * and validation) is implemented in a peer object defined in the external
798 * class XMLTrustImpl.
800 * @throws ShibbolethConfigurationException if X.509 certificate cannot be processed
801 * @throws InternalConfigurationException
803 private boolean processPluggableTrusts(ApplicationInfo appinfo){
804 boolean anyError=false;
805 PluggableType[] pluggable = appinfo.getApplicationConfig().getTrustProviderArray();
806 for (int i = 0;i<pluggable.length;i++) {
807 String uri = processPluggable(pluggable[i],
808 ShibbolethTrust.class,
810 XMLTRUSTPROVIDERTYPE,
811 certificateValidators);
814 else if (uri.length()>0) {
815 appinfo.addTrustUri(uri);
823 private boolean processPluggableRequestMapProvider(){
824 LocalConfigurationType shire = config.getSHIRE();
826 shire = config.getLocal();
828 initlog.error("No SHIRE or Local element.");
831 PluggableType mapProvider = shire.getRequestMapProvider();
833 String pluggabletype = mapProvider.getType();
834 if (!pluggabletype.equals(XMLREQUESTMAPPROVIDERTYPE)) {
835 initlog.error("Unsupported RequestMapProvider type "+pluggabletype);
839 RequestMapDocument requestMapDoc = null;
840 Document mapdoc = null;
841 Element maproot = null;
842 String uri = mapProvider.getUri();
844 if (uri==null) { // inline
848 Node fragment = mapProvider.newDomNode();
849 Node pluggableNode = fragment.getFirstChild();
850 Node contentNode=pluggableNode.getFirstChild();
852 requestMapDoc = RequestMapDocument.Factory.parse(contentNode);
853 } catch (Exception e) {
854 initlog.error("Error while parsing inline RequestMap");
855 initlog.error("XML error " + e);
859 } else { // external file
861 mapdoc = Parser.loadDom(uri,true);
864 requestMapDoc = RequestMapDocument.Factory.parse(mapdoc);
865 } catch (Exception e) {
866 initlog.error("Error while parsing RequestMap file "+uri);
867 initlog.error("XML error " + e);
872 requestMap = requestMapDoc.getRequestMap();
877 // Generate Map keys for inline plugin configuration Elements
878 private int inlinenum = 1;
879 private String genDummyUri() {
880 return INLINEURN+Integer.toString(inlinenum++);
889 * ApplicationInfo represents the <Application(s)> object, its fields,
890 * and the pluggable configuration elements under it.
892 * <p>It can return arrays of Metadata, Trust, or AAP providers, but
893 * it also exposes convenience methods that shop the lookup(),
894 * validate(), and trust() calls to each object in the collection
895 * until success or failure is determined.</p>
897 * <p>For all other parameters, such as Session parameters, you
898 * can fetch the XMLBean by calling getApplicationConf() and
899 * query their value directly.
901 public class ApplicationInfo
902 implements Metadata, Trust {
904 private Application applicationConfig;
905 public Application getApplicationConfig() {
906 return applicationConfig;
910 * Construct this object from the XML Bean.
911 * @param application XMLBean for Application element
913 ApplicationInfo(Application application) {
914 this.applicationConfig=application;
919 * Following the general rule, this object may not keep
920 * direct references to the plugin interface implementors,
921 * but must look them up on every call through their URI keys.
922 * So we keep collections of URI strings instead.
924 ArrayList groupUris = new ArrayList();
925 ArrayList trustUris = new ArrayList();
926 ArrayList aapUris = new ArrayList();
928 void addGroupUri(String uri) {
931 void addTrustUri(String uri) {
934 void addAapUri(String uri) {
939 * Return the current array of objects that implement the
940 * ...metadata.Metadata interface
944 Metadata[] getMetadataProviders() {
945 Iterator iuris = groupUris.iterator();
946 int count = groupUris.size();
947 Metadata[] metadatas = new Metadata[count];
948 for (int i=0;i<count;i++) {
949 String uri =(String) iuris.next();
950 metadatas[i]=getMetadataImplementor(uri);
956 * A convenience function based on the Metadata interface.
958 * <p>Look for an entity ID through each implementor until the
959 * first one finds locates a describing object.</p>
961 * <p>Unfortunately, Metadata.lookup() was originally specified to
962 * return a "Provider". In current SAML 2.0 terms, the object
963 * returned should be an EntityDescriptor. So this is the new
964 * function in the new interface that will use the new term, but
965 * it does the same thing.</p>
967 * @param id ID of the IdP entity
968 * @return EntityDescriptor metadata object for that site.
970 public EntityDescriptor lookup(String id, boolean strict) {
971 Iterator iuris = groupUris.iterator();
972 while (iuris.hasNext()) {
973 String uri =(String) iuris.next();
974 Metadata locator=getMetadataImplementor(uri);
975 EntityDescriptor entity = locator.lookup(id, strict);
977 reqlog.debug("Metadata.lookup resolved Entity "+ id);
981 reqlog.warn("Metadata.lookup failed to resolve Entity "+ id);
985 public EntityDescriptor lookup(Artifact artifact, boolean strict) {
986 Iterator iuris = groupUris.iterator();
987 while (iuris.hasNext()) {
988 String uri =(String) iuris.next();
989 Metadata locator=getMetadataImplementor(uri);
990 EntityDescriptor entity = locator.lookup(artifact, strict);
992 reqlog.debug("Metadata.lookup resolved Artifact "+ artifact);
996 reqlog.warn("Metadata.lookup failed to resolve Artifact "+ artifact);
1000 public EntityDescriptor lookup(String id) {
1001 return lookup(id,true);
1004 public EntityDescriptor lookup(Artifact artifact) {
1005 return lookup(artifact,true);
1008 public EntityDescriptor getRootEntity() {
1012 public EntitiesDescriptor getRootEntities() {
1017 * Return the current array of objects that implement the Trust interface
1021 public Trust[] getTrustProviders() {
1022 Iterator iuris = trustUris.iterator();
1023 int count = trustUris.size();
1025 return defaultTrust;
1026 Trust[] trusts = new Trust[count];
1027 for (int i=0;i<count;i++) {
1028 String uri =(String) iuris.next();
1029 trusts[i]=getTrustImplementor(uri);
1035 * Return the current array of objects that implement the AAP interface
1039 public AAP[] getAAPProviders() {
1040 Iterator iuris = aapUris.iterator();
1041 int count = aapUris.size();
1042 AAP[] aaps = new AAP[count];
1043 for (int i=0;i<count;i++) {
1044 String uri =(String) iuris.next();
1045 aaps[i]=getAAPImplementor(uri);
1051 * Convenience function to apply AAP by calling the apply()
1052 * method of each AAP implementor.
1054 * <p>Any AAP implementor can delete an assertion or value.
1055 * Empty SAML elements get removed from the assertion.
1056 * This can yield an AttributeAssertion with no attributes.
1058 * @param assertion SAML Attribute Assertion
1059 * @param role Role that issued the assertion
1060 * @throws SAMLException Raised if assertion is mangled beyond repair
1062 void applyAAP(SAMLAssertion assertion, RoleDescriptor role) throws SAMLException {
1064 // Foreach AAP in the collection
1065 AAP[] providers = getAAPProviders();
1066 if (providers.length == 0) {
1067 reqlog.info("no filters specified, accepting entire assertion");
1070 for (int i=0;i<providers.length;i++) {
1071 AAP aap = providers[i];
1072 if (aap.anyAttribute()) {
1073 reqlog.info("any attribute enabled, accepting entire assertion");
1078 // Foreach Statement in the Assertion
1079 Iterator statements = assertion.getStatements();
1081 while (statements.hasNext()) {
1082 Object statement = statements.next();
1083 if (statement instanceof SAMLAttributeStatement) {
1084 SAMLAttributeStatement attributeStatement = (SAMLAttributeStatement) statement;
1086 // Check each attribute, applying any matching rules.
1087 Iterator attributes = attributeStatement.getAttributes();
1089 while (attributes.hasNext()) {
1090 SAMLAttribute attribute = (SAMLAttribute) attributes.next();
1091 boolean ruleFound = false;
1092 for (int i=0;i<providers.length;i++) {
1093 AttributeRule rule = providers[i].lookup(attribute.getName(),attribute.getNamespace());
1097 rule.apply(attribute,role);
1099 catch (SAMLException ex) {
1100 reqlog.info("no values remain, removing attribute");
1101 attributeStatement.removeAttribute(iattribute--);
1107 reqlog.warn("no rule found for attribute (" + attribute.getName() + "), filtering it out");
1108 attributeStatement.removeAttribute(iattribute--);
1114 attributeStatement.checkValidity();
1117 catch (SAMLException ex) {
1118 // The statement is now defunct.
1119 reqlog.info("no attributes remain, removing statement");
1120 assertion.removeStatement(istatement);
1125 // Now see if we trashed it irrevocably.
1126 assertion.checkValidity();
1131 * Returns a collection of attribute names to request from the AA.
1133 * @return Collection of attribute Name values
1135 public Collection getAttributeDesignators() {
1136 // TODO Not sure where this should come from
1137 return new ArrayList();
1142 * Convenience method implementing Trust.validate() across
1143 * the collection of implementing objects. Returns true if
1144 * any Trust implementor approves the signatures in the object.
1146 * <p>In the interface, validate() is passed several arguments
1147 * that come from this object. In this function, those
1148 * arguments are ignored "this" is used.
1152 SAMLSignedObject token,
1157 Trust[] trustProviders = getTrustProviders();
1158 for (int i=0;i<trustProviders.length;i++) {
1159 Trust trust = trustProviders[i];
1160 if (trust.validate(token,role))
1163 reqlog.warn("SAML object failed Trust validation.");
1169 * A method of Trust that we must declare to claim that
1170 * ApplicationInfo implements Trust. However, no code in the
1171 * ServiceProvider calls this (probably an IdP thing).
1173 * @param revocations
1175 * @return This dummy always returns false.
1177 public boolean attach(Iterator revocations, RoleDescriptor role) {
1182 public boolean validate(X509Certificate certificateEE, X509Certificate[] certificateChain, RoleDescriptor descriptor) {
1183 Trust[] trustProviders = getTrustProviders();
1184 for (int i=0;i<trustProviders.length;i++) {
1185 Trust trust = trustProviders[i];
1186 if (trust.validate(certificateEE,certificateChain,descriptor))
1189 reqlog.warn("X.509 Certificate failed Trust validate");
1193 public boolean validate(X509Certificate certificateEE, X509Certificate[] certificateChain, RoleDescriptor descriptor, boolean checkName) {
1194 Trust[] trustProviders = getTrustProviders();
1195 for (int i=0;i<trustProviders.length;i++) {
1196 Trust trust = trustProviders[i];
1197 if (trust.validate(certificateEE,certificateChain,descriptor,checkName))
1200 reqlog.warn("X.509 Certificate failed Trust validate");
1204 public String getProviderId() {
1205 String entityId = this.applicationConfig.getProviderId();
1206 if (entityId==null && this!=defaultApplicationInfo) {
1207 entityId = defaultApplicationInfo.getProviderId();
1215 private static class InternalConfigurationException extends Exception {
1216 InternalConfigurationException() {
1223 public Credentials getCredentials() {