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;
229 public RelyingPartyImpl(Element partyConfig, IdPConfig globalConfig, Credentials credentials,
230 NameMapper nameMapper) throws ServiceProviderMapperException {
232 configuration = globalConfig;
235 name = ((Element) partyConfig).getAttribute("name");
236 if (name == null || name.equals("")) {
237 log.error("Relying Party name not set. Add a (name) attribute to <RelyingParty>.");
238 throw new ServiceProviderMapperException("Required configuration not specified.");
240 log.debug("Loading Relying Party: (" + name + ").");
242 // Process overrides for global configuration data
243 String attribute = ((Element) partyConfig).getAttribute("providerId");
244 if (attribute != null && !attribute.equals("")) {
245 log.debug("Overriding providerId for Relying Pary (" + name + ") with (" + attribute + ").");
246 overridenOriginProviderId = attribute;
249 attribute = ((Element) partyConfig).getAttribute("AAUrl");
250 if (attribute != null && !attribute.equals("")) {
251 log.debug("Overriding AAUrl for Relying Pary (" + name + ") with (" + attribute + ").");
253 overridenAAUrl = new URL(attribute);
254 } catch (MalformedURLException e) {
255 log.error("(AAUrl) attribute to is not a valid URL.");
256 throw new ServiceProviderMapperException("Configuration is invalid.");
260 attribute = ((Element) partyConfig).getAttribute("defaultAuthMethod");
261 if (attribute != null && !attribute.equals("")) {
262 log.debug("Overriding defaultAuthMethod for Relying Pary (" + name + ") with (" + attribute + ").");
264 overridenDefaultAuthMethod = new URI(attribute);
265 } catch (URISyntaxException e1) {
266 log.error("(defaultAuthMethod) attribute to is not a valid URI.");
267 throw new ServiceProviderMapperException("Configuration is invalid.");
271 attribute = ((Element) partyConfig).getAttribute("passThruErrors");
272 if (attribute != null && !attribute.equals("")) {
273 log.debug("Overriding passThruErrors for Relying Pary (" + name + ") with (" + attribute + ").");
274 overridenPassThruErrors = Boolean.valueOf(attribute).booleanValue();
275 passThruIsOverriden = true;
278 // Determine whether or not we are forcing attribute push on or off
279 String forcePush = ((Element) partyConfig).getAttribute("forceAttributePush");
280 String forceNoPush = ((Element) partyConfig).getAttribute("forceAttributeNoPush");
282 if (forcePush != null && Boolean.valueOf(forcePush).booleanValue() && forceNoPush != null
283 && Boolean.valueOf(forceNoPush).booleanValue()) {
284 log.error("Invalid configuration: Attribute push is forced to ON and OFF for this relying "
285 + "party. Turning off forcing in favor of profile defaults.");
287 forceAttributePush = Boolean.valueOf(forcePush).booleanValue();
288 forceAttributeNoPush = Boolean.valueOf(forceNoPush).booleanValue();
289 log.debug("Attribute push forcing is set to (" + forceAttributePush + ").");
290 log.debug("No attribute push forcing is set to (" + forceAttributeNoPush + ").");
293 // Load and verify the name format that the HS should use in
294 // assertions for this RelyingParty
295 NodeList hsNameFormats = ((Element) partyConfig).getElementsByTagNameNS(IdPConfig.configNameSpace,
297 // If no specification. Make sure we have a default mapping
298 if (hsNameFormats.getLength() < 1) {
299 if (nameMapper.getNameIdentifierMappingById(null) == null) {
300 log.error("Relying Party HS Name Format not set. Add a <HSNameFormat> element to <RelyingParty>.");
301 throw new ServiceProviderMapperException("Required configuration not specified.");
305 // We do have a specification, so make sure it points to a
306 // valid Name Mapping
307 if (hsNameFormats.getLength() > 1) {
308 log.warn("Found multiple HSNameFormat specifications for Relying Party (" + name
309 + "). Ignoring all but the first.");
312 hsNameFormatId = ((Element) hsNameFormats.item(0)).getAttribute("nameMapping");
313 if (hsNameFormatId == null || hsNameFormatId.equals("")) {
314 log.error("HS Name Format mapping not set. Add a (nameMapping) attribute to <HSNameFormat>.");
315 throw new ServiceProviderMapperException("Required configuration not specified.");
318 if (nameMapper.getNameIdentifierMappingById(hsNameFormatId) == null) {
319 log.error("Relying Party HS Name Format refers to a name mapping that is not loaded.");
320 throw new ServiceProviderMapperException("Required configuration not specified.");
324 // Load the credential for signing
325 String credentialName = ((Element) partyConfig).getAttribute("signingCredential");
326 Credential signingCredential = credentials.getCredential(credentialName);
327 if (signingCredential == null) {
328 if (credentialName == null || credentialName.equals("")) {
329 log.error("Relying Party credential not set. Add a (signingCredential) "
330 + "attribute to <RelyingParty>.");
331 throw new ServiceProviderMapperException("Required configuration not specified.");
333 log.error("Relying Party credential invalid. Fix the (signingCredential) attribute "
334 + "on <RelyingParty>.");
335 throw new ServiceProviderMapperException("Required configuration is invalid.");
340 // Initialize and Identity Provider object for this use by this relying party
341 identityProvider = new RelyingPartyIdentityProvider(overridenOriginProviderId != null
342 ? overridenOriginProviderId
343 : configuration.getProviderId(), signingCredential);
347 public String getProviderId() {
352 public String getName() {
357 public IdentityProvider getIdentityProvider() {
359 return identityProvider;
362 public boolean isLegacyProvider() {
367 public String getHSNameFormatId() {
369 return hsNameFormatId;
372 public URI getDefaultAuthMethod() {
374 if (overridenDefaultAuthMethod != null) {
375 return overridenDefaultAuthMethod;
377 return configuration.getDefaultAuthMethod();
381 public URL getAAUrl() {
383 if (overridenAAUrl != null) {
384 return overridenAAUrl;
386 return configuration.getAAUrl();
390 public boolean passThruErrors() {
392 if (passThruIsOverriden) {
393 return overridenPassThruErrors;
395 return configuration.passThruErrors();
399 public boolean forceAttributePush() {
401 return forceAttributePush;
404 public boolean forceAttributeNoPush() {
406 return forceAttributeNoPush;
410 * Default identity provider implementation.
412 * @author Walter Hoehn
414 protected class RelyingPartyIdentityProvider implements IdentityProvider {
416 private String providerId;
417 private Credential credential;
419 public RelyingPartyIdentityProvider(String providerId, Credential credential) {
421 this.providerId = providerId;
422 this.credential = credential;
426 * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getProviderId()
428 public String getProviderId() {
434 * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getSigningCredential()
436 public Credential getSigningCredential() {
444 * Relying party implementation wrapper for relying parties that are federations.
446 * @author Walter Hoehn
448 class RelyingPartyGroupWrapper implements RelyingParty {
450 private RelyingParty wrapped;
451 private String providerId;
453 RelyingPartyGroupWrapper(RelyingParty wrapped, String providerId) {
455 this.wrapped = wrapped;
456 this.providerId = providerId;
459 public String getName() {
461 return wrapped.getName();
464 public boolean isLegacyProvider() {
469 public IdentityProvider getIdentityProvider() {
471 return wrapped.getIdentityProvider();
474 public String getProviderId() {
479 public String getHSNameFormatId() {
481 return wrapped.getHSNameFormatId();
484 public URL getAAUrl() {
486 return wrapped.getAAUrl();
489 public URI getDefaultAuthMethod() {
491 return wrapped.getDefaultAuthMethod();
494 public boolean passThruErrors() {
496 return wrapped.passThruErrors();
499 public boolean forceAttributePush() {
501 return wrapped.forceAttributePush();
504 public boolean forceAttributeNoPush() {
506 return wrapped.forceAttributeNoPush();
511 * Relying party implementation wrapper for anonymous service providers.
513 * @author Walter Hoehn
515 protected class UnknownProviderWrapper implements RelyingParty {
517 protected RelyingParty wrapped;
518 protected String providerId;
520 protected UnknownProviderWrapper(RelyingParty wrapped, String providerId) {
522 this.wrapped = wrapped;
523 this.providerId = providerId;
526 public String getName() {
528 return wrapped.getName();
531 public IdentityProvider getIdentityProvider() {
533 return wrapped.getIdentityProvider();
536 public String getProviderId() {
541 public String getHSNameFormatId() {
543 return wrapped.getHSNameFormatId();
546 public boolean isLegacyProvider() {
548 return wrapped.isLegacyProvider();
551 public URL getAAUrl() {
553 return wrapped.getAAUrl();
556 public URI getDefaultAuthMethod() {
558 return wrapped.getDefaultAuthMethod();
561 public boolean passThruErrors() {
563 return wrapped.passThruErrors();
566 public boolean forceAttributePush() {
571 public boolean forceAttributeNoPush() {
578 * Relying party wrapper for Shibboleth <=1.1 service providers.
580 * @author Walter Hoehn
582 class LegacyWrapper extends UnknownProviderWrapper implements RelyingParty {
584 LegacyWrapper(RelyingParty wrapped) {
586 super(wrapped, null);
589 public boolean isLegacyProvider() {
594 public String getHSNameFormatId() {
596 return ((RelyingParty) wrapped).getHSNameFormatId();
599 public URL getAAUrl() {
601 return ((RelyingParty) wrapped).getAAUrl();
604 public URI getDefaultAuthMethod() {
606 return ((RelyingParty) wrapped).getDefaultAuthMethod();
611 * Relying party wrapper for providers for which we have no metadata
613 * @author Walter Hoehn
615 class NoMetadataWrapper extends UnknownProviderWrapper implements RelyingParty {
617 NoMetadataWrapper(RelyingParty wrapped) {
619 super(wrapped, null);
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();