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