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