use the new session manager interface
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / common / RelyingPartyMapper.java
1 /*
2  * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package edu.internet2.middleware.shibboleth.common;
18
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.Map;
23
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;
34
35 import edu.internet2.middleware.shibboleth.idp.IdPConfig;
36
37 /**
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.
41  * 
42  * @author Walter Hoehn
43  */
44 public class RelyingPartyMapper {
45
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;
50
51         private MetadataProvider metaData;
52         private Credentials credentials;
53
54         public RelyingPartyMapper(Element rawConfig, Credentials credentials) throws RelyingPartyMapperException {
55
56                 if (credentials == null) { throw new IllegalArgumentException(
57                                 "RelyingPartyMapper cannot be started without proper access to the IdP configuration."); }
58
59                 this.credentials = credentials;
60
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));
65                 }
66
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...");
71                 }
72                 if (itemElements.getLength() < 1) {
73                         log.error("No <AnonymousRelyingParty/> elements found.  Disabling support for responding "
74                                         + "to anonymous relying parties.");
75                 } else {
76                         addAnonymousRelyingParty((Element) itemElements.item(0));
77                 }
78
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...");
83                 }
84                 if (itemElements.getLength() < 1) {
85                         log.error("No <DefaultRelyingParty/> elements found.  Disabling support for responding "
86                                         + "to anonymous relying parties.");
87                 } else {
88                         addDefaultRelyingParty((Element) itemElements.item(0));
89                 }
90         }
91
92         public boolean anonymousSuported() {
93
94                 return (anonymousRelyingParty != null);
95         }
96
97         public RelyingParty getAnonymousRelyingParty() {
98
99                 return anonymousRelyingParty;
100
101         }
102
103         public void setMetadata(MetadataProvider metadata) {
104
105                 this.metaData = metadata;
106         }
107
108         private NamedRelyingParty findRelyingPartyByGroup(String providerIdFromSP) {
109
110                 if (metaData == null) { return null; }
111
112                 // Attempt to lookup the entity in the metdata
113                 EntityDescriptor provider = null;
114                 try {
115                         provider = metaData.getEntityDescriptor(providerIdFromSP);
116                 } catch (MetadataProviderException e) {
117                         log.error("Problem encountered during metadata lookup of entity (" + providerIdFromSP + "): " + e);
118                 }
119
120                 // OK, if we found it travel recurse down the tree of parent entities
121                 if (provider != null) {
122                         EntitiesDescriptor parent = getParentEntitiesDescriptor(provider);
123
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());
129                                         } else {
130                                                 log.debug("Provider is a member of group (" + parent.getName()
131                                                                 + "), but no matching Relying Party was found.");
132                                         }
133                                 }
134                                 parent = getParentEntitiesDescriptor(parent);
135                         }
136                 }
137                 return null;
138         }
139
140         /**
141          * Returns the appropriate relying party for the supplied service provider id.
142          */
143         public RelyingParty getRelyingParty(String providerIdFromSP) {
144
145                 if (providerIdFromSP == null || providerIdFromSP.equals("")) { throw new IllegalArgumentException(
146                                 "Incorrect use of ServiceProviderMapper.  Cannot lookup relying party without a provider ID."); }
147
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);
152                 }
153
154                 // Lookup by group
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() + ").");
159                         return groupParty;
160                 }
161
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;
166                 }
167
168                 // Alright, there's nothing available to us
169                 return null;
170
171         }
172
173         private void addRelyingParty(Element e) throws RelyingPartyMapperException {
174
175                 log.debug("Found a Relying Party configuration element.");
176                 try {
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);
181                         }
182                 } catch (RelyingPartyMapperException exc) {
183                         log.error("Encountered an error while attempting to load Relying Party configuration.  Skipping...");
184                 }
185         }
186
187         private void addAnonymousRelyingParty(Element e) throws RelyingPartyMapperException {
188
189                 log.debug("Found an Anonymous Relying Party configuration element.");
190                 try {
191                         if (e.getLocalName().equals("AnonymousRelyingParty")) {
192                                 RelyingParty party = new RelyingPartyImpl(e, credentials);
193                                 log.debug("Anonymous Relying Party loaded.");
194                                 anonymousRelyingParty = party;
195                         }
196                 } catch (RelyingPartyMapperException exc) {
197                         log.error("Encountered an error while attempting to load Anonymous Relying"
198                                         + " Party configuration.  Skipping...");
199                 }
200         }
201
202         private void addDefaultRelyingParty(Element e) throws RelyingPartyMapperException {
203
204                 log.debug("Found a Default Relying Party configuration element.");
205                 try {
206                         if (e.getLocalName().equals("DefaultRelyingParty")) {
207                                 RelyingParty party = new RelyingPartyImpl(e, credentials);
208                                 log.debug("Default Relying Party loaded.");
209                                 defaultRelyingParty = party;
210                         }
211                 } catch (RelyingPartyMapperException exc) {
212                         log.error("Encountered an error while attempting to load Default "
213                                         + "Relying Party configuration.  Skipping...");
214                 }
215         }
216
217         private EntitiesDescriptor getParentEntitiesDescriptor(XMLObject entity) {
218
219                 Object parent = entity.getParent();
220
221                 if (parent instanceof EntitiesDescriptor) { return (EntitiesDescriptor) parent; }
222
223                 return null;
224         }
225
226         /**
227          * Base relying party implementation.
228          * 
229          * @author Walter Hoehn
230          */
231         protected class RelyingPartyImpl implements RelyingParty {
232
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"});
247
248                 public RelyingPartyImpl(Element partyConfig, Credentials credentials) throws RelyingPartyMapperException {
249
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.");
255                         }
256                         providerId = attribute;
257                         log.debug("Setting providerId for Relying Party to (" + attribute + ").");
258
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();
263                         }
264
265                         // SSO profile defaulting
266                         attribute = ((Element) partyConfig).getAttribute("defaultToPOSTProfile");
267                         if (attribute != null && !attribute.equals("")) {
268                                 defaultToPOST = Boolean.valueOf(attribute).booleanValue();
269                         }
270                         if (defaultToPOST) {
271                                 log.debug("Relying party defaults to POST profile.");
272                         } else {
273                                 log.debug("Relying party defaults to Artifact profile.");
274                         }
275
276                         attribute = ((Element) partyConfig).getAttribute("singleAssertion");
277                         if (attribute != null && !attribute.equals("")) {
278                                 singleAssertion = Boolean.valueOf(attribute).booleanValue();
279                         }
280                         if (singleAssertion) {
281                                 log.debug("Relying party defaults to a single assertion when pushing attributes.");
282                         } else {
283                                 log.debug("Relying party defaults to multiple assertions when pushing attributes.");
284                         }
285
286                         // Relying Party wants assertions signed?
287                         attribute = ((Element) partyConfig).getAttribute("signAssertions");
288                         if (attribute != null && !attribute.equals("")) {
289                                 wantsAssertionsSigned = Boolean.valueOf(attribute).booleanValue();
290                         }
291                         if (wantsAssertionsSigned) {
292                                 log.debug("Relying party wants SAML Assertions to be signed.");
293                         } else {
294                                 log.debug("Relying party does not want SAML Assertions to be signed.");
295                         }
296
297                         // Set a default target for use in artifact redirects
298                         defaultTarget = ((Element) partyConfig).getAttribute("defaultTarget");
299
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");
303
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.");
308                         } else {
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 + ").");
313                         }
314
315                         attribute = ((Element) partyConfig).getAttribute("preferredArtifactType");
316                         if (attribute != null && !attribute.equals("")) {
317                                 log.debug("Overriding preferredArtifactType for Relying Pary with (" + attribute + ").");
318                                 try {
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.");
323                                 }
324                                 log.debug("Preferred artifact type: (" + preferredArtifactType + ").");
325                         }
326
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.");
335                                 } else {
336                                         log.error("Relying Party credential invalid.  Fix the (signingCredential) attribute "
337                                                         + "on <RelyingParty>.");
338                                         throw new RelyingPartyMapperException("Required configuration is invalid.");
339                                 }
340
341                         }
342
343                         // Initialize and Identity Provider object for this use by this relying party
344                         identityProvider = new RelyingPartyIdentityProvider(providerId, signingCredential);
345
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());
352                                 }
353                         }
354                 }
355
356                 public IdentityProvider getIdentityProvider() {
357
358                         return identityProvider;
359                 }
360
361                 public boolean passThruErrors() {
362
363                         return passThruErrors;
364                 }
365
366                 public boolean forceAttributePush() {
367
368                         return forceAttributePush;
369                 }
370
371                 public boolean forceAttributeNoPush() {
372
373                         return forceAttributeNoPush;
374                 }
375
376                 public boolean singleAssertion() {
377
378                         return singleAssertion;
379                 }
380
381                 public boolean defaultToPOSTProfile() {
382
383                         return defaultToPOST;
384                 }
385
386                 public boolean wantsAssertionsSigned() {
387
388                         return wantsAssertionsSigned;
389                 }
390
391                 public int getPreferredArtifactType() {
392
393                         return preferredArtifactType;
394                 }
395
396                 public String getDefaultTarget() {
397
398                         return defaultTarget;
399                 }
400
401                 public String getCustomAttribute(String name) {
402
403                         return extensionAttributes.get(name);
404                 }
405
406                 /**
407                  * Default identity provider implementation.
408                  * 
409                  * @author Walter Hoehn
410                  */
411                 protected class RelyingPartyIdentityProvider implements IdentityProvider {
412
413                         private String providerId;
414                         private Credential credential;
415
416                         public RelyingPartyIdentityProvider(String providerId, Credential credential) {
417
418                                 this.providerId = providerId;
419                                 this.credential = credential;
420                         }
421
422                         /*
423                          * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getProviderId()
424                          */
425                         public String getProviderId() {
426
427                                 return providerId;
428                         }
429
430                         /*
431                          * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getSigningCredential()
432                          */
433                         public Credential getSigningCredential() {
434
435                                 return credential;
436                         }
437                 }
438
439         }
440
441         class NamedRelyingParty extends RelyingPartyImpl {
442
443                 private String name;
444
445                 public NamedRelyingParty(Element partyConfig, Credentials credentials) throws RelyingPartyMapperException {
446
447                         super(partyConfig, credentials);
448                         // Get party name
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.");
453                         }
454                         log.debug("Loading Relying Party: (" + name + ").");
455                 }
456
457                 public String getName() {
458
459                         return name;
460                 }
461         }
462 }