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 group of providers (perhaps a federation). Uses the
47 * default relying party if neither is found.
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;
230 private int preferredArtifactType = 1;
231 private String defaultTarget;
233 public RelyingPartyImpl(Element partyConfig, IdPConfig globalConfig, Credentials credentials,
234 NameMapper nameMapper) throws ServiceProviderMapperException {
236 configuration = globalConfig;
239 name = ((Element) partyConfig).getAttribute("name");
240 if (name == null || name.equals("")) {
241 log.error("Relying Party name not set. Add a (name) attribute to <RelyingParty>.");
242 throw new ServiceProviderMapperException("Required configuration not specified.");
244 log.debug("Loading Relying Party: (" + name + ").");
246 // Process overrides for global configuration data
247 String attribute = ((Element) partyConfig).getAttribute("providerId");
248 if (attribute != null && !attribute.equals("")) {
249 log.debug("Overriding providerId for Relying Pary (" + name + ") with (" + attribute + ").");
250 overridenOriginProviderId = attribute;
253 attribute = ((Element) partyConfig).getAttribute("AAUrl");
254 if (attribute != null && !attribute.equals("")) {
255 log.debug("Overriding AAUrl for Relying Pary (" + name + ") with (" + attribute + ").");
257 overridenAAUrl = new URL(attribute);
258 } catch (MalformedURLException e) {
259 log.error("(AAUrl) attribute to is not a valid URL.");
260 throw new ServiceProviderMapperException("Configuration is invalid.");
264 attribute = ((Element) partyConfig).getAttribute("defaultAuthMethod");
265 if (attribute != null && !attribute.equals("")) {
266 log.debug("Overriding defaultAuthMethod for Relying Pary (" + name + ") with (" + attribute + ").");
268 overridenDefaultAuthMethod = new URI(attribute);
269 } catch (URISyntaxException e1) {
270 log.error("(defaultAuthMethod) attribute to is not a valid URI.");
271 throw new ServiceProviderMapperException("Configuration is invalid.");
275 attribute = ((Element) partyConfig).getAttribute("passThruErrors");
276 if (attribute != null && !attribute.equals("")) {
277 log.debug("Overriding passThruErrors for Relying Pary (" + name + ") with (" + attribute + ").");
278 overridenPassThruErrors = Boolean.valueOf(attribute).booleanValue();
279 passThruIsOverriden = true;
282 // SSO profile defaulting
283 attribute = ((Element) partyConfig).getAttribute("defaultToPOSTProfile");
284 if (attribute != null && !attribute.equals("")) {
285 defaultToPOST = Boolean.valueOf(attribute).booleanValue();
288 log.debug("Relying party defaults to POST profile.");
290 log.debug("Relying party defaults to Artifact profile.");
293 // Relying Party wants assertions signed?
294 attribute = ((Element) partyConfig).getAttribute("signAssertions");
295 if (attribute != null && !attribute.equals("")) {
296 wantsAssertionsSigned = Boolean.valueOf(attribute).booleanValue();
298 if (wantsAssertionsSigned) {
299 log.debug("Relying party wants SAML Assertions to be signed.");
301 log.debug("Relying party does not want SAML Assertions to be signed.");
304 // Set a default target for use in artifact redirects
305 defaultTarget = ((Element) partyConfig).getAttribute("defaultTarget");
307 // Determine whether or not we are forcing attribute push on or off
308 String forcePush = ((Element) partyConfig).getAttribute("forceAttributePush");
309 String forceNoPush = ((Element) partyConfig).getAttribute("forceAttributeNoPush");
311 if (forcePush != null && Boolean.valueOf(forcePush).booleanValue() && forceNoPush != null
312 && Boolean.valueOf(forceNoPush).booleanValue()) {
313 log.error("Invalid configuration: Attribute push is forced to ON and OFF for this relying "
314 + "party. Turning off forcing in favor of profile defaults.");
316 forceAttributePush = Boolean.valueOf(forcePush).booleanValue();
317 forceAttributeNoPush = Boolean.valueOf(forceNoPush).booleanValue();
318 log.debug("Attribute push forcing is set to (" + forceAttributePush + ").");
319 log.debug("No attribute push forcing is set to (" + forceAttributeNoPush + ").");
322 attribute = ((Element) partyConfig).getAttribute("preferredArtifactType");
323 if (attribute != null && !attribute.equals("")) {
324 log.debug("Overriding AAUrl for Relying Pary (" + name + ") with (" + attribute + ").");
326 preferredArtifactType = Integer.parseInt(attribute);
327 } catch (NumberFormatException e) {
328 log.error("(preferredArtifactType) attribute to is not a valid integer.");
329 throw new ServiceProviderMapperException("Configuration is invalid.");
331 log.debug("Preferred artifact type: (" + preferredArtifactType + ").");
334 // Load and verify the name format that the HS should use in
335 // assertions for this RelyingParty
336 NodeList hsNameFormats = ((Element) partyConfig).getElementsByTagNameNS(IdPConfig.configNameSpace,
338 // If no specification. Make sure we have a default mapping
339 if (hsNameFormats.getLength() < 1) {
340 if (nameMapper.getNameIdentifierMappingById(null) == null) {
341 log.error("Relying Party HS Name Format not set. Add a <HSNameFormat> element to <RelyingParty>.");
342 throw new ServiceProviderMapperException("Required configuration not specified.");
346 // We do have a specification, so make sure it points to a
347 // valid Name Mapping
348 if (hsNameFormats.getLength() > 1) {
349 log.warn("Found multiple HSNameFormat specifications for Relying Party (" + name
350 + "). Ignoring all but the first.");
353 hsNameFormatId = ((Element) hsNameFormats.item(0)).getAttribute("nameMapping");
354 if (hsNameFormatId == null || hsNameFormatId.equals("")) {
355 log.error("HS Name Format mapping not set. Add a (nameMapping) attribute to <HSNameFormat>.");
356 throw new ServiceProviderMapperException("Required configuration not specified.");
359 if (nameMapper.getNameIdentifierMappingById(hsNameFormatId) == null) {
360 log.error("Relying Party HS Name Format refers to a name mapping that is not loaded.");
361 throw new ServiceProviderMapperException("Required configuration not specified.");
365 // Load the credential for signing
366 String credentialName = ((Element) partyConfig).getAttribute("signingCredential");
367 Credential signingCredential = credentials.getCredential(credentialName);
368 if (signingCredential == null) {
369 if (credentialName == null || credentialName.equals("")) {
370 log.error("Relying Party credential not set. Add a (signingCredential) "
371 + "attribute to <RelyingParty>.");
372 throw new ServiceProviderMapperException("Required configuration not specified.");
374 log.error("Relying Party credential invalid. Fix the (signingCredential) attribute "
375 + "on <RelyingParty>.");
376 throw new ServiceProviderMapperException("Required configuration is invalid.");
381 // Initialize and Identity Provider object for this use by this relying party
382 identityProvider = new RelyingPartyIdentityProvider(overridenOriginProviderId != null
383 ? overridenOriginProviderId
384 : configuration.getProviderId(), signingCredential);
388 public String getProviderId() {
393 public String getName() {
398 public IdentityProvider getIdentityProvider() {
400 return identityProvider;
403 public boolean isLegacyProvider() {
408 public String getHSNameFormatId() {
410 return hsNameFormatId;
413 public URI getDefaultAuthMethod() {
415 if (overridenDefaultAuthMethod != null) {
416 return overridenDefaultAuthMethod;
418 return configuration.getDefaultAuthMethod();
422 public URL getAAUrl() {
424 if (overridenAAUrl != null) {
425 return overridenAAUrl;
427 return configuration.getAAUrl();
431 public boolean passThruErrors() {
433 if (passThruIsOverriden) {
434 return overridenPassThruErrors;
436 return configuration.passThruErrors();
440 public boolean forceAttributePush() {
442 return forceAttributePush;
445 public boolean forceAttributeNoPush() {
447 return forceAttributeNoPush;
450 public boolean defaultToPOSTProfile() {
452 return defaultToPOST;
455 public boolean wantsAssertionsSigned() {
457 return wantsAssertionsSigned;
460 public int getPreferredArtifactType() {
462 return preferredArtifactType;
465 public String getDefaultTarget() {
467 return defaultTarget;
471 * Default identity provider implementation.
473 * @author Walter Hoehn
475 protected class RelyingPartyIdentityProvider implements IdentityProvider {
477 private String providerId;
478 private Credential credential;
480 public RelyingPartyIdentityProvider(String providerId, Credential credential) {
482 this.providerId = providerId;
483 this.credential = credential;
487 * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getProviderId()
489 public String getProviderId() {
495 * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getSigningCredential()
497 public Credential getSigningCredential() {
506 * Relying party implementation wrapper for relying parties that are groups.
508 * @author Walter Hoehn
510 class RelyingPartyGroupWrapper implements RelyingParty {
512 private RelyingParty wrapped;
513 private String providerId;
515 RelyingPartyGroupWrapper(RelyingParty wrapped, String providerId) {
517 this.wrapped = wrapped;
518 this.providerId = providerId;
521 public String getName() {
523 return wrapped.getName();
526 public boolean isLegacyProvider() {
531 public IdentityProvider getIdentityProvider() {
533 return wrapped.getIdentityProvider();
536 public String getProviderId() {
541 public String getHSNameFormatId() {
543 return wrapped.getHSNameFormatId();
546 public URL getAAUrl() {
548 return wrapped.getAAUrl();
551 public URI getDefaultAuthMethod() {
553 return wrapped.getDefaultAuthMethod();
556 public boolean passThruErrors() {
558 return wrapped.passThruErrors();
561 public boolean forceAttributePush() {
563 return wrapped.forceAttributePush();
566 public boolean forceAttributeNoPush() {
568 return wrapped.forceAttributeNoPush();
571 public boolean defaultToPOSTProfile() {
573 return wrapped.defaultToPOSTProfile();
576 public boolean wantsAssertionsSigned() {
578 return wrapped.wantsAssertionsSigned();
581 public int getPreferredArtifactType() {
583 return wrapped.getPreferredArtifactType();
586 public String getDefaultTarget() {
588 return wrapped.getDefaultTarget();
593 * Relying party implementation wrapper for anonymous service providers.
595 * @author Walter Hoehn
597 protected class UnknownProviderWrapper implements RelyingParty {
599 protected RelyingParty wrapped;
600 protected String providerId;
602 protected UnknownProviderWrapper(RelyingParty wrapped, String providerId) {
604 this.wrapped = wrapped;
605 this.providerId = providerId;
608 public String getName() {
610 return wrapped.getName();
613 public IdentityProvider getIdentityProvider() {
615 return wrapped.getIdentityProvider();
618 public String getProviderId() {
623 public String getHSNameFormatId() {
625 return wrapped.getHSNameFormatId();
628 public boolean isLegacyProvider() {
630 return wrapped.isLegacyProvider();
633 public URL getAAUrl() {
635 return wrapped.getAAUrl();
638 public URI getDefaultAuthMethod() {
640 return wrapped.getDefaultAuthMethod();
643 public boolean passThruErrors() {
645 return wrapped.passThruErrors();
648 public boolean forceAttributePush() {
653 public boolean forceAttributeNoPush() {
658 public boolean defaultToPOSTProfile() {
663 public boolean wantsAssertionsSigned() {
665 return wrapped.wantsAssertionsSigned();
668 public int getPreferredArtifactType() {
670 return wrapped.getPreferredArtifactType();
673 public String getDefaultTarget() {
675 return wrapped.getDefaultTarget();
680 * Relying party wrapper for Shibboleth <=1.1 service providers.
682 * @author Walter Hoehn
684 class LegacyWrapper extends UnknownProviderWrapper implements RelyingParty {
686 LegacyWrapper(RelyingParty wrapped) {
688 super(wrapped, null);
691 public boolean isLegacyProvider() {
696 public String getHSNameFormatId() {
698 return ((RelyingParty) wrapped).getHSNameFormatId();
701 public URL getAAUrl() {
703 return ((RelyingParty) wrapped).getAAUrl();
706 public URI getDefaultAuthMethod() {
708 return ((RelyingParty) wrapped).getDefaultAuthMethod();
713 * Relying party wrapper for providers for which we have no metadata
715 * @author Walter Hoehn
717 class NoMetadataWrapper extends UnknownProviderWrapper implements RelyingParty {
719 NoMetadataWrapper(RelyingParty wrapped) {
721 super(wrapped, null);
724 public String getHSNameFormatId() {
726 return ((RelyingParty) wrapped).getHSNameFormatId();
729 public URL getAAUrl() {
731 return ((RelyingParty) wrapped).getAAUrl();
734 public URI getDefaultAuthMethod() {
736 return ((RelyingParty) wrapped).getDefaultAuthMethod();