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