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