2 * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3 * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
4 * provided that the following conditions are met: Redistributions of source code must retain the above copyright
5 * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
6 * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
7 * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
8 * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu> Internet2 Project.
9 * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
10 * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
11 * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
12 * products derived from this software without specific prior written permission. For written permission, please contact
13 * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
14 * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
15 * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
16 * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
18 * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
19 * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 package edu.internet2.middleware.shibboleth.common;
28 import java.net.MalformedURLException;
30 import java.net.URISyntaxException;
32 import java.util.HashMap;
35 import org.apache.log4j.Logger;
36 import org.w3c.dom.Element;
37 import org.w3c.dom.NodeList;
39 import edu.internet2.middleware.shibboleth.idp.IdPConfig;
40 import edu.internet2.middleware.shibboleth.metadata.EntitiesDescriptor;
41 import edu.internet2.middleware.shibboleth.metadata.Metadata;
42 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
45 * Class for determining the effective relying party from the unique id of the service provider. Checks first for an
46 * exact match on the service provider, then for membership in a federation. Uses the default relying party if neither
49 * @author Walter Hoehn
51 public class ServiceProviderMapper {
53 private static Logger log = Logger.getLogger(ServiceProviderMapper.class.getName());
54 protected Map relyingParties = new HashMap();
55 private Metadata metaData;
56 private IdPConfig configuration;
57 private Credentials credentials;
58 private NameMapper nameMapper;
60 public ServiceProviderMapper(Element rawConfig, IdPConfig configuration, Credentials credentials,
61 NameMapper nameMapper) throws ServiceProviderMapperException {
63 this.configuration = configuration;
64 this.credentials = credentials;
65 this.nameMapper = nameMapper;
67 NodeList itemElements = rawConfig.getElementsByTagNameNS(IdPConfig.configNameSpace, "RelyingParty");
69 for (int i = 0; i < itemElements.getLength(); i++) {
70 addRelyingParty((Element) itemElements.item(i));
73 verifyDefaultParty(configuration);
77 public void setMetadata(Metadata metadata) {
79 this.metaData = metadata;
82 private IdPConfig getOriginConfig() {
87 protected void verifyDefaultParty(IdPConfig configuration) throws ServiceProviderMapperException {
89 // Verify we have a proper default party
90 String defaultParty = configuration.getDefaultRelyingPartyName();
91 if (defaultParty == null || defaultParty.equals("")) {
92 if (relyingParties.size() != 1) {
94 .error("Default Relying Party not specified. Add a (defaultRelyingParty) attribute to <IdPConfig>.");
95 throw new ServiceProviderMapperException("Required configuration not specified.");
97 log.debug("Only one Relying Party loaded. Using this as the default.");
100 log.debug("Default Relying Party set to: (" + defaultParty + ").");
101 if (!relyingParties.containsKey(defaultParty)) {
102 log.error("Default Relying Party refers to a Relying Party that has not been loaded.");
103 throw new ServiceProviderMapperException("Invalid configuration (Default Relying Party).");
107 protected RelyingParty getRelyingPartyImpl(String providerIdFromTarget) {
109 // Null request, send the default
110 if (providerIdFromTarget == null) {
111 RelyingParty relyingParty = getDefaultRelyingParty();
112 log.info("Using default Relying Party: (" + relyingParty.getName() + ").");
113 return new UnknownProviderWrapper(relyingParty, providerIdFromTarget);
116 // Look for a configuration for the specific relying party
117 if (relyingParties.containsKey(providerIdFromTarget)) {
118 log.info("Found Relying Party for (" + providerIdFromTarget + ").");
119 return (RelyingParty) relyingParties.get(providerIdFromTarget);
122 // Next, check to see if the relying party is in any groups
123 RelyingParty groupParty = findRelyingPartyByGroup(providerIdFromTarget);
124 if (groupParty != null) {
125 log.info("Provider is a member of Relying Party (" + groupParty.getName() + ").");
126 return new RelyingPartyGroupWrapper(groupParty, providerIdFromTarget);
129 // OK, we can't find it... just send the default
130 RelyingParty relyingParty = getDefaultRelyingParty();
131 log.info("Could not locate Relying Party configuration for (" + providerIdFromTarget
132 + "). Using default Relying Party: (" + relyingParty.getName() + ").");
133 return new UnknownProviderWrapper(relyingParty, providerIdFromTarget);
136 private RelyingParty findRelyingPartyByGroup(String providerIdFromTarget) {
138 if (metaData == null) { return null; }
140 EntityDescriptor provider = metaData.lookup(providerIdFromTarget);
141 if (provider != null) {
142 EntitiesDescriptor parent = provider.getEntitiesDescriptor();
143 while (parent != null) {
144 if (relyingParties.containsKey(parent.getName())) {
145 log.info("Found matching Relying Party for group (" + parent.getName() + ").");
146 return (RelyingParty) relyingParties.get(parent.getName());
148 log.debug("Provider is a member of group (" + parent.getName()
149 + "), but no matching Relying Party was found.");
151 parent = parent.getEntitiesDescriptor();
157 public RelyingParty getDefaultRelyingParty() {
159 // If there is no explicit default, pick the single configured Relying
161 String defaultParty = getOriginConfig().getDefaultRelyingPartyName();
162 if (defaultParty == null || defaultParty.equals("")) { return (RelyingParty) relyingParties.values().iterator()
165 // If we do have a default specified, use it...
166 return (RelyingParty) relyingParties.get(defaultParty);
170 * Returns the relying party for a legacy provider(the default)
172 public RelyingParty getLegacyRelyingParty() {
174 RelyingParty relyingParty = getDefaultRelyingParty();
175 log.info("Request is from legacy shib target. Selecting default Relying Party: (" + relyingParty.getName()
177 return new LegacyWrapper((RelyingParty) relyingParty);
182 * Returns the appropriate relying party for the supplied service provider id.
184 public RelyingParty getRelyingParty(String providerIdFromTarget) {
186 if (providerIdFromTarget == null || providerIdFromTarget.equals("")) {
187 RelyingParty relyingParty = getDefaultRelyingParty();
188 log.info("Selecting default Relying Party: (" + relyingParty.getName() + ").");
189 return new NoMetadataWrapper((RelyingParty) relyingParty);
192 return (RelyingParty) getRelyingPartyImpl(providerIdFromTarget);
195 private void addRelyingParty(Element e) throws ServiceProviderMapperException {
197 log.debug("Found a Relying Party.");
199 if (e.getLocalName().equals("RelyingParty")) {
200 RelyingParty party = new RelyingPartyImpl(e, configuration, credentials, nameMapper);
201 log.debug("Relying Party (" + party.getName() + ") loaded.");
202 relyingParties.put(party.getName(), party);
204 } catch (ServiceProviderMapperException exc) {
205 log.error("Encountered an error while attempting to load Relying Party configuration. Skipping...");
211 * Base relying party implementation.
213 * @author Walter Hoehn
215 protected class RelyingPartyImpl implements RelyingParty {
217 private RelyingPartyIdentityProvider identityProvider;
219 private String overridenOriginProviderId;
220 private URL overridenAAUrl;
221 private URI overridenDefaultAuthMethod;
222 private String hsNameFormatId;
223 private IdPConfig configuration;
224 private boolean overridenPassThruErrors = false;
225 private boolean passThruIsOverriden = false;
226 private boolean forceAttributePush = false;
227 private boolean forceAttributeNoPush = false;
228 private boolean defaultToPOST = true;
229 private boolean wantsAssertionsSigned = false;
231 public RelyingPartyImpl(Element partyConfig, IdPConfig globalConfig, Credentials credentials,
232 NameMapper nameMapper) throws ServiceProviderMapperException {
234 configuration = globalConfig;
237 name = ((Element) partyConfig).getAttribute("name");
238 if (name == null || name.equals("")) {
239 log.error("Relying Party name not set. Add a (name) attribute to <RelyingParty>.");
240 throw new ServiceProviderMapperException("Required configuration not specified.");
242 log.debug("Loading Relying Party: (" + name + ").");
244 // Process overrides for global configuration data
245 String attribute = ((Element) partyConfig).getAttribute("providerId");
246 if (attribute != null && !attribute.equals("")) {
247 log.debug("Overriding providerId for Relying Pary (" + name + ") with (" + attribute + ").");
248 overridenOriginProviderId = attribute;
251 attribute = ((Element) partyConfig).getAttribute("AAUrl");
252 if (attribute != null && !attribute.equals("")) {
253 log.debug("Overriding AAUrl for Relying Pary (" + name + ") with (" + attribute + ").");
255 overridenAAUrl = new URL(attribute);
256 } catch (MalformedURLException e) {
257 log.error("(AAUrl) attribute to is not a valid URL.");
258 throw new ServiceProviderMapperException("Configuration is invalid.");
262 attribute = ((Element) partyConfig).getAttribute("defaultAuthMethod");
263 if (attribute != null && !attribute.equals("")) {
264 log.debug("Overriding defaultAuthMethod for Relying Pary (" + name + ") with (" + attribute + ").");
266 overridenDefaultAuthMethod = new URI(attribute);
267 } catch (URISyntaxException e1) {
268 log.error("(defaultAuthMethod) attribute to is not a valid URI.");
269 throw new ServiceProviderMapperException("Configuration is invalid.");
273 attribute = ((Element) partyConfig).getAttribute("passThruErrors");
274 if (attribute != null && !attribute.equals("")) {
275 log.debug("Overriding passThruErrors for Relying Pary (" + name + ") with (" + attribute + ").");
276 overridenPassThruErrors = Boolean.valueOf(attribute).booleanValue();
277 passThruIsOverriden = true;
280 // SSO profile defaulting
281 attribute = ((Element) partyConfig).getAttribute("defaultToPOSTProfile");
282 if (attribute != null && !attribute.equals("")) {
283 defaultToPOST = Boolean.valueOf(attribute).booleanValue();
286 log.debug("Relying party defaults to POST profile.");
288 log.debug("Relying party defaults to Artifact profile.");
291 // Relying Party wants assertions signed?
292 attribute = ((Element) partyConfig).getAttribute("signAssertions");
293 if (attribute != null && !attribute.equals("")) {
294 wantsAssertionsSigned = Boolean.valueOf(attribute).booleanValue();
296 if (wantsAssertionsSigned) {
297 log.debug("Relying party wants SAML Assertions to be signed.");
299 log.debug("Relying party does not want SAML Assertions to be signed.");
302 // Determine whether or not we are forcing attribute push on or off
303 String forcePush = ((Element) partyConfig).getAttribute("forceAttributePush");
304 String forceNoPush = ((Element) partyConfig).getAttribute("forceAttributeNoPush");
306 if (forcePush != null && Boolean.valueOf(forcePush).booleanValue() && forceNoPush != null
307 && Boolean.valueOf(forceNoPush).booleanValue()) {
308 log.error("Invalid configuration: Attribute push is forced to ON and OFF for this relying "
309 + "party. Turning off forcing in favor of profile defaults.");
311 forceAttributePush = Boolean.valueOf(forcePush).booleanValue();
312 forceAttributeNoPush = Boolean.valueOf(forceNoPush).booleanValue();
313 log.debug("Attribute push forcing is set to (" + forceAttributePush + ").");
314 log.debug("No attribute push forcing is set to (" + forceAttributeNoPush + ").");
317 // Load and verify the name format that the HS should use in
318 // assertions for this RelyingParty
319 NodeList hsNameFormats = ((Element) partyConfig).getElementsByTagNameNS(IdPConfig.configNameSpace,
321 // If no specification. Make sure we have a default mapping
322 if (hsNameFormats.getLength() < 1) {
323 if (nameMapper.getNameIdentifierMappingById(null) == null) {
324 log.error("Relying Party HS Name Format not set. Add a <HSNameFormat> element to <RelyingParty>.");
325 throw new ServiceProviderMapperException("Required configuration not specified.");
329 // We do have a specification, so make sure it points to a
330 // valid Name Mapping
331 if (hsNameFormats.getLength() > 1) {
332 log.warn("Found multiple HSNameFormat specifications for Relying Party (" + name
333 + "). Ignoring all but the first.");
336 hsNameFormatId = ((Element) hsNameFormats.item(0)).getAttribute("nameMapping");
337 if (hsNameFormatId == null || hsNameFormatId.equals("")) {
338 log.error("HS Name Format mapping not set. Add a (nameMapping) attribute to <HSNameFormat>.");
339 throw new ServiceProviderMapperException("Required configuration not specified.");
342 if (nameMapper.getNameIdentifierMappingById(hsNameFormatId) == null) {
343 log.error("Relying Party HS Name Format refers to a name mapping that is not loaded.");
344 throw new ServiceProviderMapperException("Required configuration not specified.");
348 // Load the credential for signing
349 String credentialName = ((Element) partyConfig).getAttribute("signingCredential");
350 Credential signingCredential = credentials.getCredential(credentialName);
351 if (signingCredential == null) {
352 if (credentialName == null || credentialName.equals("")) {
353 log.error("Relying Party credential not set. Add a (signingCredential) "
354 + "attribute to <RelyingParty>.");
355 throw new ServiceProviderMapperException("Required configuration not specified.");
357 log.error("Relying Party credential invalid. Fix the (signingCredential) attribute "
358 + "on <RelyingParty>.");
359 throw new ServiceProviderMapperException("Required configuration is invalid.");
364 // Initialize and Identity Provider object for this use by this relying party
365 identityProvider = new RelyingPartyIdentityProvider(overridenOriginProviderId != null
366 ? overridenOriginProviderId
367 : configuration.getProviderId(), signingCredential);
371 public String getProviderId() {
376 public String getName() {
381 public IdentityProvider getIdentityProvider() {
383 return identityProvider;
386 public boolean isLegacyProvider() {
391 public String getHSNameFormatId() {
393 return hsNameFormatId;
396 public URI getDefaultAuthMethod() {
398 if (overridenDefaultAuthMethod != null) {
399 return overridenDefaultAuthMethod;
401 return configuration.getDefaultAuthMethod();
405 public URL getAAUrl() {
407 if (overridenAAUrl != null) {
408 return overridenAAUrl;
410 return configuration.getAAUrl();
414 public boolean passThruErrors() {
416 if (passThruIsOverriden) {
417 return overridenPassThruErrors;
419 return configuration.passThruErrors();
423 public boolean forceAttributePush() {
425 return forceAttributePush;
428 public boolean forceAttributeNoPush() {
430 return forceAttributeNoPush;
433 public boolean defaultToPOSTProfile() {
435 return defaultToPOST;
438 public boolean wantsAssertionsSigned() {
440 return wantsAssertionsSigned;
444 * Default identity provider implementation.
446 * @author Walter Hoehn
448 protected class RelyingPartyIdentityProvider implements IdentityProvider {
450 private String providerId;
451 private Credential credential;
453 public RelyingPartyIdentityProvider(String providerId, Credential credential) {
455 this.providerId = providerId;
456 this.credential = credential;
460 * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getProviderId()
462 public String getProviderId() {
468 * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getSigningCredential()
470 public Credential getSigningCredential() {
479 * Relying party implementation wrapper for relying parties that are federations.
481 * @author Walter Hoehn
483 class RelyingPartyGroupWrapper implements RelyingParty {
485 private RelyingParty wrapped;
486 private String providerId;
488 RelyingPartyGroupWrapper(RelyingParty wrapped, String providerId) {
490 this.wrapped = wrapped;
491 this.providerId = providerId;
494 public String getName() {
496 return wrapped.getName();
499 public boolean isLegacyProvider() {
504 public IdentityProvider getIdentityProvider() {
506 return wrapped.getIdentityProvider();
509 public String getProviderId() {
514 public String getHSNameFormatId() {
516 return wrapped.getHSNameFormatId();
519 public URL getAAUrl() {
521 return wrapped.getAAUrl();
524 public URI getDefaultAuthMethod() {
526 return wrapped.getDefaultAuthMethod();
529 public boolean passThruErrors() {
531 return wrapped.passThruErrors();
534 public boolean forceAttributePush() {
536 return wrapped.forceAttributePush();
539 public boolean forceAttributeNoPush() {
541 return wrapped.forceAttributeNoPush();
544 public boolean defaultToPOSTProfile() {
546 return wrapped.defaultToPOSTProfile();
549 public boolean wantsAssertionsSigned() {
551 return wrapped.wantsAssertionsSigned();
556 * Relying party implementation wrapper for anonymous service providers.
558 * @author Walter Hoehn
560 protected class UnknownProviderWrapper implements RelyingParty {
562 protected RelyingParty wrapped;
563 protected String providerId;
565 protected UnknownProviderWrapper(RelyingParty wrapped, String providerId) {
567 this.wrapped = wrapped;
568 this.providerId = providerId;
571 public String getName() {
573 return wrapped.getName();
576 public IdentityProvider getIdentityProvider() {
578 return wrapped.getIdentityProvider();
581 public String getProviderId() {
586 public String getHSNameFormatId() {
588 return wrapped.getHSNameFormatId();
591 public boolean isLegacyProvider() {
593 return wrapped.isLegacyProvider();
596 public URL getAAUrl() {
598 return wrapped.getAAUrl();
601 public URI getDefaultAuthMethod() {
603 return wrapped.getDefaultAuthMethod();
606 public boolean passThruErrors() {
608 return wrapped.passThruErrors();
611 public boolean forceAttributePush() {
616 public boolean forceAttributeNoPush() {
621 public boolean defaultToPOSTProfile() {
626 public boolean wantsAssertionsSigned() {
628 return wrapped.wantsAssertionsSigned();
633 * Relying party wrapper for Shibboleth <=1.1 service providers.
635 * @author Walter Hoehn
637 class LegacyWrapper extends UnknownProviderWrapper implements RelyingParty {
639 LegacyWrapper(RelyingParty wrapped) {
641 super(wrapped, null);
644 public boolean isLegacyProvider() {
649 public String getHSNameFormatId() {
651 return ((RelyingParty) wrapped).getHSNameFormatId();
654 public URL getAAUrl() {
656 return ((RelyingParty) wrapped).getAAUrl();
659 public URI getDefaultAuthMethod() {
661 return ((RelyingParty) wrapped).getDefaultAuthMethod();
666 * Relying party wrapper for providers for which we have no metadata
668 * @author Walter Hoehn
670 class NoMetadataWrapper extends UnknownProviderWrapper implements RelyingParty {
672 NoMetadataWrapper(RelyingParty wrapped) {
674 super(wrapped, null);
677 public String getHSNameFormatId() {
679 return ((RelyingParty) wrapped).getHSNameFormatId();
682 public URL getAAUrl() {
684 return ((RelyingParty) wrapped).getAAUrl();
687 public URI getDefaultAuthMethod() {
689 return ((RelyingParty) wrapped).getDefaultAuthMethod();