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