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