Convert Attribute Resolver to new config.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / common / ServiceProviderMapper.java
1 /*
2  * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation
3  * for Advanced Internet Development, Inc. All rights reserved
4  * 
5  * 
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  * 
9  * Redistributions of source code must retain the above copyright notice, this
10  * list of conditions and the following disclaimer.
11  * 
12  * Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution, if any, must include
15  * the following acknowledgment: "This product includes software developed by
16  * the University Corporation for Advanced Internet Development
17  * <http://www.ucaid.edu> Internet2 Project. Alternately, this acknowledegement
18  * may appear in the software itself, if and wherever such third-party
19  * acknowledgments normally appear.
20  * 
21  * Neither the name of Shibboleth nor the names of its contributors, nor
22  * Internet2, nor the University Corporation for Advanced Internet Development,
23  * Inc., nor UCAID may be used to endorse or promote products derived from this
24  * software without specific prior written permission. For written permission,
25  * please contact shibboleth@shibboleth.org
26  * 
27  * Products derived from this software may not be called Shibboleth, Internet2,
28  * UCAID, or the University Corporation for Advanced Internet Development, nor
29  * may Shibboleth appear in their name, without prior written permission of the
30  * University Corporation for Advanced Internet Development.
31  * 
32  * 
33  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
34  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
35  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
36  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
37  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
38  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
39  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY
40  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
41  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
45  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46  */
47 package edu.internet2.middleware.shibboleth.common;
48
49 import java.util.HashMap;
50 import java.util.Map;
51 import java.util.Properties;
52
53 import org.apache.log4j.Logger;
54 import org.w3c.dom.Element;
55 import org.w3c.dom.NodeList;
56
57 import edu.internet2.middleware.shibboleth.hs.HSNameMapper;
58
59 /**
60  * @author Walter Hoehn
61  *  
62  */
63 public class ServiceProviderMapper {
64
65         private static Logger log = Logger.getLogger(ShibbolethOriginConfig.class.getName());
66         private ShibbolethOriginConfig configuration;
67         private Credentials credentials;
68         private HSNameMapper nameMapper;
69         private Map relyingParties = new HashMap();
70
71         public ServiceProviderMapper(
72                 Element rawConfig,
73                 ShibbolethOriginConfig configuration,
74                 Credentials credentials,
75                 HSNameMapper nameMapper)
76                 throws ServiceProviderMapperException {
77
78                 this.configuration = configuration;
79                 this.credentials = credentials;
80                 this.nameMapper = nameMapper;
81
82                 NodeList itemElements =
83                         rawConfig.getElementsByTagNameNS(ShibbolethOriginConfig.originConfigNamespace, "RelyingParty");
84
85                 for (int i = 0; i < itemElements.getLength(); i++) {
86                         addHSRelyingParty((Element) itemElements.item(i));
87                 }
88
89                 verifyDefaultParty(configuration);
90         }
91
92         public ServiceProviderMapper(Element rawConfig, ShibbolethOriginConfig configuration)
93                 throws ServiceProviderMapperException {
94
95                 this.configuration = configuration;
96
97                 NodeList itemElements =
98                         rawConfig.getElementsByTagNameNS(ShibbolethOriginConfig.originConfigNamespace, "RelyingParty");
99
100                 for (int i = 0; i < itemElements.getLength(); i++) {
101                         addAARelyingParty((Element) itemElements.item(i));
102                 }
103
104                 verifyDefaultParty(configuration);
105         }
106
107         private void verifyDefaultParty(ShibbolethOriginConfig configuration) throws ServiceProviderMapperException {
108                 //Verify we have a proper default party
109                 String defaultParty =
110                         configuration.getConfigProperty(
111                                 "edu.internet2.middleware.shibboleth.common.RelyingParty.defaultRelyingParty");
112                 if (defaultParty == null || defaultParty.equals("")) {
113                         if (relyingParties.size() != 1) {
114                                 log.error(
115                                         "Default Relying Party not specified.  Add a (defaultRelyingParty) attribute to <ShibbolethOriginConfig>.");
116                                 throw new ServiceProviderMapperException("Required configuration not specified.");
117                         } else {
118                                 log.debug("Only one Relying Party loaded.  Using this as the default.");
119                         }
120                 }
121                 log.debug("Default Relying Party set to: (" + defaultParty + ").");
122                 if (!relyingParties.containsKey(defaultParty)) {
123                         log.error("Default Relying Party refers to a Relying Party that has not been loaded.");
124                         throw new ServiceProviderMapperException("Invalid configuration (Default Relying Party).");
125                 }
126         }
127
128         private void addHSRelyingParty(Element e) throws ServiceProviderMapperException {
129
130                 log.debug("Found a Relying Party.");
131                 try {
132                         if (e.getLocalName().equals("RelyingParty")) {
133                                 RelyingParty party = new RelyingPartyImpl(e, configuration, credentials, nameMapper);
134                                 log.debug("Relying Party (" + party.getName() + ") loaded.");
135                                 relyingParties.put(party.getName(), party);
136                         }
137                 } catch (ServiceProviderMapperException exc) {
138                         log.error("Encountered an error while attempting to load Relying Party configuration.  Skipping...");
139                 }
140         }
141
142         private void addAARelyingParty(Element e) throws ServiceProviderMapperException {
143
144                 log.debug("Found a Relying Party.");
145                 try {
146                         if (e.getLocalName().equals("RelyingParty")) {
147                                 RelyingParty party = new RelyingPartyImpl(e, configuration);
148                                 log.debug("Relying Party (" + party.getName() + ") loaded.");
149                                 relyingParties.put(party.getName(), party);
150                         }
151                 } catch (ServiceProviderMapperException exc) {
152                         log.error("Encountered an error while attempting to load Relying Party configuration.  Skipping...");
153                 }
154         }
155         public RelyingParty getRelyingParty(String providerIdFromTarget) {
156
157                 //If the target did not send a Provider Id, then assume it is a Shib
158                 // 1.1 or older target
159                 if (providerIdFromTarget == null || providerIdFromTarget.equals("")) {
160                         log.info("Request is from legacy shib target.  Selecting default Relying Party.");
161                         return new LegacyWrapper(getDefaultRelyingPatry());
162                 }
163
164                 //Look for a configuration for the specific relying party
165                 if (relyingParties.containsKey(providerIdFromTarget)) {
166                         log.info("Found Relying Party for (" + providerIdFromTarget + ").");
167                         return (RelyingParty) relyingParties.get(providerIdFromTarget);
168                 }
169
170                 //Next, check to see if the relying party is in any groups
171                 RelyingParty groupParty = findRelyingPartyByGroup(providerIdFromTarget);
172                 if (groupParty != null) {
173                         log.info("Provider is a member of Relying Party (" + groupParty.getName() + ").");
174                         return new RelyingPartyGroupWrapper(groupParty, providerIdFromTarget);
175                 }
176
177                 //OK, just send the default
178                 log.info(
179                         "Could not locate Relying Party configuration for ("
180                                 + providerIdFromTarget
181                                 + ").  Using default Relying Party.");
182                 return new UnknownProviderWrapper(getDefaultRelyingPatry());
183         }
184
185         private RelyingParty findRelyingPartyByGroup(String providerIdFromTarget) {
186
187                 // TODO This is totally a stub and needs to be based on target metadata
188                 // lookup
189                 if (providerIdFromTarget.startsWith("urn:mace:inqueue:")) {
190                         if (relyingParties.containsKey("urn:mace:inqueue")) {
191                                 return (RelyingParty) relyingParties.get("urn:mace:inqueue");
192                         }
193                 }
194                 return null;
195         }
196
197         private RelyingParty getDefaultRelyingPatry() {
198
199                 //If there is no explicit default, pick the single configured Relying
200                 // Party
201                 String defaultParty =
202                         configuration.getConfigProperty(
203                                 "edu.internet2.middleware.shibboleth.common.RelyingParty.defaultRelyingParty");
204                 if (defaultParty == null || defaultParty.equals("")) {
205                         return (RelyingParty) relyingParties.values().iterator().next();
206                 }
207
208                 //If we do have a default specified, use it...
209                 return (RelyingParty) relyingParties.get(defaultParty);
210         }
211
212         class RelyingPartyImpl implements RelyingParty {
213
214                 protected ShibbolethOriginConfig originConfig;
215                 protected Properties partyOverrides = new Properties();
216                 protected RelyingPartyIdentityProvider identityProvider;
217                 protected String name;
218                 protected String hsNameFormatId;
219
220                 /**
221                 * Constructor for use by Handle Service
222                 */
223                 public RelyingPartyImpl(
224                         Element partyConfig,
225                         ShibbolethOriginConfig globalConfig,
226                         Credentials credentials,
227                         HSNameMapper nameMapper)
228                         throws ServiceProviderMapperException {
229
230                         this(partyConfig, globalConfig);
231
232                         //Load a credential for signing
233                         String credentialName = ((Element) partyConfig).getAttribute("signingCredential");
234                         Credential credential = credentials.getCredential(credentialName);
235
236                         if (credential == null) {
237                                 if (credentialName == null || credentialName.equals("")) {
238                                         log.error(
239                                                 "Relying Party credential not set.  Add a (signingCredential) attribute to <RelyingParty>.");
240                                         throw new ServiceProviderMapperException("Required configuration not specified.");
241                                 } else {
242                                         log.error(
243                                                 "Relying Party credential not set.  Add a (signingCredential) attribute to <RelyingParty>.");
244                                         throw new ServiceProviderMapperException("Required configuration not specified.");
245                                 }
246                         }
247
248                         //Load and verify the name format that the HS should use in
249                         //assertions for this RelyingParty
250                         NodeList hsNameFormats =
251                                 ((Element) partyConfig).getElementsByTagNameNS(
252                                         ShibbolethOriginConfig.originConfigNamespace,
253                                         "HSNameFormat");
254                         //If no specification. Make sure we have a default mapping
255                         if (hsNameFormats.getLength() < 1) {
256                                 if (nameMapper.getNameIdentifierMappingById(null) == null) {
257                                         log.error("Relying Party HS Name Format not set.  Add a <HSNameFormat> element to <RelyingParty>.");
258                                         throw new ServiceProviderMapperException("Required configuration not specified.");
259                                 }
260
261                         } else {
262                                 //We do have a specification, so make sure it points to a
263                                 // valid Name Mapping
264                                 if (hsNameFormats.getLength() > 1) {
265                                         log.warn(
266                                                 "Found multiple HSNameFormat specifications for Relying Party ("
267                                                         + name
268                                                         + ").  Ignoring all but the first.");
269                                 }
270
271                                 String hsNameFormatId = ((Element) hsNameFormats.item(0)).getAttribute("nameMapping");
272                                 if (hsNameFormatId == null || hsNameFormatId.equals("")) {
273                                         log.error("HS Name Format mapping not set.  Add a (nameMapping) attribute to <HSNameFormat>.");
274                                         throw new ServiceProviderMapperException("Required configuration not specified.");
275                                 }
276
277                                 if (nameMapper.getNameIdentifierMappingById(hsNameFormatId) == null) {
278                                         log.error("Relying Party HS Name Format refers to a name mapping that is not loaded.");
279                                         throw new ServiceProviderMapperException("Required configuration not specified.");
280                                 }
281                         }
282
283                         //Global overrides
284                         String attribute = ((Element) partyConfig).getAttribute("AAUrl");
285                         if (attribute != null && !attribute.equals("")) {
286                                 log.debug("Overriding AAUrl for Relying Pary (" + name + ") with (" + attribute + ").");
287                                 partyOverrides.setProperty("edu.internet2.middleware.shibboleth.hs.HandleServlet.AAUrl", attribute);
288                         }
289
290                         attribute = ((Element) partyConfig).getAttribute("defaultAuthMethod");
291                         if (attribute != null && !attribute.equals("")) {
292                                 log.debug("Overriding defaultAuthMethod for Relying Pary (" + name + ") with (" + attribute + ").");
293                                 partyOverrides.setProperty(
294                                         "edu.internet2.middleware.shibboleth.hs.HandleServlet.defaultAuthMethod",
295                                         attribute);
296                         }
297
298                         identityProvider =
299                                 new RelyingPartyIdentityProvider(
300                                         getConfigProperty("edu.internet2.middleware.shibboleth.hs.HandleServlet.providerId"),
301                                         credential);
302                 }
303
304                 /**
305                  * Shared constructor
306                  */
307                 public RelyingPartyImpl(Element partyConfig, ShibbolethOriginConfig globalConfig)
308                         throws ServiceProviderMapperException {
309
310                         //Use global config for defaults
311                         this.originConfig = globalConfig;
312
313                         //Get party name
314                         name = ((Element) partyConfig).getAttribute("name");
315                         if (name == null || name.equals("")) {
316                                 log.error("Relying Party name not set.  Add a (name) attribute to <RelyingParty>.");
317                                 throw new ServiceProviderMapperException("Required configuration not specified.");
318                         }
319                         log.debug("Loading Relying Party: (" + name + ").");
320
321                         //Process overrides for global data
322                         String attribute = ((Element) partyConfig).getAttribute("providerId");
323                         if (attribute != null && !attribute.equals("")) {
324                                 log.debug("Overriding providerId for Relying Pary (" + name + ") with (" + attribute + ").");
325                                 partyOverrides.setProperty(
326                                         "edu.internet2.middleware.shibboleth.hs.HandleServlet.providerId",
327                                         attribute);
328                         }
329
330                         attribute = ((Element) partyConfig).getAttribute("passThruErrors");
331                         if (attribute != null && !attribute.equals("")) {
332                                 if (!attribute.equalsIgnoreCase("TRUE") && !attribute.equalsIgnoreCase("FALSE")) {
333                                         log.error("passThrue errors is a boolean property.");
334                                 } else {
335                                         log.debug("Overriding passThruErrors for Relying Pary (" + name + ") with (" + attribute + ").");
336                                         partyOverrides.setProperty(
337                                                 "edu.internet2.middleware.shibboleth.aa.AAServlet.passThruErrors",
338                                                 attribute);
339                                 }
340                         }
341
342                         identityProvider =
343                                 new RelyingPartyIdentityProvider(
344                                         getConfigProperty("edu.internet2.middleware.shibboleth.hs.HandleServlet.providerId"),
345                                         null);
346
347                 }
348                 public String getProviderId() {
349                         return name;
350                 }
351
352                 public String getName() {
353                         return name;
354                 }
355
356                 public boolean isLegacyProvider() {
357                         return false;
358                 }
359
360                 public String getConfigProperty(String key) {
361                         if (partyOverrides.containsKey(key)) {
362                                 return partyOverrides.getProperty(key);
363                         }
364                         return originConfig.getConfigProperty(key);
365                 }
366
367                 public String getHSNameFormatId() {
368                         return null;
369                 }
370
371                 public IdentityProvider getIdentityProvider() {
372                         return identityProvider;
373                 }
374
375                 class RelyingPartyIdentityProvider implements IdentityProvider {
376
377                         private String providerId;
378                         private Credential responseSigningCredential;
379
380                         RelyingPartyIdentityProvider(String providerId, Credential responseSigningCred) {
381                                 this.providerId = providerId;
382                                 this.responseSigningCredential = responseSigningCred;
383                         }
384
385                         public String getProviderId() {
386                                 return providerId;
387                         }
388
389                         public Credential getResponseSigningCredential() {
390                                 return responseSigningCredential;
391                         }
392
393                         public Credential getAssertionSigningCredential() {
394                                 return null;
395                         }
396
397                 }
398         }
399
400         class RelyingPartyGroupWrapper implements RelyingParty {
401
402                 private RelyingParty wrapped;
403                 private String providerId;
404
405                 RelyingPartyGroupWrapper(RelyingParty wrapped, String providerId) {
406                         this.wrapped = wrapped;
407                         this.providerId = providerId;
408                 }
409
410                 public String getName() {
411                         return wrapped.getName();
412                 }
413
414                 public String getConfigProperty(String key) {
415                         return wrapped.getConfigProperty(key);
416                 }
417
418                 public boolean isLegacyProvider() {
419                         return false;
420                 }
421
422                 public String getHSNameFormatId() {
423                         return wrapped.getHSNameFormatId();
424                 }
425
426                 public IdentityProvider getIdentityProvider() {
427                         return wrapped.getIdentityProvider();
428                 }
429
430                 public String getProviderId() {
431                         return providerId;
432                 }
433         }
434
435         class UnknownProviderWrapper implements RelyingParty {
436                 private RelyingParty wrapped;
437
438                 UnknownProviderWrapper(RelyingParty wrapped) {
439                         this.wrapped = wrapped;
440                 }
441
442                 public String getName() {
443                         return wrapped.getName();
444                 }
445
446                 public String getConfigProperty(String key) {
447                         return wrapped.getConfigProperty(key);
448                 }
449
450                 public boolean isLegacyProvider() {
451                         return wrapped.isLegacyProvider();
452                 }
453
454                 public String getHSNameFormatId() {
455                         return wrapped.getHSNameFormatId();
456                 }
457
458                 public IdentityProvider getIdentityProvider() {
459                         return wrapped.getIdentityProvider();
460                 }
461
462                 public String getProviderId() {
463                         return null;
464                 }
465         }
466
467         class LegacyWrapper extends UnknownProviderWrapper {
468
469                 LegacyWrapper(RelyingParty wrapped) {
470                         super(wrapped);
471                 }
472                 public boolean isLegacyProvider() {
473                         return true;
474                 }
475         }
476 }