More re-architecting of the IdP servlet.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / common / ServiceProviderMapper.java
1 /*
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.
24  */
25
26 package edu.internet2.middleware.shibboleth.common;
27
28 import java.net.MalformedURLException;
29 import java.net.URI;
30 import java.net.URISyntaxException;
31 import java.net.URL;
32 import java.util.HashMap;
33 import java.util.Map;
34
35 import org.apache.log4j.Logger;
36 import org.w3c.dom.Element;
37 import org.w3c.dom.NodeList;
38
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;
43
44 /**
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
47  * is found.
48  * 
49  * @author Walter Hoehn
50  */
51 public class ServiceProviderMapper {
52
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;
59
60         public ServiceProviderMapper(Element rawConfig, IdPConfig configuration, Credentials credentials,
61                         NameMapper nameMapper) throws ServiceProviderMapperException {
62
63                 this.configuration = configuration;
64                 this.credentials = credentials;
65                 this.nameMapper = nameMapper;
66
67                 NodeList itemElements = rawConfig.getElementsByTagNameNS(IdPConfig.originConfigNamespace, "RelyingParty");
68
69                 for (int i = 0; i < itemElements.getLength(); i++) {
70                         addRelyingParty((Element) itemElements.item(i));
71                 }
72
73                 verifyDefaultParty(configuration);
74
75         }
76         
77         public void setMetadata(Metadata metadata) {
78                 this.metaData = metadata;
79         }
80
81         private IdPConfig getOriginConfig() {
82
83                 return configuration;
84         }
85
86         protected void verifyDefaultParty(IdPConfig configuration) throws ServiceProviderMapperException {
87
88                 // Verify we have a proper default party
89                 String defaultParty = configuration.getDefaultRelyingPartyName();
90                 if (defaultParty == null || defaultParty.equals("")) {
91                         if (relyingParties.size() != 1) {
92                                 log
93                                                 .error("Default Relying Party not specified.  Add a (defaultRelyingParty) attribute to <IdPConfig>.");
94                                 throw new ServiceProviderMapperException("Required configuration not specified.");
95                         } else {
96                                 log.debug("Only one Relying Party loaded.  Using this as the default.");
97                         }
98                 }
99                 log.debug("Default Relying Party set to: (" + defaultParty + ").");
100                 if (!relyingParties.containsKey(defaultParty)) {
101                         log.error("Default Relying Party refers to a Relying Party that has not been loaded.");
102                         throw new ServiceProviderMapperException("Invalid configuration (Default Relying Party).");
103                 }
104         }
105
106         protected RelyingParty getRelyingPartyImpl(String providerIdFromTarget) {
107
108                 // Null request, send the default
109                 if (providerIdFromTarget == null) {
110                         RelyingParty relyingParty = getDefaultRelyingParty();
111                         log.info("Using default Relying Party: (" + relyingParty.getName() + ").");
112                         return new UnknownProviderWrapper(relyingParty, providerIdFromTarget);
113                 }
114
115                 // Look for a configuration for the specific relying party
116                 if (relyingParties.containsKey(providerIdFromTarget)) {
117                         log.info("Found Relying Party for (" + providerIdFromTarget + ").");
118                         return (RelyingParty) relyingParties.get(providerIdFromTarget);
119                 }
120
121                 // Next, check to see if the relying party is in any groups
122                 RelyingParty groupParty = findRelyingPartyByGroup(providerIdFromTarget);
123                 if (groupParty != null) {
124                         log.info("Provider is a member of Relying Party (" + groupParty.getName() + ").");
125                         return new RelyingPartyGroupWrapper(groupParty, providerIdFromTarget);
126                 }
127
128                 // OK, we can't find it... just send the default
129                 RelyingParty relyingParty = getDefaultRelyingParty();
130                 log.info("Could not locate Relying Party configuration for (" + providerIdFromTarget
131                                 + ").  Using default Relying Party: (" + relyingParty.getName() + ").");
132                 return new UnknownProviderWrapper(relyingParty, providerIdFromTarget);
133         }
134
135         private RelyingParty findRelyingPartyByGroup(String providerIdFromTarget) {
136
137                 if (metaData == null) { return null; }
138                 
139                 EntityDescriptor provider = metaData.lookup(providerIdFromTarget);
140                 if (provider != null) {
141                         EntitiesDescriptor parent = provider.getEntitiesDescriptor();
142                         while (parent != null) {
143                                 if (relyingParties.containsKey(parent.getName())) {
144                                         log.info("Found matching Relying Party for group (" + parent.getName() + ").");
145                                         return (RelyingParty) relyingParties.get(parent.getName());
146                                 } else {
147                                         log.debug("Provider is a member of group (" + parent.getName()
148                                                         + "), but no matching Relying Party was found.");
149                                 }
150                                 parent = parent.getEntitiesDescriptor();
151                         }
152                 }
153                 return null;
154         }
155
156         public RelyingParty getDefaultRelyingParty() {
157
158                 // If there is no explicit default, pick the single configured Relying
159                 // Party
160                 String defaultParty = getOriginConfig().getDefaultRelyingPartyName();
161                 if (defaultParty == null || defaultParty.equals("")) { return (RelyingParty) relyingParties.values().iterator()
162                                 .next(); }
163
164                 // If we do have a default specified, use it...
165                 return (RelyingParty) relyingParties.get(defaultParty);
166         }
167
168         /**
169          * Returns the relying party for a legacy provider(the default)
170          */
171         public RelyingParty getLegacyRelyingParty() {
172
173                 RelyingParty relyingParty = getDefaultRelyingParty();
174                 log.info("Request is from legacy shib target.  Selecting default Relying Party: (" + relyingParty.getName()
175                                 + ").");
176                 return new LegacyWrapper((RelyingParty) relyingParty);
177
178         }
179
180         /**
181          * Returns the appropriate relying party for the supplied service provider id.
182          */
183         public RelyingParty getRelyingParty(String providerIdFromTarget) {
184
185                 if (providerIdFromTarget == null || providerIdFromTarget.equals("")) {
186                         RelyingParty relyingParty = getDefaultRelyingParty();
187                         log.info("Selecting default Relying Party: (" + relyingParty.getName() + ").");
188                         return new NoMetadataWrapper((RelyingParty) relyingParty);
189                 }
190
191                 return (RelyingParty) getRelyingPartyImpl(providerIdFromTarget);
192         }
193
194         private void addRelyingParty(Element e) throws ServiceProviderMapperException {
195
196                 log.debug("Found a Relying Party.");
197                 try {
198                         if (e.getLocalName().equals("RelyingParty")) {
199                                 RelyingParty party = new RelyingPartyImpl(e, configuration, credentials, nameMapper);
200                                 log.debug("Relying Party (" + party.getName() + ") loaded.");
201                                 relyingParties.put(party.getName(), party);
202                         }
203                 } catch (ServiceProviderMapperException exc) {
204                         log.error("Encountered an error while attempting to load Relying Party configuration.  Skipping...");
205                 }
206
207         }
208
209         /**
210          * Base relying party implementation.
211          * 
212          * @author Walter Hoehn
213          */
214         protected class RelyingPartyImpl implements RelyingParty {
215
216                 private RelyingPartyIdentityProvider identityProvider;
217                 private String name;
218                 private String overridenOriginProviderId;
219                 private URL overridenAAUrl;
220                 private URI overridenDefaultAuthMethod;
221                 private String hsNameFormatId;
222                 private IdPConfig configuration;
223                 private boolean overridenPassThruErrors = false;
224                 private boolean passThruIsOverriden = false;
225
226                 public RelyingPartyImpl(Element partyConfig, IdPConfig globalConfig, Credentials credentials,
227                                 NameMapper nameMapper) throws ServiceProviderMapperException {
228
229                         configuration = globalConfig;
230
231                         // Get party name
232                         name = ((Element) partyConfig).getAttribute("name");
233                         if (name == null || name.equals("")) {
234                                 log.error("Relying Party name not set.  Add a (name) attribute to <RelyingParty>.");
235                                 throw new ServiceProviderMapperException("Required configuration not specified.");
236                         }
237                         log.debug("Loading Relying Party: (" + name + ").");
238
239                         // Process overrides for global configuration data
240                         String attribute = ((Element) partyConfig).getAttribute("providerId");
241                         if (attribute != null && !attribute.equals("")) {
242                                 log.debug("Overriding providerId for Relying Pary (" + name + ") with (" + attribute + ").");
243                                 overridenOriginProviderId = attribute;
244                         }
245
246                         attribute = ((Element) partyConfig).getAttribute("AAUrl");
247                         if (attribute != null && !attribute.equals("")) {
248                                 log.debug("Overriding AAUrl for Relying Pary (" + name + ") with (" + attribute + ").");
249                                 try {
250                                         overridenAAUrl = new URL(attribute);
251                                 } catch (MalformedURLException e) {
252                                         log.error("(AAUrl) attribute to is not a valid URL.");
253                                         throw new ServiceProviderMapperException("Configuration is invalid.");
254                                 }
255                         }
256
257                         attribute = ((Element) partyConfig).getAttribute("defaultAuthMethod");
258                         if (attribute != null && !attribute.equals("")) {
259                                 log.debug("Overriding defaultAuthMethod for Relying Pary (" + name + ") with (" + attribute + ").");
260                                 try {
261                                         overridenDefaultAuthMethod = new URI(attribute);
262                                 } catch (URISyntaxException e1) {
263                                         log.error("(defaultAuthMethod) attribute to is not a valid URI.");
264                                         throw new ServiceProviderMapperException("Configuration is invalid.");
265                                 }
266                         }
267
268                         attribute = ((Element) partyConfig).getAttribute("passThruErrors");
269                         if (attribute != null && !attribute.equals("")) {
270                                 log.debug("Overriding passThruErrors for Relying Pary (" + name + ") with (" + attribute + ").");
271                                 overridenPassThruErrors = Boolean.valueOf(attribute).booleanValue();
272                                 passThruIsOverriden = true;
273                         }
274
275                         // Load and verify the name format that the HS should use in
276                         // assertions for this RelyingParty
277                         NodeList hsNameFormats = ((Element) partyConfig).getElementsByTagNameNS(IdPConfig.originConfigNamespace,
278                                         "HSNameFormat");
279                         // If no specification. Make sure we have a default mapping
280                         if (hsNameFormats.getLength() < 1) {
281                                 if (nameMapper.getNameIdentifierMappingById(null) == null) {
282                                         log.error("Relying Party HS Name Format not set.  Add a <HSNameFormat> element to <RelyingParty>.");
283                                         throw new ServiceProviderMapperException("Required configuration not specified.");
284                                 }
285
286                         } else {
287                                 // We do have a specification, so make sure it points to a
288                                 // valid Name Mapping
289                                 if (hsNameFormats.getLength() > 1) {
290                                         log.warn("Found multiple HSNameFormat specifications for Relying Party (" + name
291                                                         + ").  Ignoring all but the first.");
292                                 }
293
294                                 hsNameFormatId = ((Element) hsNameFormats.item(0)).getAttribute("nameMapping");
295                                 if (hsNameFormatId == null || hsNameFormatId.equals("")) {
296                                         log.error("HS Name Format mapping not set.  Add a (nameMapping) attribute to <HSNameFormat>.");
297                                         throw new ServiceProviderMapperException("Required configuration not specified.");
298                                 }
299
300                                 if (nameMapper.getNameIdentifierMappingById(hsNameFormatId) == null) {
301                                         log.error("Relying Party HS Name Format refers to a name mapping that is not loaded.");
302                                         throw new ServiceProviderMapperException("Required configuration not specified.");
303                                 }
304                         }
305
306                         // Load the credential for signing
307                         String credentialName = ((Element) partyConfig).getAttribute("signingCredential");
308                         Credential signingCredential = credentials.getCredential(credentialName);
309                         if (signingCredential == null) {
310                                 if (credentialName == null || credentialName.equals("")) {
311                                         log.error("Relying Party credential not set.  Add a (signingCredential) "
312                                                         + "attribute to <RelyingParty>.");
313                                         throw new ServiceProviderMapperException("Required configuration not specified.");
314                                 } else {
315                                         log.error("Relying Party credential invalid.  Fix the (signingCredential) attribute "
316                                                         + "on <RelyingParty>.");
317                                         throw new ServiceProviderMapperException("Required configuration is invalid.");
318                                 }
319
320                         }
321
322                         // Initialize and Identity Provider object for this use by this relying party
323                         identityProvider = new RelyingPartyIdentityProvider(overridenOriginProviderId != null
324                                         ? overridenOriginProviderId
325                                         : configuration.getProviderId(), signingCredential);
326
327                 }
328
329                 public String getProviderId() {
330
331                         return name;
332                 }
333
334                 public String getName() {
335
336                         return name;
337                 }
338
339                 public IdentityProvider getIdentityProvider() {
340
341                         return identityProvider;
342                 }
343
344                 public boolean isLegacyProvider() {
345
346                         return false;
347                 }
348
349                 public String getHSNameFormatId() {
350
351                         return hsNameFormatId;
352                 }
353
354                 public URI getDefaultAuthMethod() {
355
356                         if (overridenDefaultAuthMethod != null) {
357                                 return overridenDefaultAuthMethod;
358                         } else {
359                                 return configuration.getDefaultAuthMethod();
360                         }
361                 }
362
363                 public URL getAAUrl() {
364
365                         if (overridenAAUrl != null) {
366                                 return overridenAAUrl;
367                         } else {
368                                 return configuration.getAAUrl();
369                         }
370                 }
371
372                 public boolean passThruErrors() {
373
374                         if (passThruIsOverriden) {
375                                 return overridenPassThruErrors;
376                         } else {
377                                 return configuration.passThruErrors();
378                         }
379                 }
380
381                 /**
382                  * Default identity provider implementation.
383                  * 
384                  * @author Walter Hoehn
385                  */
386                 protected class RelyingPartyIdentityProvider implements IdentityProvider {
387
388                         private String providerId;
389                         private Credential credential;
390
391                         public RelyingPartyIdentityProvider(String providerId, Credential credential) {
392
393                                 this.providerId = providerId;
394                                 this.credential = credential;
395                         }
396
397                         /*
398                          * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getProviderId()
399                          */
400                         public String getProviderId() {
401
402                                 return providerId;
403                         }
404
405                         /*
406                          * @see edu.internet2.middleware.shibboleth.common.IdentityProvider#getSigningCredential()
407                          */
408                         public Credential getSigningCredential() {
409
410                                 return credential;
411                         }
412                 }
413         }
414
415         /**
416          * Relying party implementation wrapper for relying parties that are federations.
417          * 
418          * @author Walter Hoehn
419          */
420         class RelyingPartyGroupWrapper implements RelyingParty {
421
422                 private RelyingParty wrapped;
423                 private String providerId;
424
425                 RelyingPartyGroupWrapper(RelyingParty wrapped, String providerId) {
426
427                         this.wrapped = wrapped;
428                         this.providerId = providerId;
429                 }
430
431                 public String getName() {
432
433                         return wrapped.getName();
434                 }
435
436                 public boolean isLegacyProvider() {
437
438                         return false;
439                 }
440
441                 public IdentityProvider getIdentityProvider() {
442
443                         return wrapped.getIdentityProvider();
444                 }
445
446                 public String getProviderId() {
447
448                         return providerId;
449                 }
450
451                 public String getHSNameFormatId() {
452
453                         return wrapped.getHSNameFormatId();
454                 }
455
456                 public URL getAAUrl() {
457
458                         return wrapped.getAAUrl();
459                 }
460
461                 public URI getDefaultAuthMethod() {
462
463                         return wrapped.getDefaultAuthMethod();
464                 }
465
466                 public boolean passThruErrors() {
467
468                         return wrapped.passThruErrors();
469                 }
470         }
471
472         /**
473          * Relying party implementation wrapper for anonymous service providers.
474          * 
475          * @author Walter Hoehn
476          */
477         protected class UnknownProviderWrapper implements RelyingParty {
478
479                 protected RelyingParty wrapped;
480                 protected String providerId;
481
482                 protected UnknownProviderWrapper(RelyingParty wrapped, String providerId) {
483
484                         this.wrapped = wrapped;
485                         this.providerId = providerId;
486                 }
487
488                 public String getName() {
489
490                         return wrapped.getName();
491                 }
492
493                 public IdentityProvider getIdentityProvider() {
494
495                         return wrapped.getIdentityProvider();
496                 }
497
498                 public String getProviderId() {
499
500                         return providerId;
501                 }
502
503                 public String getHSNameFormatId() {
504
505                         return wrapped.getHSNameFormatId();
506                 }
507
508                 public boolean isLegacyProvider() {
509
510                         return wrapped.isLegacyProvider();
511                 }
512
513                 public URL getAAUrl() {
514
515                         return wrapped.getAAUrl();
516                 }
517
518                 public URI getDefaultAuthMethod() {
519
520                         return wrapped.getDefaultAuthMethod();
521                 }
522
523                 public boolean passThruErrors() {
524
525                         return wrapped.passThruErrors();
526                 }
527         }
528
529         /**
530          * Relying party wrapper for Shibboleth &lt;=1.1 service providers.
531          * 
532          * @author Walter Hoehn
533          */
534         class LegacyWrapper extends UnknownProviderWrapper implements RelyingParty {
535
536                 LegacyWrapper(RelyingParty wrapped) {
537
538                         super(wrapped, null);
539                 }
540
541                 public boolean isLegacyProvider() {
542
543                         return true;
544                 }
545
546                 public String getHSNameFormatId() {
547
548                         return ((RelyingParty) wrapped).getHSNameFormatId();
549                 }
550
551                 public URL getAAUrl() {
552
553                         return ((RelyingParty) wrapped).getAAUrl();
554                 }
555
556                 public URI getDefaultAuthMethod() {
557
558                         return ((RelyingParty) wrapped).getDefaultAuthMethod();
559                 }
560         }
561
562         /**
563          * Relying party wrapper for providers for which we have no metadata
564          * 
565          * @author Walter Hoehn
566          */
567         class NoMetadataWrapper extends UnknownProviderWrapper implements RelyingParty {
568
569                 NoMetadataWrapper(RelyingParty wrapped) {
570
571                         super(wrapped, null);
572                 }
573
574                 public String getHSNameFormatId() {
575
576                         return ((RelyingParty) wrapped).getHSNameFormatId();
577                 }
578
579                 public URL getAAUrl() {
580
581                         return ((RelyingParty) wrapped).getAAUrl();
582                 }
583
584                 public URI getDefaultAuthMethod() {
585
586                         return ((RelyingParty) wrapped).getDefaultAuthMethod();
587                 }
588         }
589 }