More re-organization of the Relying Party code in order to support new configuration...
[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.HashMap;
20 import java.util.Map;
21
22 import org.apache.log4j.Logger;
23 import org.opensaml.saml2.metadata.EntitiesDescriptor;
24 import org.opensaml.saml2.metadata.EntityDescriptor;
25 import org.opensaml.saml2.metadata.provider.MetadataProvider;
26 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
27 import org.opensaml.xml.XMLObject;
28 import org.w3c.dom.Element;
29 import org.w3c.dom.NodeList;
30
31 import edu.internet2.middleware.shibboleth.idp.IdPConfig;
32
33 /**
34  * Class for determining the effective relying party from the unique id of the service provider. Checks first for an
35  * exact match on the service provider, then for membership in a group of providers (perhaps a federation). Uses the
36  * default relying party if neither is found.
37  * 
38  * @author Walter Hoehn
39  */
40 public class RelyingPartyMapper {
41
42         private static Logger log = Logger.getLogger(RelyingPartyMapper.class.getName());
43         protected Map<String, NamedRelyingParty> relyingParties = new HashMap<String, NamedRelyingParty>();
44         protected RelyingParty defaultRelyingParty;
45         protected RelyingParty anonymousRelyingParty;
46
47         private MetadataProvider metaData;
48         private Credentials credentials;
49
50         public RelyingPartyMapper(Element rawConfig, Credentials credentials) throws RelyingPartyMapperException {
51
52                 if (credentials == null) { throw new IllegalArgumentException(
53                                 "RelyingPartyMapper cannot be started without proper access to the IdP configuration."); }
54
55                 this.credentials = credentials;
56
57                 // Load specified <RelyingParty/> elements
58                 NodeList itemElements = rawConfig.getElementsByTagNameNS(IdPConfig.configNameSpace, "RelyingParty");
59                 for (int i = 0; i < itemElements.getLength(); i++) {
60                         addRelyingParty((Element) itemElements.item(i));
61                 }
62
63                 // Load <AnonymousRelyingParty/> element, if specified
64                 itemElements = rawConfig.getElementsByTagNameNS(IdPConfig.configNameSpace, "AnonymousRelyingParty");
65                 if (itemElements.getLength() > 1) {
66                         log.error("Found multiple <AnonymousRelyingParty/> elements.  Ignoring all but the first...");
67                 }
68                 if (itemElements.getLength() < 1) {
69                         log.error("No <AnonymousRelyingParty/> elements found.  Disabling support for responding "
70                                         + "to anonymous relying parties.");
71                 } else {
72                         addAnonymousRelyingParty((Element) itemElements.item(0));
73                 }
74
75                 // Load <DefaultRelyingParty/> element, if specified
76                 itemElements = rawConfig.getElementsByTagNameNS(IdPConfig.configNameSpace, "DefaultRelyingParty");
77                 if (itemElements.getLength() > 1) {
78                         log.error("Found multiple <DefaultRelyingParty/> elements.  Ignoring all but the first...");
79                 }
80                 if (itemElements.getLength() < 1) {
81                         log.error("No <DefaultRelyingParty/> elements found.  Disabling support for responding "
82                                         + "to anonymous relying parties.");
83                 } else {
84                         addDefaultRelyingParty((Element) itemElements.item(0));
85                 }
86         }
87
88         public boolean anonymousSuported() {
89
90                 return (anonymousRelyingParty != null);
91         }
92
93         public RelyingParty getAnonymousRelyingParty() {
94
95                 return anonymousRelyingParty;
96
97         }
98
99         public void setMetadata(MetadataProvider metadata) {
100
101                 this.metaData = metadata;
102         }
103
104         private NamedRelyingParty findRelyingPartyByGroup(String providerIdFromSP) {
105
106                 if (metaData == null) { return null; }
107
108                 // Attempt to lookup the entity in the metdata
109                 EntityDescriptor provider = null;
110                 try {
111                         provider = metaData.getEntityDescriptor(providerIdFromSP);
112                 } catch (MetadataProviderException e) {
113                         log.error("Problem encountered during metadata lookup of entity (" + providerIdFromSP + "): " + e);
114                 }
115
116                 // OK, if we found it travel recurse down the tree of parent entities
117                 if (provider != null) {
118                         EntitiesDescriptor parent = getParentEntitiesDescriptor(provider);
119
120                         while (parent != null) {
121                                 if (parent.getName() != null) {
122                                         if (relyingParties.containsKey(parent.getName())) {
123                                                 log.info("Found matching Relying Party for group (" + parent.getName() + ").");
124                                                 return (NamedRelyingParty) relyingParties.get(parent.getName());
125                                         } else {
126                                                 log.debug("Provider is a member of group (" + parent.getName()
127                                                                 + "), but no matching Relying Party was found.");
128                                         }
129                                 }
130                                 parent = getParentEntitiesDescriptor(parent);
131                         }
132                 }
133                 return null;
134         }
135
136         /**
137          * Returns the appropriate relying party for the supplied service provider id.
138          */
139         public RelyingParty getRelyingParty(String providerIdFromSP) {
140
141                 if (providerIdFromSP == null || providerIdFromSP.equals("")) { throw new IllegalArgumentException(
142                                 "Incorrect use of ServiceProviderMapper.  Cannot lookup relying party without a provider ID."); }
143
144                 // Look for a configuration for the specific relying party
145                 if (relyingParties.containsKey(providerIdFromSP)) {
146                         log.info("Found Relying Party for (" + providerIdFromSP + ").");
147                         return (RelyingParty) relyingParties.get(providerIdFromSP);
148                 }
149
150                 // Lookup by group
151                 // Next, check to see if the relying party is in any groups
152                 NamedRelyingParty groupParty = findRelyingPartyByGroup(providerIdFromSP);
153                 if (groupParty != null) {
154                         log.info("Provider is a member of Relying Party (" + groupParty.getName() + ").");
155                         return groupParty;
156                 }
157
158                 // Use default if we have one
159                 if (defaultRelyingParty != null) {
160                         log.debug("No matching relying party found.  Using default relying party.");
161                         return defaultRelyingParty;
162                 }
163
164                 // Alright, there's nothing available to us
165                 return null;
166
167         }
168
169         private void addRelyingParty(Element e) throws RelyingPartyMapperException {
170
171                 log.debug("Found a Relying Party configuration element.");
172                 try {
173                         if (e.getLocalName().equals("RelyingParty")) {
174                                 NamedRelyingParty party = new NamedRelyingParty(e, credentials);
175                                 log.debug("Relying Party (" + party.getName() + ") loaded.");
176                                 relyingParties.put(party.getName(), party);
177                         }
178                 } catch (RelyingPartyMapperException exc) {
179                         log.error("Encountered an error while attempting to load Relying Party configuration.  Skipping...");
180                 }
181         }
182
183         private void addAnonymousRelyingParty(Element e) throws RelyingPartyMapperException {
184
185                 log.debug("Found an Anonymous Relying Party configuration element.");
186                 try {
187                         if (e.getLocalName().equals("AnonymousRelyingParty")) {
188                                 RelyingParty party = new RelyingPartyImpl(e, credentials);
189                                 log.debug("Anonymous Relying Party loaded.");
190                                 anonymousRelyingParty = party;
191                         }
192                 } catch (RelyingPartyMapperException exc) {
193                         log.error("Encountered an error while attempting to load Anonymous Relying"
194                                         + " Party configuration.  Skipping...");
195                 }
196         }
197
198         private void addDefaultRelyingParty(Element e) throws RelyingPartyMapperException {
199
200                 log.debug("Found a Default Relying Party configuration element.");
201                 try {
202                         if (e.getLocalName().equals("DefaultRelyingParty")) {
203                                 RelyingParty party = new RelyingPartyImpl(e, credentials);
204                                 log.debug("Default Relying Party loaded.");
205                                 defaultRelyingParty = party;
206                         }
207                 } catch (RelyingPartyMapperException exc) {
208                         log.error("Encountered an error while attempting to load Default "
209                                         + "Relying Party configuration.  Skipping...");
210                 }
211         }
212
213         private EntitiesDescriptor getParentEntitiesDescriptor(XMLObject entity) {
214
215                 Object parent = entity.getParent();
216
217                 if (parent instanceof EntitiesDescriptor) { return (EntitiesDescriptor) parent; }
218
219                 return null;
220         }
221
222         /**
223          * Base relying party implementation.
224          * 
225          * @author Walter Hoehn
226          */
227         protected class RelyingPartyImpl implements RelyingParty {
228
229                 private RelyingPartyIdentityProvider identityProvider;
230                 private String providerId;
231                 private boolean passThruErrors = false;
232                 private boolean forceAttributePush = false;
233                 private boolean forceAttributeNoPush = false;
234                 private boolean singleAssertion = false;
235                 private boolean defaultToPOST = true;
236                 private boolean wantsAssertionsSigned = false;
237                 private int preferredArtifactType = 1;
238                 private String defaultTarget;
239
240                 public RelyingPartyImpl(Element partyConfig, Credentials credentials) throws RelyingPartyMapperException {
241
242                         // Process overrides for global configuration data
243                         String attribute = ((Element) partyConfig).getAttribute("providerId");
244                         if (attribute == null || attribute.equals("")) {
245                                 log.error("Relying Party providerId not set.  Add a (providerId) " + "attribute to <RelyingParty>.");
246                                 throw new RelyingPartyMapperException("Required configuration not specified.");
247                         }
248                         providerId = attribute;
249                         log.debug("Setting providerId for Relying Party to (" + attribute + ").");
250
251                         attribute = ((Element) partyConfig).getAttribute("passThruErrors");
252                         if (attribute != null && !attribute.equals("")) {
253                                 log.debug("Setting passThruErrors for Relying Pary with (" + attribute + ").");
254                                 passThruErrors = Boolean.valueOf(attribute).booleanValue();
255                         }
256
257                         // SSO profile defaulting
258                         attribute = ((Element) partyConfig).getAttribute("defaultToPOSTProfile");
259                         if (attribute != null && !attribute.equals("")) {
260                                 defaultToPOST = Boolean.valueOf(attribute).booleanValue();
261                         }
262                         if (defaultToPOST) {
263                                 log.debug("Relying party defaults to POST profile.");
264                         } else {
265                                 log.debug("Relying party defaults to Artifact profile.");
266                         }
267
268                         attribute = ((Element) partyConfig).getAttribute("singleAssertion");
269                         if (attribute != null && !attribute.equals("")) {
270                                 singleAssertion = Boolean.valueOf(attribute).booleanValue();
271                         }
272                         if (singleAssertion) {
273                                 log.debug("Relying party defaults to a single assertion when pushing attributes.");
274                         } else {
275                                 log.debug("Relying party defaults to multiple assertions when pushing attributes.");
276                         }
277
278                         // Relying Party wants assertions signed?
279                         attribute = ((Element) partyConfig).getAttribute("signAssertions");
280                         if (attribute != null && !attribute.equals("")) {
281                                 wantsAssertionsSigned = Boolean.valueOf(attribute).booleanValue();
282                         }
283                         if (wantsAssertionsSigned) {
284                                 log.debug("Relying party wants SAML Assertions to be signed.");
285                         } else {
286                                 log.debug("Relying party does not want SAML Assertions to be signed.");
287                         }
288
289                         // Set a default target for use in artifact redirects
290                         defaultTarget = ((Element) partyConfig).getAttribute("defaultTarget");
291
292                         // Determine whether or not we are forcing attribute push on or off
293                         String forcePush = ((Element) partyConfig).getAttribute("forceAttributePush");
294                         String forceNoPush = ((Element) partyConfig).getAttribute("forceAttributeNoPush");
295
296                         if (forcePush != null && Boolean.valueOf(forcePush).booleanValue() && forceNoPush != null
297                                         && Boolean.valueOf(forceNoPush).booleanValue()) {
298                                 log.error("Invalid configuration:  Attribute push is forced to ON and OFF for this relying "
299                                                 + "party.  Turning off forcing in favor of profile defaults.");
300                         } else {
301                                 forceAttributePush = Boolean.valueOf(forcePush).booleanValue();
302                                 forceAttributeNoPush = Boolean.valueOf(forceNoPush).booleanValue();
303                                 log.debug("Attribute push forcing is set to (" + forceAttributePush + ").");
304                                 log.debug("No attribute push forcing is set to (" + forceAttributeNoPush + ").");
305                         }
306
307                         attribute = ((Element) partyConfig).getAttribute("preferredArtifactType");
308                         if (attribute != null && !attribute.equals("")) {
309                                 log.debug("Overriding preferredArtifactType for Relying Pary with (" + attribute + ").");
310                                 try {
311                                         preferredArtifactType = Integer.parseInt(attribute);
312                                 } catch (NumberFormatException e) {
313                                         log.error("(preferredArtifactType) attribute to is not a valid integer.");
314                                         throw new RelyingPartyMapperException("Configuration is invalid.");
315                                 }
316                                 log.debug("Preferred artifact type: (" + preferredArtifactType + ").");
317                         }
318
319                         // Load the credential for signing
320                         String credentialName = ((Element) partyConfig).getAttribute("signingCredential");
321                         Credential signingCredential = credentials.getCredential(credentialName);
322                         if (signingCredential == null) {
323                                 if (credentialName == null || credentialName.equals("")) {
324                                         log.error("Relying Party credential not set.  Add a (signingCredential) "
325                                                         + "attribute to <RelyingParty>.");
326                                         throw new RelyingPartyMapperException("Required configuration not specified.");
327                                 } else {
328                                         log.error("Relying Party credential invalid.  Fix the (signingCredential) attribute "
329                                                         + "on <RelyingParty>.");
330                                         throw new RelyingPartyMapperException("Required configuration is invalid.");
331                                 }
332
333                         }
334
335                         // Initialize and Identity Provider object for this use by this relying party
336                         identityProvider = new RelyingPartyIdentityProvider(providerId, signingCredential);
337
338                 }
339
340                 public IdentityProvider getIdentityProvider() {
341
342                         return identityProvider;
343                 }
344
345                 public boolean passThruErrors() {
346
347                         return passThruErrors;
348                 }
349
350                 public boolean forceAttributePush() {
351
352                         return forceAttributePush;
353                 }
354
355                 public boolean forceAttributeNoPush() {
356
357                         return forceAttributeNoPush;
358                 }
359
360                 public boolean singleAssertion() {
361
362                         return singleAssertion;
363                 }
364
365                 public boolean defaultToPOSTProfile() {
366
367                         return defaultToPOST;
368                 }
369
370                 public boolean wantsAssertionsSigned() {
371
372                         return wantsAssertionsSigned;
373                 }
374
375                 public int getPreferredArtifactType() {
376
377                         return preferredArtifactType;
378                 }
379
380                 public String getDefaultTarget() {
381
382                         return defaultTarget;
383                 }
384
385                 /**
386                  * Default identity provider implementation.
387                  * 
388                  * @author Walter Hoehn
389                  */
390                 protected class RelyingPartyIdentityProvider implements IdentityProvider {
391
392                         private String providerId;
393                         private Credential credential;
394
395                         public RelyingPartyIdentityProvider(String providerId, Credential credential) {
396
397                                 this.providerId = providerId;
398                                 this.credential = credential;
399                         }
400
401                         /*
402                          * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getProviderId()
403                          */
404                         public String getProviderId() {
405
406                                 return providerId;
407                         }
408
409                         /*
410                          * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getSigningCredential()
411                          */
412                         public Credential getSigningCredential() {
413
414                                 return credential;
415                         }
416                 }
417         }
418
419         class NamedRelyingParty extends RelyingPartyImpl {
420
421                 private String name;
422
423                 public NamedRelyingParty(Element partyConfig, Credentials credentials) throws RelyingPartyMapperException {
424
425                         super(partyConfig, credentials);
426                         // Get party name
427                         name = ((Element) partyConfig).getAttribute("name");
428                         if (name == null || name.equals("")) {
429                                 log.error("Relying Party name not set.  Add a (name) attribute to <RelyingParty>.");
430                                 throw new RelyingPartyMapperException("Required configuration not specified.");
431                         }
432                         log.debug("Loading Relying Party: (" + name + ").");
433                 }
434
435                 public String getName() {
436
437                         return name;
438                 }
439         }
440 }