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;
230 public RelyingPartyImpl(Element partyConfig, IdPConfig globalConfig, Credentials credentials,
231 NameMapper nameMapper) throws ServiceProviderMapperException {
233 configuration = globalConfig;
236 name = ((Element) partyConfig).getAttribute("name");
237 if (name == null || name.equals("")) {
238 log.error("Relying Party name not set. Add a (name) attribute to <RelyingParty>.");
239 throw new ServiceProviderMapperException("Required configuration not specified.");
241 log.debug("Loading Relying Party: (" + name + ").");
243 // Process overrides for global configuration data
244 String attribute = ((Element) partyConfig).getAttribute("providerId");
245 if (attribute != null && !attribute.equals("")) {
246 log.debug("Overriding providerId for Relying Pary (" + name + ") with (" + attribute + ").");
247 overridenOriginProviderId = attribute;
250 attribute = ((Element) partyConfig).getAttribute("AAUrl");
251 if (attribute != null && !attribute.equals("")) {
252 log.debug("Overriding AAUrl for Relying Pary (" + name + ") with (" + attribute + ").");
254 overridenAAUrl = new URL(attribute);
255 } catch (MalformedURLException e) {
256 log.error("(AAUrl) attribute to is not a valid URL.");
257 throw new ServiceProviderMapperException("Configuration is invalid.");
261 attribute = ((Element) partyConfig).getAttribute("defaultAuthMethod");
262 if (attribute != null && !attribute.equals("")) {
263 log.debug("Overriding defaultAuthMethod for Relying Pary (" + name + ") with (" + attribute + ").");
265 overridenDefaultAuthMethod = new URI(attribute);
266 } catch (URISyntaxException e1) {
267 log.error("(defaultAuthMethod) attribute to is not a valid URI.");
268 throw new ServiceProviderMapperException("Configuration is invalid.");
272 attribute = ((Element) partyConfig).getAttribute("passThruErrors");
273 if (attribute != null && !attribute.equals("")) {
274 log.debug("Overriding passThruErrors for Relying Pary (" + name + ") with (" + attribute + ").");
275 overridenPassThruErrors = Boolean.valueOf(attribute).booleanValue();
276 passThruIsOverriden = true;
279 // SSO profile defaulting
280 attribute = ((Element) partyConfig).getAttribute("defaultToPOSTProfile");
281 if (attribute != null && !attribute.equals("")) {
282 defaultToPOST = Boolean.valueOf(attribute).booleanValue();
284 log.debug("Relying party defaults to POST profile.");
286 log.debug("Relying party defaults to Artifact profile.");
290 // Determine whether or not we are forcing attribute push on or off
291 String forcePush = ((Element) partyConfig).getAttribute("forceAttributePush");
292 String forceNoPush = ((Element) partyConfig).getAttribute("forceAttributeNoPush");
294 if (forcePush != null && Boolean.valueOf(forcePush).booleanValue() && forceNoPush != null
295 && Boolean.valueOf(forceNoPush).booleanValue()) {
296 log.error("Invalid configuration: Attribute push is forced to ON and OFF for this relying "
297 + "party. Turning off forcing in favor of profile defaults.");
299 forceAttributePush = Boolean.valueOf(forcePush).booleanValue();
300 forceAttributeNoPush = Boolean.valueOf(forceNoPush).booleanValue();
301 log.debug("Attribute push forcing is set to (" + forceAttributePush + ").");
302 log.debug("No attribute push forcing is set to (" + forceAttributeNoPush + ").");
305 // Load and verify the name format that the HS should use in
306 // assertions for this RelyingParty
307 NodeList hsNameFormats = ((Element) partyConfig).getElementsByTagNameNS(IdPConfig.configNameSpace,
309 // If no specification. Make sure we have a default mapping
310 if (hsNameFormats.getLength() < 1) {
311 if (nameMapper.getNameIdentifierMappingById(null) == null) {
312 log.error("Relying Party HS Name Format not set. Add a <HSNameFormat> element to <RelyingParty>.");
313 throw new ServiceProviderMapperException("Required configuration not specified.");
317 // We do have a specification, so make sure it points to a
318 // valid Name Mapping
319 if (hsNameFormats.getLength() > 1) {
320 log.warn("Found multiple HSNameFormat specifications for Relying Party (" + name
321 + "). Ignoring all but the first.");
324 hsNameFormatId = ((Element) hsNameFormats.item(0)).getAttribute("nameMapping");
325 if (hsNameFormatId == null || hsNameFormatId.equals("")) {
326 log.error("HS Name Format mapping not set. Add a (nameMapping) attribute to <HSNameFormat>.");
327 throw new ServiceProviderMapperException("Required configuration not specified.");
330 if (nameMapper.getNameIdentifierMappingById(hsNameFormatId) == null) {
331 log.error("Relying Party HS Name Format refers to a name mapping that is not loaded.");
332 throw new ServiceProviderMapperException("Required configuration not specified.");
336 // Load the credential for signing
337 String credentialName = ((Element) partyConfig).getAttribute("signingCredential");
338 Credential signingCredential = credentials.getCredential(credentialName);
339 if (signingCredential == null) {
340 if (credentialName == null || credentialName.equals("")) {
341 log.error("Relying Party credential not set. Add a (signingCredential) "
342 + "attribute to <RelyingParty>.");
343 throw new ServiceProviderMapperException("Required configuration not specified.");
345 log.error("Relying Party credential invalid. Fix the (signingCredential) attribute "
346 + "on <RelyingParty>.");
347 throw new ServiceProviderMapperException("Required configuration is invalid.");
352 // Initialize and Identity Provider object for this use by this relying party
353 identityProvider = new RelyingPartyIdentityProvider(overridenOriginProviderId != null
354 ? overridenOriginProviderId
355 : configuration.getProviderId(), signingCredential);
359 public String getProviderId() {
364 public String getName() {
369 public IdentityProvider getIdentityProvider() {
371 return identityProvider;
374 public boolean isLegacyProvider() {
379 public String getHSNameFormatId() {
381 return hsNameFormatId;
384 public URI getDefaultAuthMethod() {
386 if (overridenDefaultAuthMethod != null) {
387 return overridenDefaultAuthMethod;
389 return configuration.getDefaultAuthMethod();
393 public URL getAAUrl() {
395 if (overridenAAUrl != null) {
396 return overridenAAUrl;
398 return configuration.getAAUrl();
402 public boolean passThruErrors() {
404 if (passThruIsOverriden) {
405 return overridenPassThruErrors;
407 return configuration.passThruErrors();
411 public boolean forceAttributePush() {
413 return forceAttributePush;
416 public boolean forceAttributeNoPush() {
418 return forceAttributeNoPush;
421 public boolean defaultToPOSTProfile() {
423 return defaultToPOST;
427 * Default identity provider implementation.
429 * @author Walter Hoehn
431 protected class RelyingPartyIdentityProvider implements IdentityProvider {
433 private String providerId;
434 private Credential credential;
436 public RelyingPartyIdentityProvider(String providerId, Credential credential) {
438 this.providerId = providerId;
439 this.credential = credential;
443 * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getProviderId()
445 public String getProviderId() {
451 * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getSigningCredential()
453 public Credential getSigningCredential() {
462 * Relying party implementation wrapper for relying parties that are federations.
464 * @author Walter Hoehn
466 class RelyingPartyGroupWrapper implements RelyingParty {
468 private RelyingParty wrapped;
469 private String providerId;
471 RelyingPartyGroupWrapper(RelyingParty wrapped, String providerId) {
473 this.wrapped = wrapped;
474 this.providerId = providerId;
477 public String getName() {
479 return wrapped.getName();
482 public boolean isLegacyProvider() {
487 public IdentityProvider getIdentityProvider() {
489 return wrapped.getIdentityProvider();
492 public String getProviderId() {
497 public String getHSNameFormatId() {
499 return wrapped.getHSNameFormatId();
502 public URL getAAUrl() {
504 return wrapped.getAAUrl();
507 public URI getDefaultAuthMethod() {
509 return wrapped.getDefaultAuthMethod();
512 public boolean passThruErrors() {
514 return wrapped.passThruErrors();
517 public boolean forceAttributePush() {
519 return wrapped.forceAttributePush();
522 public boolean forceAttributeNoPush() {
524 return wrapped.forceAttributeNoPush();
527 public boolean defaultToPOSTProfile() {
529 return wrapped.defaultToPOSTProfile();
534 * Relying party implementation wrapper for anonymous service providers.
536 * @author Walter Hoehn
538 protected class UnknownProviderWrapper implements RelyingParty {
540 protected RelyingParty wrapped;
541 protected String providerId;
543 protected UnknownProviderWrapper(RelyingParty wrapped, String providerId) {
545 this.wrapped = wrapped;
546 this.providerId = providerId;
549 public String getName() {
551 return wrapped.getName();
554 public IdentityProvider getIdentityProvider() {
556 return wrapped.getIdentityProvider();
559 public String getProviderId() {
564 public String getHSNameFormatId() {
566 return wrapped.getHSNameFormatId();
569 public boolean isLegacyProvider() {
571 return wrapped.isLegacyProvider();
574 public URL getAAUrl() {
576 return wrapped.getAAUrl();
579 public URI getDefaultAuthMethod() {
581 return wrapped.getDefaultAuthMethod();
584 public boolean passThruErrors() {
586 return wrapped.passThruErrors();
589 public boolean forceAttributePush() {
594 public boolean forceAttributeNoPush() {
599 public boolean defaultToPOSTProfile() {
606 * Relying party wrapper for Shibboleth <=1.1 service providers.
608 * @author Walter Hoehn
610 class LegacyWrapper extends UnknownProviderWrapper implements RelyingParty {
612 LegacyWrapper(RelyingParty wrapped) {
614 super(wrapped, null);
617 public boolean isLegacyProvider() {
622 public String getHSNameFormatId() {
624 return ((RelyingParty) wrapped).getHSNameFormatId();
627 public URL getAAUrl() {
629 return ((RelyingParty) wrapped).getAAUrl();
632 public URI getDefaultAuthMethod() {
634 return ((RelyingParty) wrapped).getDefaultAuthMethod();
639 * Relying party wrapper for providers for which we have no metadata
641 * @author Walter Hoehn
643 class NoMetadataWrapper extends UnknownProviderWrapper implements RelyingParty {
645 NoMetadataWrapper(RelyingParty wrapped) {
647 super(wrapped, null);
650 public String getHSNameFormatId() {
652 return ((RelyingParty) wrapped).getHSNameFormatId();
655 public URL getAAUrl() {
657 return ((RelyingParty) wrapped).getAAUrl();
660 public URI getDefaultAuthMethod() {
662 return ((RelyingParty) wrapped).getDefaultAuthMethod();