2 * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package edu.internet2.middleware.shibboleth.common;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.HashMap;
24 import org.apache.log4j.Logger;
25 import org.opensaml.saml2.metadata.EntitiesDescriptor;
26 import org.opensaml.saml2.metadata.EntityDescriptor;
27 import org.opensaml.saml2.metadata.provider.MetadataProvider;
28 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
29 import org.opensaml.xml.XMLObject;
30 import org.w3c.dom.Attr;
31 import org.w3c.dom.Element;
32 import org.w3c.dom.NamedNodeMap;
33 import org.w3c.dom.NodeList;
35 import edu.internet2.middleware.shibboleth.idp.IdPConfig;
38 * Class for determining the effective relying party from the unique id of the service provider. Checks first for an
39 * exact match on the service provider, then for membership in a group of providers (perhaps a federation). Uses the
40 * default relying party if neither is found.
42 * @author Walter Hoehn
44 public class RelyingPartyMapper {
46 private static Logger log = Logger.getLogger(RelyingPartyMapper.class.getName());
47 protected Map<String, NamedRelyingParty> relyingParties = new HashMap<String, NamedRelyingParty>();
48 protected RelyingParty defaultRelyingParty;
49 protected RelyingParty anonymousRelyingParty;
51 private MetadataProvider metaData;
52 private Credentials credentials;
54 public RelyingPartyMapper(Element rawConfig, Credentials credentials) throws RelyingPartyMapperException {
56 if (credentials == null) { throw new IllegalArgumentException(
57 "RelyingPartyMapper cannot be started without proper access to the IdP configuration."); }
59 this.credentials = credentials;
61 // Load specified <RelyingParty/> elements
62 NodeList itemElements = rawConfig.getElementsByTagNameNS(IdPConfig.configNameSpace, "RelyingParty");
63 for (int i = 0; i < itemElements.getLength(); i++) {
64 addRelyingParty((Element) itemElements.item(i));
67 // Load <AnonymousRelyingParty/> element, if specified
68 itemElements = rawConfig.getElementsByTagNameNS(IdPConfig.configNameSpace, "AnonymousRelyingParty");
69 if (itemElements.getLength() > 1) {
70 log.error("Found multiple <AnonymousRelyingParty/> elements. Ignoring all but the first...");
72 if (itemElements.getLength() < 1) {
73 log.error("No <AnonymousRelyingParty/> elements found. Disabling support for responding "
74 + "to anonymous relying parties.");
76 addAnonymousRelyingParty((Element) itemElements.item(0));
79 // Load <DefaultRelyingParty/> element, if specified
80 itemElements = rawConfig.getElementsByTagNameNS(IdPConfig.configNameSpace, "DefaultRelyingParty");
81 if (itemElements.getLength() > 1) {
82 log.error("Found multiple <DefaultRelyingParty/> elements. Ignoring all but the first...");
84 if (itemElements.getLength() < 1) {
85 log.error("No <DefaultRelyingParty/> elements found. Disabling support for responding "
86 + "to anonymous relying parties.");
88 addDefaultRelyingParty((Element) itemElements.item(0));
92 public boolean anonymousSuported() {
94 return (anonymousRelyingParty != null);
97 public RelyingParty getAnonymousRelyingParty() {
99 return anonymousRelyingParty;
103 public void setMetadata(MetadataProvider metadata) {
105 this.metaData = metadata;
108 private NamedRelyingParty findRelyingPartyByGroup(String providerIdFromSP) {
110 if (metaData == null) { return null; }
112 // Attempt to lookup the entity in the metdata
113 EntityDescriptor provider = null;
115 provider = metaData.getEntityDescriptor(providerIdFromSP);
116 } catch (MetadataProviderException e) {
117 log.error("Problem encountered during metadata lookup of entity (" + providerIdFromSP + "): " + e);
120 // OK, if we found it travel recurse down the tree of parent entities
121 if (provider != null) {
122 EntitiesDescriptor parent = getParentEntitiesDescriptor(provider);
124 while (parent != null) {
125 if (parent.getName() != null) {
126 if (relyingParties.containsKey(parent.getName())) {
127 log.info("Found matching Relying Party for group (" + parent.getName() + ").");
128 return (NamedRelyingParty) relyingParties.get(parent.getName());
130 log.debug("Provider is a member of group (" + parent.getName()
131 + "), but no matching Relying Party was found.");
134 parent = getParentEntitiesDescriptor(parent);
141 * Returns the appropriate relying party for the supplied service provider id.
143 public RelyingParty getRelyingParty(String providerIdFromSP) {
145 if (providerIdFromSP == null || providerIdFromSP.equals("")) { throw new IllegalArgumentException(
146 "Incorrect use of ServiceProviderMapper. Cannot lookup relying party without a provider ID."); }
148 // Look for a configuration for the specific relying party
149 if (relyingParties.containsKey(providerIdFromSP)) {
150 log.info("Found Relying Party for (" + providerIdFromSP + ").");
151 return (RelyingParty) relyingParties.get(providerIdFromSP);
155 // Next, check to see if the relying party is in any groups
156 NamedRelyingParty groupParty = findRelyingPartyByGroup(providerIdFromSP);
157 if (groupParty != null) {
158 log.info("Provider is a member of Relying Party (" + groupParty.getName() + ").");
162 // Use default if we have one
163 if (defaultRelyingParty != null) {
164 log.debug("No matching relying party found. Using default relying party.");
165 return defaultRelyingParty;
168 // Alright, there's nothing available to us
173 private void addRelyingParty(Element e) throws RelyingPartyMapperException {
175 log.debug("Found a Relying Party configuration element.");
177 if (e.getLocalName().equals("RelyingParty")) {
178 NamedRelyingParty party = new NamedRelyingParty(e, credentials);
179 log.debug("Relying Party (" + party.getName() + ") loaded.");
180 relyingParties.put(party.getName(), party);
182 } catch (RelyingPartyMapperException exc) {
183 log.error("Encountered an error while attempting to load Relying Party configuration. Skipping...");
187 private void addAnonymousRelyingParty(Element e) throws RelyingPartyMapperException {
189 log.debug("Found an Anonymous Relying Party configuration element.");
191 if (e.getLocalName().equals("AnonymousRelyingParty")) {
192 RelyingParty party = new RelyingPartyImpl(e, credentials);
193 log.debug("Anonymous Relying Party loaded.");
194 anonymousRelyingParty = party;
196 } catch (RelyingPartyMapperException exc) {
197 log.error("Encountered an error while attempting to load Anonymous Relying"
198 + " Party configuration. Skipping...");
202 private void addDefaultRelyingParty(Element e) throws RelyingPartyMapperException {
204 log.debug("Found a Default Relying Party configuration element.");
206 if (e.getLocalName().equals("DefaultRelyingParty")) {
207 RelyingParty party = new RelyingPartyImpl(e, credentials);
208 log.debug("Default Relying Party loaded.");
209 defaultRelyingParty = party;
211 } catch (RelyingPartyMapperException exc) {
212 log.error("Encountered an error while attempting to load Default "
213 + "Relying Party configuration. Skipping...");
217 private EntitiesDescriptor getParentEntitiesDescriptor(XMLObject entity) {
219 Object parent = entity.getParent();
221 if (parent instanceof EntitiesDescriptor) { return (EntitiesDescriptor) parent; }
227 * Base relying party implementation.
229 * @author Walter Hoehn
231 protected class RelyingPartyImpl implements RelyingParty {
233 private RelyingPartyIdentityProvider identityProvider;
234 private String providerId;
235 private boolean passThruErrors = false;
236 private boolean forceAttributePush = false;
237 private boolean forceAttributeNoPush = false;
238 private boolean singleAssertion = false;
239 private boolean defaultToPOST = true;
240 private boolean wantsAssertionsSigned = false;
241 private int preferredArtifactType = 1;
242 private String defaultTarget;
243 private Map<String, String> extensionAttributes = new HashMap<String, String>();
244 private Collection<String> knownAttributes = Arrays.asList(new String[]{"providerId", "passThruErrors",
245 "defaultToPOSTProfile", "singleAssertion", "signAssertions", "defaultTarget", "forceAttributePush",
246 "preferredArtifactType", "signingCredential"});
248 public RelyingPartyImpl(Element partyConfig, Credentials credentials) throws RelyingPartyMapperException {
250 // Process overrides for global configuration data
251 String attribute = ((Element) partyConfig).getAttribute("providerId");
252 if (attribute == null || attribute.equals("")) {
253 log.error("Relying Party providerId not set. Add a (providerId) " + "attribute to <RelyingParty>.");
254 throw new RelyingPartyMapperException("Required configuration not specified.");
256 providerId = attribute;
257 log.debug("Setting providerId for Relying Party to (" + attribute + ").");
259 attribute = ((Element) partyConfig).getAttribute("passThruErrors");
260 if (attribute != null && !attribute.equals("")) {
261 log.debug("Setting passThruErrors for Relying Pary with (" + attribute + ").");
262 passThruErrors = Boolean.valueOf(attribute).booleanValue();
265 // SSO profile defaulting
266 attribute = ((Element) partyConfig).getAttribute("defaultToPOSTProfile");
267 if (attribute != null && !attribute.equals("")) {
268 defaultToPOST = Boolean.valueOf(attribute).booleanValue();
271 log.debug("Relying party defaults to POST profile.");
273 log.debug("Relying party defaults to Artifact profile.");
276 attribute = ((Element) partyConfig).getAttribute("singleAssertion");
277 if (attribute != null && !attribute.equals("")) {
278 singleAssertion = Boolean.valueOf(attribute).booleanValue();
280 if (singleAssertion) {
281 log.debug("Relying party defaults to a single assertion when pushing attributes.");
283 log.debug("Relying party defaults to multiple assertions when pushing attributes.");
286 // Relying Party wants assertions signed?
287 attribute = ((Element) partyConfig).getAttribute("signAssertions");
288 if (attribute != null && !attribute.equals("")) {
289 wantsAssertionsSigned = Boolean.valueOf(attribute).booleanValue();
291 if (wantsAssertionsSigned) {
292 log.debug("Relying party wants SAML Assertions to be signed.");
294 log.debug("Relying party does not want SAML Assertions to be signed.");
297 // Set a default target for use in artifact redirects
298 defaultTarget = ((Element) partyConfig).getAttribute("defaultTarget");
300 // Determine whether or not we are forcing attribute push on or off
301 String forcePush = ((Element) partyConfig).getAttribute("forceAttributePush");
302 String forceNoPush = ((Element) partyConfig).getAttribute("forceAttributeNoPush");
304 if (forcePush != null && Boolean.valueOf(forcePush).booleanValue() && forceNoPush != null
305 && Boolean.valueOf(forceNoPush).booleanValue()) {
306 log.error("Invalid configuration: Attribute push is forced to ON and OFF for this relying "
307 + "party. Turning off forcing in favor of profile defaults.");
309 forceAttributePush = Boolean.valueOf(forcePush).booleanValue();
310 forceAttributeNoPush = Boolean.valueOf(forceNoPush).booleanValue();
311 log.debug("Attribute push forcing is set to (" + forceAttributePush + ").");
312 log.debug("No attribute push forcing is set to (" + forceAttributeNoPush + ").");
315 attribute = ((Element) partyConfig).getAttribute("preferredArtifactType");
316 if (attribute != null && !attribute.equals("")) {
317 log.debug("Overriding preferredArtifactType for Relying Pary with (" + attribute + ").");
319 preferredArtifactType = Integer.parseInt(attribute);
320 } catch (NumberFormatException e) {
321 log.error("(preferredArtifactType) attribute to is not a valid integer.");
322 throw new RelyingPartyMapperException("Configuration is invalid.");
324 log.debug("Preferred artifact type: (" + preferredArtifactType + ").");
327 // Load the credential for signing
328 String credentialName = ((Element) partyConfig).getAttribute("signingCredential");
329 Credential signingCredential = credentials.getCredential(credentialName);
330 if (signingCredential == null) {
331 if (credentialName == null || credentialName.equals("")) {
332 log.error("Relying Party credential not set. Add a (signingCredential) "
333 + "attribute to <RelyingParty>.");
334 throw new RelyingPartyMapperException("Required configuration not specified.");
336 log.error("Relying Party credential invalid. Fix the (signingCredential) attribute "
337 + "on <RelyingParty>.");
338 throw new RelyingPartyMapperException("Required configuration is invalid.");
343 // Initialize and Identity Provider object for this use by this relying party
344 identityProvider = new RelyingPartyIdentityProvider(providerId, signingCredential);
346 // Track extension attributes
347 NamedNodeMap nodeMap = ((Element) partyConfig).getAttributes();
348 for (int i = 0; i < nodeMap.getLength(); i++) {
349 Attr attr = (Attr) nodeMap.item(i);
350 if (!knownAttributes.contains(attr.getName())) {
351 extensionAttributes.put(attr.getName(), attr.getValue());
356 public IdentityProvider getIdentityProvider() {
358 return identityProvider;
361 public boolean passThruErrors() {
363 return passThruErrors;
366 public boolean forceAttributePush() {
368 return forceAttributePush;
371 public boolean forceAttributeNoPush() {
373 return forceAttributeNoPush;
376 public boolean singleAssertion() {
378 return singleAssertion;
381 public boolean defaultToPOSTProfile() {
383 return defaultToPOST;
386 public boolean wantsAssertionsSigned() {
388 return wantsAssertionsSigned;
391 public int getPreferredArtifactType() {
393 return preferredArtifactType;
396 public String getDefaultTarget() {
398 return defaultTarget;
401 public String getCustomAttribute(String name) {
403 return extensionAttributes.get(name);
407 * Default identity provider implementation.
409 * @author Walter Hoehn
411 protected class RelyingPartyIdentityProvider implements IdentityProvider {
413 private String providerId;
414 private Credential credential;
416 public RelyingPartyIdentityProvider(String providerId, Credential credential) {
418 this.providerId = providerId;
419 this.credential = credential;
423 * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getProviderId()
425 public String getProviderId() {
431 * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getSigningCredential()
433 public Credential getSigningCredential() {
441 class NamedRelyingParty extends RelyingPartyImpl {
445 public NamedRelyingParty(Element partyConfig, Credentials credentials) throws RelyingPartyMapperException {
447 super(partyConfig, credentials);
449 name = ((Element) partyConfig).getAttribute("name");
450 if (name == null || name.equals("")) {
451 log.error("Relying Party name not set. Add a (name) attribute to <RelyingParty>.");
452 throw new RelyingPartyMapperException("Required configuration not specified.");
454 log.debug("Loading Relying Party: (" + name + ").");
457 public String getName() {