bc1fd28f9507ff10d6a3d03d552f9584f58c603b
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / IdPResponder.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.idp;
27
28 import java.io.IOException;
29 import java.net.URI;
30 import java.net.URISyntaxException;
31 import java.net.URLEncoder;
32 import java.security.Principal;
33 import java.security.cert.CertificateParsingException;
34 import java.security.cert.X509Certificate;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.Collection;
38 import java.util.Collections;
39 import java.util.Date;
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.Random;
43
44 import javax.security.auth.x500.X500Principal;
45 import javax.servlet.RequestDispatcher;
46 import javax.servlet.ServletException;
47 import javax.servlet.UnavailableException;
48 import javax.servlet.http.HttpServletRequest;
49 import javax.servlet.http.HttpServletResponse;
50
51 import org.apache.log4j.Logger;
52 import org.apache.log4j.MDC;
53 import org.apache.xml.security.exceptions.XMLSecurityException;
54 import org.apache.xml.security.keys.KeyInfo;
55 import org.apache.xml.security.signature.XMLSignature;
56 import org.opensaml.InvalidCryptoException;
57 import org.opensaml.SAMLAssertion;
58 import org.opensaml.SAMLAttribute;
59 import org.opensaml.SAMLAttributeDesignator;
60 import org.opensaml.SAMLAttributeQuery;
61 import org.opensaml.SAMLAttributeStatement;
62 import org.opensaml.SAMLAudienceRestrictionCondition;
63 import org.opensaml.SAMLBinding;
64 import org.opensaml.SAMLBindingFactory;
65 import org.opensaml.SAMLCondition;
66 import org.opensaml.SAMLException;
67 import org.opensaml.SAMLNameIdentifier;
68 import org.opensaml.SAMLRequest;
69 import org.opensaml.SAMLResponse;
70 import org.opensaml.SAMLStatement;
71 import org.opensaml.SAMLSubject;
72 import org.w3c.dom.Document;
73 import org.w3c.dom.Element;
74 import org.w3c.dom.NodeList;
75
76 import sun.misc.BASE64Decoder;
77 import edu.internet2.middleware.shibboleth.aa.AAException;
78 import edu.internet2.middleware.shibboleth.aa.AAResponder;
79 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
80 import edu.internet2.middleware.shibboleth.aa.arp.ArpException;
81 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
82 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolverException;
83 import edu.internet2.middleware.shibboleth.artifact.ArtifactMapper;
84 import edu.internet2.middleware.shibboleth.artifact.ArtifactMapping;
85 import edu.internet2.middleware.shibboleth.artifact.provider.MemoryArtifactMapper;
86 import edu.internet2.middleware.shibboleth.common.AuthNPrincipal;
87 import edu.internet2.middleware.shibboleth.common.Credential;
88 import edu.internet2.middleware.shibboleth.common.Credentials;
89 import edu.internet2.middleware.shibboleth.common.InvalidNameIdentifierException;
90 import edu.internet2.middleware.shibboleth.common.NameIdentifierMapping;
91 import edu.internet2.middleware.shibboleth.common.NameIdentifierMappingException;
92 import edu.internet2.middleware.shibboleth.common.NameMapper;
93 import edu.internet2.middleware.shibboleth.common.OriginConfig;
94 import edu.internet2.middleware.shibboleth.common.RelyingParty;
95 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
96 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapperException;
97 import edu.internet2.middleware.shibboleth.common.ShibBrowserProfile;
98 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
99 import edu.internet2.middleware.shibboleth.common.TargetFederationComponent;
100 import edu.internet2.middleware.shibboleth.metadata.AttributeConsumerRole;
101 import edu.internet2.middleware.shibboleth.metadata.Endpoint;
102 import edu.internet2.middleware.shibboleth.metadata.KeyDescriptor;
103 import edu.internet2.middleware.shibboleth.metadata.Provider;
104 import edu.internet2.middleware.shibboleth.metadata.ProviderRole;
105 import edu.internet2.middleware.shibboleth.metadata.SPProviderRole;
106
107 /**
108  * Primary entry point for requests to the SAML IdP. Listens on multiple endpoints, routes requests to the appropriate
109  * IdP processing components, and delivers proper protocol responses.
110  * 
111  * @author Walter Hoehn
112  */
113
114 public class IdPResponder extends TargetFederationComponent {
115
116         // TODO Maybe should rethink the inheritance here, since there is only one
117         // servlet
118         
119         //TODO idp config should accept new or old element <ShibbolethOriginConfiguration/> or <IdPConfig/>
120
121         private static Logger transactionLog = Logger.getLogger("Shibboleth-TRANSACTION");
122         private static Logger log = Logger.getLogger(IdPResponder.class.getName());
123         private static Random idgen = new Random();
124
125         private SAMLBinding binding;
126         private Semaphore throttle;
127         private ArtifactMapper artifactMapper;
128         private SSOProfileHandler[] profileHandlers;
129         private IdPConfig configuration;
130         private NameMapper nameMapper;
131         private ServiceProviderMapper spMapper;
132
133         // TODO Need to rename, rework, and init
134         private AAResponder responder;
135
136         public void init() throws ServletException {
137
138                 super.init();
139                 MDC.put("serviceId", "[IdP] Core");
140                 log.info("Initializing Identity Provider.");
141
142                 try {
143                         binding = SAMLBindingFactory.getInstance(SAMLBinding.SOAP);
144                         nameMapper = new NameMapper();
145                         // TODO this needs to be pluggable
146                         artifactMapper = new MemoryArtifactMapper();
147                         loadConfiguration();
148                         
149                         //TODO what should we do with the throttle now!!!  default???
150                         
151                         //Load a semaphore that throttles how many requests the IdP will handle at once
152                         throttle = new Semaphore(configuration.getMaxThreads());
153                         
154                         log.info("Identity Provider initialization complete.");
155
156                 } catch (ShibbolethConfigurationException ae) {
157                         log.fatal("The Identity Provider could not be initialized: " + ae);
158                         throw new UnavailableException("Identity Provider failed to initialize.");
159                 } catch (SAMLException se) {
160                         log.fatal("SAML SOAP binding could not be loaded: " + se);
161                         throw new UnavailableException("Identity Provider failed to initialize.");
162                 }
163         }
164
165         private void loadConfiguration() throws ShibbolethConfigurationException {
166
167                 Document originConfig = OriginConfig.getOriginConfig(this.getServletContext());
168
169                 // TODO I think some of the failure cases here are different than in the
170                 // HS, so when the loadConfiguration() is unified, that must be taken
171                 // into account
172
173                 // TODO do we need to check active endpoints to determine which
174                 // components to load, for instance artifact repository, arp engine,
175                 // attribute resolver
176
177                 // Load global configuration properties
178                 configuration = new IdPConfig(originConfig.getDocumentElement());
179
180                 // Load name mappings
181                 NodeList itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
182                                 NameIdentifierMapping.mappingNamespace, "NameMapping");
183
184                 for (int i = 0; i < itemElements.getLength(); i++) {
185                         try {
186                                 nameMapper.addNameMapping((Element) itemElements.item(i));
187                         } catch (NameIdentifierMappingException e) {
188                                 log.error("Name Identifier mapping could not be loaded: " + e);
189                         }
190                 }
191
192                 // Load signing credentials
193                 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(Credentials.credentialsNamespace,
194                                 "Credentials");
195                 if (itemElements.getLength() < 1) {
196                         log.error("No credentials specified.");
197                 }
198                 if (itemElements.getLength() > 1) {
199                         log.error("Multiple Credentials specifications found, using first.");
200                 }
201                 Credentials credentials = new Credentials((Element) itemElements.item(0));
202
203                 // Load metadata
204                 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.originConfigNamespace,
205                                 "FederationProvider");
206                 for (int i = 0; i < itemElements.getLength(); i++) {
207                         addFederationProvider((Element) itemElements.item(i));
208                 }
209                 if (providerCount() < 1) {
210                         log.error("No Federation Provider metadata loaded.");
211                         throw new ShibbolethConfigurationException("Could not load federation metadata.");
212                 }
213
214                 // Load relying party config
215                 try {
216                         spMapper = new ServiceProviderMapper(originConfig.getDocumentElement(), configuration, credentials,
217                                         nameMapper, this);
218                 } catch (ServiceProviderMapperException e) {
219                         log.error("Could not load Identity Provider configuration: " + e);
220                         throw new ShibbolethConfigurationException("Could not load Identity Provider configuration.");
221                 }
222
223                 try {
224                         // Startup Attribute Resolver
225                         AttributeResolver resolver = new AttributeResolver(configuration);
226
227                         // Startup ARP Engine
228                         ArpEngine arpEngine = null;
229                         itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.originConfigNamespace,
230                                         "ReleasePolicyEngine");
231
232                         if (itemElements.getLength() > 1) {
233                                 log.warn("Encountered multiple <ReleasePolicyEngine> configuration elements.  Using first...");
234                         }
235                         if (itemElements.getLength() < 1) {
236                                 arpEngine = new ArpEngine();
237                         } else {
238                                 arpEngine = new ArpEngine((Element) itemElements.item(0));
239                         }
240
241                         // Startup responder
242                         responder = new AAResponder(arpEngine, resolver);
243
244                 } catch (ArpException ae) {
245                         log.fatal("The Identity Provider could not be initialized "
246                                         + "due to a problem with the ARP Engine configuration: " + ae);
247                         throw new ShibbolethConfigurationException("Could not load ARP Engine.");
248                 } catch (AttributeResolverException ne) {
249                         log.fatal("The Identity Provider could not be initialized due "
250                                         + "to a problem with the Attribute Resolver configuration: " + ne);
251                         throw new ShibbolethConfigurationException("Could not load Attribute Resolver.");
252                 }
253
254         }
255
256         public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
257
258                 MDC.put("serviceId", "[IdP] " + idgen.nextInt());
259                 MDC.put("remoteAddr", request.getRemoteAddr());
260                 log.debug("Recieved a request via GET.");
261                 log.info("Handling authN request.");
262
263                 try {
264                         throttle.enter();
265
266                         // Ensure that we have the required data from the servlet container
267                         validateEngineData(request);
268
269                         // Determine which profile of SAML we are responding to (at this point, Shib vs. EAuth)
270                         SSOProfileHandler activeHandler = null;
271                         for (int i = 0; i < profileHandlers.length; i++) {
272                                 if (profileHandlers[i].validForRequest(request)) {
273                                         activeHandler = profileHandlers[i];
274                                         break;
275                                 }
276                         }
277                         if (activeHandler == null) { throw new InvalidClientDataException(
278                                         "The request did not contain sufficient parameter data to determine the protocol."); }
279
280                         // Run profile specific preprocessing
281                         if (activeHandler.preProcessHook(request, response)) { return; }
282
283                         // Get the authN info
284                         String username = configuration.getAuthHeaderName().equalsIgnoreCase("REMOTE_USER") ? request
285                                         .getRemoteUser() : request.getHeader(configuration.getAuthHeaderName());
286
287                         // Select the appropriate Relying Party configuration for the request
288                         RelyingParty relyingParty = null;
289                         String remoteProviderId = activeHandler.getRemoteProviderId(request);
290                         // If the target did not send a Provider Id, then assume it is a Shib
291                         // 1.1 or older target
292                         if (remoteProviderId == null) {
293                                 relyingParty = spMapper.getLegacyRelyingParty();
294                         } else if (remoteProviderId.equals("")) {
295                                 throw new InvalidClientDataException("Invalid service provider id.");
296                         } else {
297                                 log.debug("Remote provider has identified itself as: (" + remoteProviderId + ").");
298                                 relyingParty = spMapper.getRelyingParty(remoteProviderId);
299                         }
300
301                         // Grab the metadata for the provider
302                         Provider provider = lookup(relyingParty.getProviderId());
303
304                         // Use profile-specific method for determining the acceptance URL
305                         String acceptanceURL = activeHandler.getAcceptanceURL(request, relyingParty, provider);
306
307                         // Make sure that the selected relying party configuration is appropriate for this
308                         // acceptance URL
309                         if (!relyingParty.isLegacyProvider()) {
310
311                                 if (provider == null) {
312                                         log.info("No metadata found for provider: (" + relyingParty.getProviderId() + ").");
313                                         relyingParty = spMapper.getRelyingParty(null);
314
315                                 } else {
316
317                                         if (isValidAssertionConsumerURL(provider, acceptanceURL)) {
318                                                 log.info("Supplied consumer URL validated for this provider.");
319                                         } else {
320                                                 log.error("Assertion consumer service URL (" + acceptanceURL + ") is NOT valid for provider ("
321                                                                 + relyingParty.getProviderId() + ").");
322                                                 throw new InvalidClientDataException("Invalid assertion consumer service URL.");
323                                         }
324                                 }
325                         }
326
327                         // Create SAML Name Identifier
328                         SAMLNameIdentifier nameId = nameMapper.getNameIdentifierName(relyingParty.getHSNameFormatId(),
329                                         new AuthNPrincipal(username), relyingParty, relyingParty.getIdentityProvider());
330
331                         String authenticationMethod = request.getHeader("SAMLAuthenticationMethod");
332                         if (authenticationMethod == null || authenticationMethod.equals("")) {
333                                 authenticationMethod = relyingParty.getDefaultAuthMethod().toString();
334                                 log.debug("User was authenticated via the default method for this relying party ("
335                                                 + authenticationMethod + ").");
336                         } else {
337                                 log.debug("User was authenticated via the method (" + authenticationMethod + ").");
338                         }
339
340                         // We might someday want to provide a mechanism for the authenticator to specify the auth time
341                         SAMLAssertion[] assertions = activeHandler.processHook(request, relyingParty, provider, nameId,
342                                         authenticationMethod, new Date(System.currentTimeMillis()));
343
344                         // TODO do assertion signing here
345
346                         // SAML Artifact profile
347                         if (useArtifactProfile(provider, acceptanceURL)) {
348                                 log.debug("Responding with Artifact profile.");
349
350                                 // Create artifacts for each assertion
351                                 ArrayList artifacts = new ArrayList();
352                                 for (int i = 0; i < assertions.length; i++) {
353                                         artifacts.add(artifactMapper.generateArtifact(assertions[i], relyingParty));
354                                 }
355
356                                 // Assemble the query string
357                                 StringBuffer destination = new StringBuffer(acceptanceURL);
358                                 destination.append("?TARGET=");
359                                 destination.append(URLEncoder.encode(activeHandler.getSAMLTargetParameter(request, relyingParty,
360                                                 provider), "UTF-8"));
361                                 Iterator iterator = artifacts.iterator();
362                                 StringBuffer artifactBuffer = new StringBuffer(); // Buffer for the transaction log
363                                 while (iterator.hasNext()) {
364                                         destination.append("&SAMLart=");
365                                         String artifact = (String) iterator.next();
366                                         destination.append(URLEncoder.encode(artifact, "UTF-8"));
367                                         artifactBuffer.append("(" + artifact + ")");
368                                 }
369                                 log.debug("Redirecting to (" + destination.toString() + ").");
370                                 response.sendRedirect(destination.toString()); // Redirect to the artifact receiver
371
372                                 transactionLog.info("Assertion artifact(s) (" + artifactBuffer.toString() + ") issued to provider ("
373                                                 + relyingParty.getIdentityProvider().getProviderId() + ") on behalf of principal (" + username
374                                                 + "). Name Identifier: (" + nameId.getName() + "). Name Identifier Format: ("
375                                                 + nameId.getFormat() + ").");
376
377                                 // SAML POST profile
378                         } else {
379                                 log.debug("Responding with POST profile.");
380                                 request.setAttribute("acceptanceURL", acceptanceURL);
381                                 request.setAttribute("target", activeHandler.getSAMLTargetParameter(request, relyingParty, provider));
382
383                                 SAMLResponse samlResponse = new SAMLResponse(null, acceptanceURL, Arrays.asList(assertions), null);
384                                 addSignatures(samlResponse, relyingParty, provider, true);
385                                 createPOSTForm(request, response, samlResponse.toBase64());
386
387                                 // Make transaction log entry
388                                 if (relyingParty.isLegacyProvider()) {
389                                         transactionLog.info("Authentication assertion issued to legacy provider (SHIRE: "
390                                                         + request.getParameter("shire") + ") on behalf of principal (" + username
391                                                         + ") for resource (" + request.getParameter("target") + "). Name Identifier: ("
392                                                         + nameId.getName() + "). Name Identifier Format: (" + nameId.getFormat() + ").");
393                                 } else {
394                                         transactionLog.info("Authentication assertion issued to provider ("
395                                                         + relyingParty.getIdentityProvider().getProviderId() + ") on behalf of principal ("
396                                                         + username + "). Name Identifier: (" + nameId.getName() + "). Name Identifier Format: ("
397                                                         + nameId.getFormat() + ").");
398                                 }
399                         }
400
401                         // TODO profile specific error handling
402                 } catch (NameIdentifierMappingException ex) {
403                         log.error(ex);
404                         handleSSOError(request, response, ex);
405                         return;
406                 } catch (InvalidClientDataException ex) {
407                         log.error(ex);
408                         handleSSOError(request, response, ex);
409                         return;
410                 } catch (SAMLException ex) {
411                         log.error(ex);
412                         handleSSOError(request, response, ex);
413                         return;
414                 } catch (InterruptedException ex) {
415                         log.error(ex);
416                         handleSSOError(request, response, ex);
417                         return;
418                 } finally {
419                         throttle.exit();
420                 }
421
422         }
423
424         public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
425
426                 MDC.put("serviceId", "[IdP] " + idgen.nextInt());
427                 MDC.put("remoteAddr", request.getRemoteAddr());
428                 log.debug("Recieved a request via POST.");
429
430                 // Parse SOAP request and marshall SAML request object
431                 SAMLRequest samlRequest = null;
432                 try {
433                         try {
434                                 samlRequest = binding.receive(request);
435                         } catch (SAMLException e) {
436                                 log.fatal("Unable to parse request: " + e);
437                                 throw new SAMLException("Invalid request data.");
438                         }
439
440                         // If we have DEBUGing turned on, dump out the request to the log
441                         // This takes some processing, so only do it if we need to
442                         if (log.isDebugEnabled()) {
443                                 try {
444                                         log.debug("Dumping generated SAML Request:"
445                                                         + System.getProperty("line.separator")
446                                                         + new String(new BASE64Decoder().decodeBuffer(new String(samlRequest.toBase64(), "ASCII")),
447                                                                         "UTF8"));
448                                 } catch (SAMLException e) {
449                                         log.error("Encountered an error while decoding SAMLRequest for logging purposes.");
450                                 } catch (IOException e) {
451                                         log.error("Encountered an error while decoding SAMLRequest for logging purposes.");
452                                 }
453                         }
454
455                         // Determine the request type
456                         Iterator artifacts = samlRequest.getArtifacts();
457                         if (artifacts.hasNext()) {
458                                 artifacts = null; // get rid of the iterator
459                                 log.info("Recieved a request to dereference an assertion artifact.");
460                                 processArtifactDereference(samlRequest, request, response);
461                                 return;
462                         }
463
464                         if (samlRequest.getQuery() != null && (samlRequest.getQuery() instanceof SAMLAttributeQuery)) {
465                                 log.info("Recieved an attribute query.");
466                                 processAttributeQuery(samlRequest, request, response);
467                                 return;
468                         }
469
470                         throw new SAMLException(SAMLException.REQUESTER,
471                                         "Identity Provider unable to respond to this SAML Request type.");
472
473                 } catch (InvalidNameIdentifierException invalidNameE) {
474                         log.info("Could not associate the request subject with a principal: " + invalidNameE);
475                         try {
476                                 // TODO once again, ifgure out passThruErrors
477                                 if (false) {
478                                         // if (relyingParty.passThruErrors()) {
479                                         sendSAMLFailureResponse(response, samlRequest, new SAMLException(Arrays.asList(invalidNameE
480                                                         .getSAMLErrorCodes()), "The supplied Subject was unrecognized.", invalidNameE));
481
482                                 } else {
483                                         sendSAMLFailureResponse(response, samlRequest, new SAMLException(Arrays.asList(invalidNameE
484                                                         .getSAMLErrorCodes()), "The supplied Subject was unrecognized."));
485                                 }
486                                 return;
487                         } catch (Exception ee) {
488                                 log.fatal("Could not construct a SAML error response: " + ee);
489                                 throw new ServletException("Identity Provider response failure.");
490                         }
491                 } catch (Exception e) {
492                         log.error("Error while processing request: " + e);
493                         try {
494                                 // TODO figure out how to implement the passThru error handling
495                                 // below
496                                 // if (relyingParty != null && relyingParty.passThruErrors()) {
497                                 if (false) {
498                                         sendSAMLFailureResponse(response, samlRequest, new SAMLException(SAMLException.RESPONDER,
499                                                         "General error processing request.", e));
500                                 } else if (configuration.passThruErrors()) {
501                                         sendSAMLFailureResponse(response, samlRequest, new SAMLException(SAMLException.RESPONDER,
502                                                         "General error processing request.", e));
503                                 } else {
504                                         sendSAMLFailureResponse(response, samlRequest, new SAMLException(SAMLException.RESPONDER,
505                                                         "General error processing request."));
506                                 }
507                                 return;
508                         } catch (Exception ee) {
509                                 log.fatal("Could not construct a SAML error response: " + ee);
510                                 throw new ServletException("Identity Provider response failure.");
511                         }
512                 }
513         }
514
515         // TODO this should be renamed, since it is now only one type of response
516         // that we can send
517         public void sendSAMLResponse(HttpServletResponse resp, SAMLAttribute[] attrs, SAMLRequest samlRequest,
518                         RelyingParty relyingParty, SAMLException exception) throws IOException {
519
520                 SAMLException ourSE = null;
521                 SAMLResponse samlResponse = null;
522
523                 try {
524                         if (attrs == null || attrs.length == 0) {
525                                 // No attribute found
526                                 samlResponse = new SAMLResponse(samlRequest.getId(), null, null, exception);
527                         } else {
528
529                                 SAMLAttributeQuery attributeQuery = (SAMLAttributeQuery) samlRequest.getQuery();
530
531                                 // Reference requested subject
532                                 SAMLSubject rSubject = (SAMLSubject) attributeQuery.getSubject().clone();
533
534                                 // Set appropriate audience
535                                 ArrayList audiences = new ArrayList();
536                                 if (relyingParty.getProviderId() != null) {
537                                         audiences.add(relyingParty.getProviderId());
538                                 }
539                                 if (relyingParty.getName() != null && !relyingParty.getName().equals(relyingParty.getProviderId())) {
540                                         audiences.add(relyingParty.getName());
541                                 }
542                                 SAMLCondition condition = new SAMLAudienceRestrictionCondition(audiences);
543
544                                 // Put all attributes into an assertion
545                                 SAMLStatement statement = new SAMLAttributeStatement(rSubject, Arrays.asList(attrs));
546
547                                 // Set assertion expiration to longest attribute expiration
548                                 long max = 0;
549                                 for (int i = 0; i < attrs.length; i++) {
550                                         if (max < attrs[i].getLifetime()) {
551                                                 max = attrs[i].getLifetime();
552                                         }
553                                 }
554                                 Date now = new Date();
555                                 Date then = new Date(now.getTime() + (max * 1000)); // max is in
556                                 // seconds
557
558                                 SAMLAssertion sAssertion = new SAMLAssertion(relyingParty.getIdentityProvider().getProviderId(), now,
559                                                 then, Collections.singleton(condition), null, Collections.singleton(statement));
560
561                                 samlResponse = new SAMLResponse(samlRequest.getId(), null, Collections.singleton(sAssertion), exception);
562                                 addSignatures(samlResponse, relyingParty, lookup(relyingParty.getProviderId()), false);
563                         }
564                 } catch (SAMLException se) {
565                         ourSE = se;
566                 } catch (CloneNotSupportedException ex) {
567                         ourSE = new SAMLException(SAMLException.RESPONDER, ex);
568
569                 } finally {
570
571                         if (log.isDebugEnabled()) { // This takes some processing, so only do it if we need to
572                                 try {
573                                         log.debug("Dumping generated SAML Response:"
574                                                         + System.getProperty("line.separator")
575                                                         + new String(
576                                                                         new BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")),
577                                                                         "UTF8"));
578                                 } catch (SAMLException e) {
579                                         log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
580                                 } catch (IOException e) {
581                                         log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
582                                 }
583                         }
584
585                         try {
586                                 binding.respond(resp, samlResponse, ourSE);
587                         } catch (SAMLException e) {
588                                 log.error("Caught exception while responding to requester: " + e.getMessage());
589                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while responding.");
590                         }
591                 }
592         }
593
594         // TODO get rid of this AAException thing
595         private void processAttributeQuery(SAMLRequest samlRequest, HttpServletRequest request, HttpServletResponse response)
596                         throws SAMLException, IOException, ServletException, AAException, InvalidNameIdentifierException,
597                         NameIdentifierMappingException {
598
599                 // TODO validate that the endpoint is valid for the request type
600
601                 RelyingParty relyingParty = null;
602
603                 SAMLAttributeQuery attributeQuery = (SAMLAttributeQuery) samlRequest.getQuery();
604
605                 if (!fromLegacyProvider(request)) {
606                         log.info("Remote provider has identified itself as: (" + attributeQuery.getResource() + ").");
607                 }
608
609                 // This is the requester name that will be passed to subsystems
610                 String effectiveName = null;
611
612                 X509Certificate credential = getCredentialFromProvider(request);
613                 if (credential == null || credential.getSubjectX500Principal().getName(X500Principal.RFC2253).equals("")) {
614                         log.info("Request is from an unauthenticated service provider.");
615                 } else {
616
617                         // Identify a Relying Party
618                         relyingParty = spMapper.getRelyingParty(attributeQuery.getResource());
619
620                         try {
621                                 effectiveName = getEffectiveName(request, relyingParty);
622                         } catch (InvalidProviderCredentialException ipc) {
623                                 sendSAMLFailureResponse(response, samlRequest, new SAMLException(SAMLException.RESPONDER,
624                                                 "Invalid credentials for request."));
625                                 return;
626                         }
627                 }
628
629                 if (effectiveName == null) {
630                         log.debug("Using default Relying Party for unauthenticated provider.");
631                         relyingParty = spMapper.getRelyingParty(null);
632                 }
633
634                 // Fail if we can't honor SAML Subject Confirmation
635                 if (!fromLegacyProvider(request)) {
636                         Iterator iterator = attributeQuery.getSubject().getConfirmationMethods();
637                         boolean hasConfirmationMethod = false;
638                         while (iterator.hasNext()) {
639                                 log.info("Request contains SAML Subject Confirmation method: (" + (String) iterator.next() + ").");
640                         }
641                         if (hasConfirmationMethod) { throw new SAMLException(SAMLException.REQUESTER,
642                                         "This SAML authority cannot honor requests containing the supplied SAML Subject Confirmation Method."); }
643                 }
644
645                 // Map Subject to local principal
646                 Principal principal = nameMapper.getPrincipal(attributeQuery.getSubject().getName(), relyingParty, relyingParty
647                                 .getIdentityProvider());
648                 log.info("Request is for principal (" + principal.getName() + ").");
649
650                 SAMLAttribute[] attrs;
651                 Iterator requestedAttrsIterator = attributeQuery.getDesignators();
652                 if (requestedAttrsIterator.hasNext()) {
653                         log.info("Request designates specific attributes, resolving this set.");
654                         ArrayList requestedAttrs = new ArrayList();
655                         while (requestedAttrsIterator.hasNext()) {
656                                 SAMLAttributeDesignator attribute = (SAMLAttributeDesignator) requestedAttrsIterator.next();
657                                 try {
658                                         log.debug("Designated attribute: (" + attribute.getName() + ")");
659                                         requestedAttrs.add(new URI(attribute.getName()));
660                                 } catch (URISyntaxException use) {
661                                         log.error("Request designated an attribute name that does not conform to the required URI syntax ("
662                                                         + attribute.getName() + ").  Ignoring this attribute");
663                                 }
664                         }
665
666                         attrs = responder.getReleaseAttributes(principal, effectiveName, null, (URI[]) requestedAttrs
667                                         .toArray(new URI[0]));
668                 } else {
669                         log.info("Request does not designate specific attributes, resolving all available.");
670                         attrs = responder.getReleaseAttributes(principal, effectiveName, null);
671                 }
672
673                 log.info("Found " + attrs.length + " attribute(s) for " + principal.getName());
674                 sendSAMLResponse(response, attrs, samlRequest, relyingParty, null);
675                 log.info("Successfully responded about " + principal.getName());
676
677                 if (effectiveName == null) {
678                         if (fromLegacyProvider(request)) {
679                                 transactionLog.info("Attribute assertion issued to anonymous legacy provider at ("
680                                                 + request.getRemoteAddr() + ") on behalf of principal (" + principal.getName() + ").");
681                         } else {
682                                 transactionLog.info("Attribute assertion issued to anonymous provider at (" + request.getRemoteAddr()
683                                                 + ") on behalf of principal (" + principal.getName() + ").");
684                         }
685                 } else {
686                         if (fromLegacyProvider(request)) {
687                                 transactionLog.info("Attribute assertion issued to legacy provider (" + effectiveName
688                                                 + ") on behalf of principal (" + principal.getName() + ").");
689                         } else {
690                                 transactionLog.info("Attribute assertion issued to provider (" + effectiveName
691                                                 + ") on behalf of principal (" + principal.getName() + ").");
692                         }
693                 }
694
695         }
696
697         private void processArtifactDereference(SAMLRequest samlRequest, HttpServletRequest request,
698                         HttpServletResponse response) throws SAMLException, IOException {
699
700                 // TODO validate that the endpoint is valid for the request type
701                 // TODO how about signatures on artifact dereferencing
702
703                 // Pull credential from request
704                 X509Certificate credential = getCredentialFromProvider(request);
705                 if (credential == null || credential.getSubjectX500Principal().getName(X500Principal.RFC2253).equals("")) {
706                         // The spec says that mutual authentication is required for the
707                         // artifact profile
708                         log.info("Request is from an unauthenticated service provider.");
709                         throw new SAMLException(SAMLException.REQUESTER,
710                                         "SAML Artifacts cannot be dereferenced for unauthenticated requesters.");
711                 }
712
713                 log.info("Request contains credential: (" + credential.getSubjectX500Principal().getName(X500Principal.RFC2253)
714                                 + ").");
715
716                 ArrayList assertions = new ArrayList();
717                 Iterator artifacts = samlRequest.getArtifacts();
718
719                 int queriedArtifacts = 0;
720                 StringBuffer dereferencedArtifacts = new StringBuffer(); // for
721                 // transaction
722                 // log
723                 while (artifacts.hasNext()) {
724                         queriedArtifacts++;
725                         String artifact = (String) artifacts.next();
726                         log.debug("Attempting to dereference artifact: (" + artifact + ").");
727                         ArtifactMapping mapping = artifactMapper.recoverAssertion(artifact);
728                         if (mapping != null) {
729                                 SAMLAssertion assertion = mapping.getAssertion();
730
731                                 // See if we have metadata for this provider
732                                 Provider provider = lookup(mapping.getServiceProviderId());
733                                 if (provider == null) {
734                                         log.info("No metadata found for provider: (" + mapping.getServiceProviderId() + ").");
735                                         throw new SAMLException(SAMLException.REQUESTER, "Invalid service provider.");
736                                 }
737
738                                 // Make sure that the suppplied credential is valid for the
739                                 // provider to which the artifact was issued
740                                 if (!isValidCredential(provider, credential)) {
741                                         log.error("Supplied credential ("
742                                                         + credential.getSubjectX500Principal().getName(X500Principal.RFC2253)
743                                                         + ") is NOT valid for provider (" + mapping.getServiceProviderId()
744                                                         + "), to whom this artifact was issued.");
745                                         throw new SAMLException(SAMLException.REQUESTER, "Invalid credential.");
746                                 }
747
748                                 log.debug("Supplied credential validated for the provider to which this artifact was issued.");
749
750                                 assertions.add(assertion);
751                                 dereferencedArtifacts.append("(" + artifact + ")");
752                         }
753                 }
754
755                 // The spec requires that if any artifacts are dereferenced, they must
756                 // all be dereferenced
757                 if (assertions.size() > 0 && assertions.size() != queriedArtifacts) { throw new SAMLException(
758                                 SAMLException.REQUESTER, "Unable to successfully dereference all artifacts."); }
759
760                 // Create and send response
761                 // The spec says that we should send "success" in the case where no
762                 // artifacts match
763                 SAMLResponse samlResponse = new SAMLResponse(samlRequest.getId(), null, assertions, null);
764
765                 if (log.isDebugEnabled()) {
766                         try {
767                                 log.debug("Dumping generated SAML Response:"
768                                                 + System.getProperty("line.separator")
769                                                 + new String(new BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")),
770                                                                 "UTF8"));
771                         } catch (SAMLException e) {
772                                 log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
773                         } catch (IOException e) {
774                                 log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
775                         }
776                 }
777
778                 binding.respond(response, samlResponse, null);
779
780                 transactionLog.info("Succesfully dereferenced the following artifacts: " + dereferencedArtifacts.toString());
781         }
782
783         private void sendSAMLFailureResponse(HttpServletResponse httpResponse, SAMLRequest samlRequest,
784                         SAMLException exception) throws IOException {
785
786                 try {
787                         SAMLResponse samlResponse = new SAMLResponse((samlRequest != null) ? samlRequest.getId() : null, null,
788                                         null, exception);
789                         if (log.isDebugEnabled()) {
790                                 try {
791                                         log.debug("Dumping generated SAML Error Response:"
792                                                         + System.getProperty("line.separator")
793                                                         + new String(
794                                                                         new BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")),
795                                                                         "UTF8"));
796                                 } catch (IOException e) {
797                                         log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
798                                 }
799                         }
800                         binding.respond(httpResponse, samlResponse, null);
801                         log.debug("Returning SAML Error Response.");
802                 } catch (SAMLException se) {
803                         try {
804                                 binding.respond(httpResponse, null, exception);
805                         } catch (SAMLException e) {
806                                 log.error("Caught exception while responding to requester: " + e.getMessage());
807                                 httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while responding.");
808                         }
809                         log.error("Identity Provider failed to make an error message: " + se);
810                 }
811         }
812
813         private String getEffectiveName(HttpServletRequest req, RelyingParty relyingParty)
814                         throws InvalidProviderCredentialException {
815
816                 // X500Principal credentialName = getCredentialName(req);
817                 X509Certificate credential = getCredentialFromProvider(req);
818
819                 if (credential == null || credential.getSubjectX500Principal().getName(X500Principal.RFC2253).equals("")) {
820                         log.info("Request is from an unauthenticated service provider.");
821                         return null;
822
823                 } else {
824                         log.info("Request contains credential: ("
825                                         + credential.getSubjectX500Principal().getName(X500Principal.RFC2253) + ").");
826                         // Mockup old requester name for requests from < 1.2 targets
827                         if (fromLegacyProvider(req)) {
828                                 String legacyName = ShibBrowserProfile.getHostNameFromDN(credential.getSubjectX500Principal());
829                                 if (legacyName == null) {
830                                         log.error("Unable to extract legacy requester name from certificate subject.");
831                                 }
832
833                                 log.info("Request from legacy service provider: (" + legacyName + ").");
834                                 return legacyName;
835
836                         } else {
837
838                                 // See if we have metadata for this provider
839                                 Provider provider = lookup(relyingParty.getProviderId());
840                                 if (provider == null) {
841                                         log.info("No metadata found for provider: (" + relyingParty.getProviderId() + ").");
842                                         log.info("Treating remote provider as unauthenticated.");
843                                         return null;
844                                 }
845
846                                 // Make sure that the suppplied credential is valid for the
847                                 // selected relying party
848                                 if (isValidCredential(provider, credential)) {
849                                         log.info("Supplied credential validated for this provider.");
850                                         log.info("Request from service provider: (" + relyingParty.getProviderId() + ").");
851                                         return relyingParty.getProviderId();
852                                 } else {
853                                         log.error("Supplied credential ("
854                                                         + credential.getSubjectX500Principal().getName(X500Principal.RFC2253)
855                                                         + ") is NOT valid for provider (" + relyingParty.getProviderId() + ").");
856                                         throw new InvalidProviderCredentialException("Invalid credential.");
857                                 }
858                         }
859                 }
860         }
861
862         private static void addSignatures(SAMLResponse response, RelyingParty relyingParty, Provider provider,
863                         boolean signResponse) throws SAMLException {
864
865                 if (provider != null) {
866                         boolean signAssertions = false;
867
868                         ProviderRole[] roles = provider.getRoles();
869                         if (roles.length == 0) {
870                                 log.info("Inappropriate metadata for provider: " + provider.getId() + ".  Expected SPSSODescriptor.");
871                         }
872                         for (int i = 0; roles.length > i; i++) {
873                                 if (roles[i] instanceof SPProviderRole) {
874                                         if (((SPProviderRole) roles[i]).wantAssertionsSigned()) {
875                                                 signAssertions = true;
876                                         }
877                                 }
878                         }
879
880                         if (signAssertions && relyingParty.getIdentityProvider().getSigningCredential() != null
881                                         && relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() != null) {
882
883                                 Iterator assertions = response.getAssertions();
884
885                                 while (assertions.hasNext()) {
886                                         SAMLAssertion assertion = (SAMLAssertion) assertions.next();
887                                         String assertionAlgorithm;
888                                         if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
889                                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
890                                         } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
891                                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
892                                         } else {
893                                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
894                                                                 "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
895                                         }
896
897                                         assertion.sign(assertionAlgorithm, relyingParty.getIdentityProvider().getSigningCredential()
898                                                         .getPrivateKey(), Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential()
899                                                         .getX509CertificateChain()));
900                                 }
901                         }
902                 }
903
904                 // Sign the response, if appropriate
905                 if (signResponse && relyingParty.getIdentityProvider().getSigningCredential() != null
906                                 && relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() != null) {
907
908                         String responseAlgorithm;
909                         if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
910                                 responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
911                         } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
912                                 responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
913                         } else {
914                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
915                                                 "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
916                         }
917
918                         response.sign(responseAlgorithm, relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey(),
919                                         Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential().getX509CertificateChain()));
920                 }
921         }
922
923         private static boolean useArtifactProfile(Provider provider, String acceptanceURL) {
924
925                 // Default to POST if we have no metadata
926                 if (provider == null) { return false; }
927
928                 // Default to POST if we have incomplete metadata
929                 ProviderRole[] roles = provider.getRoles();
930                 if (roles.length == 0) { return false; }
931
932                 for (int i = 0; roles.length > i; i++) {
933                         if (roles[i] instanceof SPProviderRole) {
934                                 Endpoint[] endpoints = ((SPProviderRole) roles[i]).getAssertionConsumerServiceURLs();
935
936                                 for (int j = 0; endpoints.length > j; j++) {
937                                         if (acceptanceURL.equals(endpoints[j].getLocation())
938                                                         && "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01".equals(endpoints[j].getBinding())) { return true; }
939                                 }
940                         }
941                 }
942                 // Default to POST if we have incomplete metadata
943                 return false;
944         }
945
946         private static void validateEngineData(HttpServletRequest req) throws InvalidClientDataException {
947
948                 if ((req.getRemoteUser() == null) || (req.getRemoteUser().equals(""))) { throw new InvalidClientDataException(
949                                 "Unable to authenticate remote user"); }
950                 if ((req.getRemoteAddr() == null) || (req.getRemoteAddr().equals(""))) { throw new InvalidClientDataException(
951                                 "Unable to obtain client address."); }
952         }
953
954         private static boolean isValidAssertionConsumerURL(Provider provider, String shireURL)
955                         throws InvalidClientDataException {
956
957                 ProviderRole[] roles = provider.getRoles();
958                 if (roles.length == 0) {
959                         log.info("Inappropriate metadata for provider.");
960                         return false;
961                 }
962
963                 for (int i = 0; roles.length > i; i++) {
964                         if (roles[i] instanceof SPProviderRole) {
965                                 Endpoint[] endpoints = ((SPProviderRole) roles[i]).getAssertionConsumerServiceURLs();
966                                 for (int j = 0; endpoints.length > j; j++) {
967                                         if (shireURL.equals(endpoints[j].getLocation())) { return true; }
968                                 }
969                         }
970                 }
971                 log.info("Supplied consumer URL not found in metadata.");
972                 return false;
973         }
974
975         private static X509Certificate getCredentialFromProvider(HttpServletRequest req) {
976
977                 X509Certificate[] certArray = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
978                 if (certArray != null && certArray.length > 0) { return certArray[0]; }
979                 return null;
980         }
981
982         private static boolean isValidCredential(Provider provider, X509Certificate certificate) {
983
984                 ProviderRole[] roles = provider.getRoles();
985                 if (roles.length == 0) {
986                         log.info("Inappropriate metadata for provider.");
987                         return false;
988                 }
989                 // TODO figure out what to do about this role business here
990                 for (int i = 0; roles.length > i; i++) {
991                         if (roles[i] instanceof AttributeConsumerRole) {
992                                 KeyDescriptor[] descriptors = roles[i].getKeyDescriptors();
993                                 for (int j = 0; descriptors.length > j; j++) {
994                                         KeyInfo[] keyInfo = descriptors[j].getKeyInfo();
995                                         for (int k = 0; keyInfo.length > k; k++) {
996                                                 for (int l = 0; keyInfo[k].lengthKeyName() > l; l++) {
997                                                         try {
998
999                                                                 // First, try to match DN against metadata
1000                                                                 try {
1001                                                                         if (certificate.getSubjectX500Principal().getName(X500Principal.RFC2253).equals(
1002                                                                                         new X500Principal(keyInfo[k].itemKeyName(l).getKeyName())
1003                                                                                                         .getName(X500Principal.RFC2253))) {
1004                                                                                 log.debug("Matched against DN.");
1005                                                                                 return true;
1006                                                                         }
1007                                                                 } catch (IllegalArgumentException iae) {
1008                                                                         // squelch this runtime exception, since
1009                                                                         // this might be a valid case
1010                                                                 }
1011
1012                                                                 // If that doesn't work, we try matching against
1013                                                                 // some Subject Alt Names
1014                                                                 try {
1015                                                                         Collection altNames = certificate.getSubjectAlternativeNames();
1016                                                                         if (altNames != null) {
1017                                                                                 for (Iterator nameIterator = altNames.iterator(); nameIterator.hasNext();) {
1018                                                                                         List altName = (List) nameIterator.next();
1019                                                                                         if (altName.get(0).equals(new Integer(2))
1020                                                                                                         || altName.get(0).equals(new Integer(6))) { // 2 is
1021                                                                                                 // DNS,
1022                                                                                                 // 6 is
1023                                                                                                 // URI
1024                                                                                                 if (altName.get(1).equals(keyInfo[k].itemKeyName(l).getKeyName())) {
1025                                                                                                         log.debug("Matched against SubjectAltName.");
1026                                                                                                         return true;
1027                                                                                                 }
1028                                                                                         }
1029                                                                                 }
1030                                                                         }
1031                                                                 } catch (CertificateParsingException e1) {
1032                                                                         log
1033                                                                                         .error("Encountered an problem trying to extract Subject Alternate Name from supplied certificate: "
1034                                                                                                         + e1);
1035                                                                 }
1036
1037                                                                 // If that doesn't work, try to match using
1038                                                                 // SSL-style hostname matching
1039                                                                 if (ShibBrowserProfile.getHostNameFromDN(certificate.getSubjectX500Principal()).equals(
1040                                                                                 keyInfo[k].itemKeyName(l).getKeyName())) {
1041                                                                         log.debug("Matched against hostname.");
1042                                                                         return true;
1043                                                                 }
1044
1045                                                         } catch (XMLSecurityException e) {
1046                                                                 log.error("Encountered an error reading federation metadata: " + e);
1047                                                         }
1048                                                 }
1049                                         }
1050                                 }
1051                         }
1052                 }
1053                 log.info("Supplied credential not found in metadata.");
1054                 return false;
1055         }
1056
1057         private static boolean fromLegacyProvider(HttpServletRequest request) {
1058
1059                 String version = request.getHeader("Shibboleth");
1060                 if (version != null) {
1061                         log.debug("Request from Shibboleth version: " + version);
1062                         return false;
1063                 }
1064                 log.debug("No version header found.");
1065                 return true;
1066         }
1067
1068         private static void createPOSTForm(HttpServletRequest req, HttpServletResponse res, byte[] buf) throws IOException,
1069                         ServletException {
1070
1071                 // Hardcoded to ASCII to ensure Base64 encoding compatibility
1072                 req.setAttribute("assertion", new String(buf, "ASCII"));
1073
1074                 if (log.isDebugEnabled()) {
1075                         try {
1076                                 log.debug("Dumping generated SAML Response:" + System.getProperty("line.separator")
1077                                                 + new String(new BASE64Decoder().decodeBuffer(new String(buf, "ASCII")), "UTF8"));
1078                         } catch (IOException e) {
1079                                 log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
1080                         }
1081                 }
1082
1083                 // TODO rename from hs.jsp to more appropriate name
1084                 RequestDispatcher rd = req.getRequestDispatcher("/hs.jsp");
1085                 rd.forward(req, res);
1086         }
1087
1088         private static void handleSSOError(HttpServletRequest req, HttpServletResponse res, Exception e)
1089                         throws ServletException, IOException {
1090
1091                 req.setAttribute("errorText", e.toString());
1092                 req.setAttribute("requestURL", req.getRequestURI().toString());
1093                 RequestDispatcher rd = req.getRequestDispatcher("/hserror.jsp");
1094                 // TODO rename hserror.jsp to a more appropriate name
1095                 rd.forward(req, res);
1096         }
1097
1098         private class Semaphore {
1099
1100                 private int value;
1101
1102                 public Semaphore(int value) {
1103
1104                         this.value = value;
1105                 }
1106
1107                 public synchronized void enter() throws InterruptedException {
1108
1109                         --value;
1110                         if (value < 0) {
1111                                 wait();
1112                         }
1113                 }
1114
1115                 public synchronized void exit() {
1116
1117                         ++value;
1118                         notify();
1119                 }
1120         }
1121
1122         private class InvalidProviderCredentialException extends Exception {
1123
1124                 public InvalidProviderCredentialException(String message) {
1125
1126                         super(message);
1127                 }
1128         }
1129
1130         abstract class SSOProfileHandler {
1131
1132                 abstract String getHandlerName();
1133
1134                 abstract String getRemoteProviderId(HttpServletRequest req);
1135
1136                 abstract boolean validForRequest(HttpServletRequest request);
1137
1138                 abstract boolean preProcessHook(HttpServletRequest request, HttpServletResponse response) throws IOException;
1139
1140                 abstract SAMLAssertion[] processHook(HttpServletRequest request, RelyingParty relyingParty, Provider provider,
1141                                 SAMLNameIdentifier nameId, String authenticationMethod, Date authTime) throws SAMLException,
1142                                 IOException;
1143
1144                 abstract String getSAMLTargetParameter(HttpServletRequest request, RelyingParty relyingParty, Provider provider);
1145
1146                 abstract String getAcceptanceURL(HttpServletRequest request, RelyingParty relyingParty, Provider provider)
1147                                 throws InvalidClientDataException;
1148         }
1149
1150 }
1151
1152 class InvalidClientDataException extends Exception {
1153
1154         public InvalidClientDataException(String message) {
1155
1156                 super(message);
1157         }
1158
1159 }