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