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