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