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 getIdPConfig() {
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 = getIdPConfig().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 overridenIdPProviderId;
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;
232 private boolean wantsSchemaHack = false;
234 public RelyingPartyImpl(Element partyConfig, IdPConfig globalConfig, Credentials credentials,
235 NameMapper nameMapper) throws ServiceProviderMapperException {
237 configuration = globalConfig;
240 name = ((Element) partyConfig).getAttribute("name");
241 if (name == null || name.equals("")) {
242 log.error("Relying Party name not set. Add a (name) attribute to <RelyingParty>.");
243 throw new ServiceProviderMapperException("Required configuration not specified.");
245 log.debug("Loading Relying Party: (" + name + ").");
247 // Process overrides for global configuration data
248 String attribute = ((Element) partyConfig).getAttribute("providerId");
249 if (attribute != null && !attribute.equals("")) {
250 log.debug("Overriding providerId for Relying Pary (" + name + ") with (" + attribute + ").");
251 overridenIdPProviderId = attribute;
254 attribute = ((Element) partyConfig).getAttribute("AAUrl");
255 if (attribute != null && !attribute.equals("")) {
256 log.debug("Overriding AAUrl for Relying Pary (" + name + ") with (" + attribute + ").");
258 overridenAAUrl = new URL(attribute);
259 } catch (MalformedURLException e) {
260 log.error("(AAUrl) attribute to is not a valid URL.");
261 throw new ServiceProviderMapperException("Configuration is invalid.");
265 attribute = ((Element) partyConfig).getAttribute("defaultAuthMethod");
266 if (attribute != null && !attribute.equals("")) {
267 log.debug("Overriding defaultAuthMethod for Relying Pary (" + name + ") with (" + attribute + ").");
269 overridenDefaultAuthMethod = new URI(attribute);
270 } catch (URISyntaxException e1) {
271 log.error("(defaultAuthMethod) attribute to is not a valid URI.");
272 throw new ServiceProviderMapperException("Configuration is invalid.");
276 attribute = ((Element) partyConfig).getAttribute("passThruErrors");
277 if (attribute != null && !attribute.equals("")) {
278 log.debug("Overriding passThruErrors for Relying Pary (" + name + ") with (" + attribute + ").");
279 overridenPassThruErrors = Boolean.valueOf(attribute).booleanValue();
280 passThruIsOverriden = true;
283 // SSO profile defaulting
284 attribute = ((Element) partyConfig).getAttribute("defaultToPOSTProfile");
285 if (attribute != null && !attribute.equals("")) {
286 defaultToPOST = Boolean.valueOf(attribute).booleanValue();
289 log.debug("Relying party defaults to POST profile.");
291 log.debug("Relying party defaults to Artifact profile.");
294 // Relying Party wants assertions signed?
295 attribute = ((Element) partyConfig).getAttribute("signAssertions");
296 if (attribute != null && !attribute.equals("")) {
297 wantsAssertionsSigned = Boolean.valueOf(attribute).booleanValue();
299 if (wantsAssertionsSigned) {
300 log.debug("Relying party wants SAML Assertions to be signed.");
302 log.debug("Relying party does not want SAML Assertions to be signed.");
305 // Decide whether or not to use the schema hack for old xerces
306 attribute = ((Element) partyConfig).getAttribute("schemaHack");
307 if (attribute != null && !attribute.equals("")) {
308 wantsSchemaHack = Boolean.valueOf(attribute).booleanValue();
310 if (wantsSchemaHack) {
311 log.debug("XML schema hack enabled for this relying party.");
314 // Set a default target for use in artifact redirects
315 defaultTarget = ((Element) partyConfig).getAttribute("defaultTarget");
317 // Determine whether or not we are forcing attribute push on or off
318 String forcePush = ((Element) partyConfig).getAttribute("forceAttributePush");
319 String forceNoPush = ((Element) partyConfig).getAttribute("forceAttributeNoPush");
321 if (forcePush != null && Boolean.valueOf(forcePush).booleanValue() && forceNoPush != null
322 && Boolean.valueOf(forceNoPush).booleanValue()) {
323 log.error("Invalid configuration: Attribute push is forced to ON and OFF for this relying "
324 + "party. Turning off forcing in favor of profile defaults.");
326 forceAttributePush = Boolean.valueOf(forcePush).booleanValue();
327 forceAttributeNoPush = Boolean.valueOf(forceNoPush).booleanValue();
328 log.debug("Attribute push forcing is set to (" + forceAttributePush + ").");
329 log.debug("No attribute push forcing is set to (" + forceAttributeNoPush + ").");
332 attribute = ((Element) partyConfig).getAttribute("preferredArtifactType");
333 if (attribute != null && !attribute.equals("")) {
334 log.debug("Overriding AAUrl for Relying Pary (" + name + ") with (" + attribute + ").");
336 preferredArtifactType = Integer.parseInt(attribute);
337 } catch (NumberFormatException e) {
338 log.error("(preferredArtifactType) attribute to is not a valid integer.");
339 throw new ServiceProviderMapperException("Configuration is invalid.");
341 log.debug("Preferred artifact type: (" + preferredArtifactType + ").");
344 // Load and verify the name format that the HS should use in
345 // assertions for this RelyingParty
346 NodeList hsNameFormats = ((Element) partyConfig).getElementsByTagNameNS(IdPConfig.configNameSpace,
348 // If no specification. Make sure we have a default mapping
349 if (hsNameFormats.getLength() < 1) {
350 if (nameMapper.getNameIdentifierMappingById(null) == null) {
351 log.error("Relying Party HS Name Format not set. Add a <HSNameFormat> element to <RelyingParty>.");
352 throw new ServiceProviderMapperException("Required configuration not specified.");
356 // We do have a specification, so make sure it points to a
357 // valid Name Mapping
358 if (hsNameFormats.getLength() > 1) {
359 log.warn("Found multiple HSNameFormat specifications for Relying Party (" + name
360 + "). Ignoring all but the first.");
363 hsNameFormatId = ((Element) hsNameFormats.item(0)).getAttribute("nameMapping");
364 if (hsNameFormatId == null || hsNameFormatId.equals("")) {
365 log.error("HS Name Format mapping not set. Add a (nameMapping) attribute to <HSNameFormat>.");
366 throw new ServiceProviderMapperException("Required configuration not specified.");
369 if (nameMapper.getNameIdentifierMappingById(hsNameFormatId) == null) {
370 log.error("Relying Party HS Name Format refers to a name mapping that is not loaded.");
371 throw new ServiceProviderMapperException("Required configuration not specified.");
375 // Load the credential for signing
376 String credentialName = ((Element) partyConfig).getAttribute("signingCredential");
377 Credential signingCredential = credentials.getCredential(credentialName);
378 if (signingCredential == null) {
379 if (credentialName == null || credentialName.equals("")) {
380 log.error("Relying Party credential not set. Add a (signingCredential) "
381 + "attribute to <RelyingParty>.");
382 throw new ServiceProviderMapperException("Required configuration not specified.");
384 log.error("Relying Party credential invalid. Fix the (signingCredential) attribute "
385 + "on <RelyingParty>.");
386 throw new ServiceProviderMapperException("Required configuration is invalid.");
391 // Initialize and Identity Provider object for this use by this relying party
392 identityProvider = new RelyingPartyIdentityProvider(overridenIdPProviderId != null
393 ? overridenIdPProviderId
394 : configuration.getProviderId(), signingCredential);
398 public String getProviderId() {
403 public String getName() {
408 public IdentityProvider getIdentityProvider() {
410 return identityProvider;
413 public boolean isLegacyProvider() {
418 public String getHSNameFormatId() {
420 return hsNameFormatId;
423 public URI getDefaultAuthMethod() {
425 if (overridenDefaultAuthMethod != null) {
426 return overridenDefaultAuthMethod;
428 return configuration.getDefaultAuthMethod();
432 public URL getAAUrl() {
434 if (overridenAAUrl != null) {
435 return overridenAAUrl;
437 return configuration.getAAUrl();
441 public boolean passThruErrors() {
443 if (passThruIsOverriden) {
444 return overridenPassThruErrors;
446 return configuration.passThruErrors();
450 public boolean forceAttributePush() {
452 return forceAttributePush;
455 public boolean forceAttributeNoPush() {
457 return forceAttributeNoPush;
460 public boolean defaultToPOSTProfile() {
462 return defaultToPOST;
465 public boolean wantsAssertionsSigned() {
467 return wantsAssertionsSigned;
470 public int getPreferredArtifactType() {
472 return preferredArtifactType;
475 public String getDefaultTarget() {
477 return defaultTarget;
480 public boolean wantsSchemaHack() {
482 return wantsSchemaHack;
486 * Default identity provider implementation.
488 * @author Walter Hoehn
490 protected class RelyingPartyIdentityProvider implements IdentityProvider {
492 private String providerId;
493 private Credential credential;
495 public RelyingPartyIdentityProvider(String providerId, Credential credential) {
497 this.providerId = providerId;
498 this.credential = credential;
502 * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getProviderId()
504 public String getProviderId() {
510 * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getSigningCredential()
512 public Credential getSigningCredential() {
521 * Relying party implementation wrapper for relying parties that are groups.
523 * @author Walter Hoehn
525 class RelyingPartyGroupWrapper implements RelyingParty {
527 private RelyingParty wrapped;
528 private String providerId;
530 RelyingPartyGroupWrapper(RelyingParty wrapped, String providerId) {
532 this.wrapped = wrapped;
533 this.providerId = providerId;
536 public String getName() {
538 return wrapped.getName();
541 public boolean isLegacyProvider() {
546 public IdentityProvider getIdentityProvider() {
548 return wrapped.getIdentityProvider();
551 public String getProviderId() {
556 public String getHSNameFormatId() {
558 return wrapped.getHSNameFormatId();
561 public URL getAAUrl() {
563 return wrapped.getAAUrl();
566 public URI getDefaultAuthMethod() {
568 return wrapped.getDefaultAuthMethod();
571 public boolean passThruErrors() {
573 return wrapped.passThruErrors();
576 public boolean forceAttributePush() {
578 return wrapped.forceAttributePush();
581 public boolean forceAttributeNoPush() {
583 return wrapped.forceAttributeNoPush();
586 public boolean defaultToPOSTProfile() {
588 return wrapped.defaultToPOSTProfile();
591 public boolean wantsAssertionsSigned() {
593 return wrapped.wantsAssertionsSigned();
596 public int getPreferredArtifactType() {
598 return wrapped.getPreferredArtifactType();
601 public String getDefaultTarget() {
603 return wrapped.getDefaultTarget();
606 public boolean wantsSchemaHack() {
608 return wrapped.wantsSchemaHack();
613 * Relying party implementation wrapper for anonymous service providers.
615 * @author Walter Hoehn
617 protected class UnknownProviderWrapper implements RelyingParty {
619 protected RelyingParty wrapped;
620 protected String providerId;
622 protected UnknownProviderWrapper(RelyingParty wrapped, String providerId) {
624 this.wrapped = wrapped;
625 this.providerId = providerId;
628 public String getName() {
630 return wrapped.getName();
633 public IdentityProvider getIdentityProvider() {
635 return wrapped.getIdentityProvider();
638 public String getProviderId() {
643 public String getHSNameFormatId() {
645 return wrapped.getHSNameFormatId();
648 public boolean isLegacyProvider() {
650 return wrapped.isLegacyProvider();
653 public URL getAAUrl() {
655 return wrapped.getAAUrl();
658 public URI getDefaultAuthMethod() {
660 return wrapped.getDefaultAuthMethod();
663 public boolean passThruErrors() {
665 return wrapped.passThruErrors();
668 public boolean forceAttributePush() {
673 public boolean forceAttributeNoPush() {
678 public boolean defaultToPOSTProfile() {
683 public boolean wantsAssertionsSigned() {
685 return wrapped.wantsAssertionsSigned();
688 public int getPreferredArtifactType() {
690 return wrapped.getPreferredArtifactType();
693 public String getDefaultTarget() {
695 return wrapped.getDefaultTarget();
698 public boolean wantsSchemaHack() {
700 return wrapped.wantsSchemaHack();
705 * Relying party wrapper for Shibboleth <=1.1 service providers.
707 * @author Walter Hoehn
709 class LegacyWrapper extends UnknownProviderWrapper implements RelyingParty {
711 LegacyWrapper(RelyingParty wrapped) {
713 super(wrapped, null);
716 public boolean isLegacyProvider() {
721 public String getHSNameFormatId() {
723 return ((RelyingParty) wrapped).getHSNameFormatId();
726 public URL getAAUrl() {
728 return ((RelyingParty) wrapped).getAAUrl();
731 public URI getDefaultAuthMethod() {
733 return ((RelyingParty) wrapped).getDefaultAuthMethod();
738 * Relying party wrapper for providers for which we have no metadata
740 * @author Walter Hoehn
742 class NoMetadataWrapper extends UnknownProviderWrapper implements RelyingParty {
744 NoMetadataWrapper(RelyingParty wrapped) {
746 super(wrapped, null);
749 public String getHSNameFormatId() {
751 return ((RelyingParty) wrapped).getHSNameFormatId();
754 public URL getAAUrl() {
756 return ((RelyingParty) wrapped).getAAUrl();
759 public URI getDefaultAuthMethod() {
761 return ((RelyingParty) wrapped).getDefaultAuthMethod();