Wasn't properly encoding artifact in transaction log.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / provider / ShibbolethV1SSOHandler.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.provider;
27
28 import java.io.IOException;
29 import java.io.UnsupportedEncodingException;
30 import java.net.URLEncoder;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.Date;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Vector;
38
39 import javax.servlet.RequestDispatcher;
40 import javax.servlet.ServletException;
41 import javax.servlet.http.HttpServletRequest;
42 import javax.servlet.http.HttpServletResponse;
43 import javax.xml.namespace.QName;
44
45 import org.apache.log4j.Logger;
46 import org.opensaml.SAMLAssertion;
47 import org.opensaml.SAMLAttribute;
48 import org.opensaml.SAMLAttributeStatement;
49 import org.opensaml.SAMLAudienceRestrictionCondition;
50 import org.opensaml.SAMLAuthenticationStatement;
51 import org.opensaml.SAMLAuthorityBinding;
52 import org.opensaml.SAMLBinding;
53 import org.opensaml.SAMLBrowserProfile;
54 import org.opensaml.SAMLCondition;
55 import org.opensaml.SAMLException;
56 import org.opensaml.SAMLNameIdentifier;
57 import org.opensaml.SAMLRequest;
58 import org.opensaml.SAMLResponse;
59 import org.opensaml.SAMLStatement;
60 import org.opensaml.SAMLSubject;
61 import org.opensaml.artifact.Artifact;
62 import org.w3c.dom.Document;
63 import org.w3c.dom.Element;
64
65 import sun.misc.BASE64Decoder;
66 import edu.internet2.middleware.shibboleth.aa.AAException;
67 import edu.internet2.middleware.shibboleth.common.AuthNPrincipal;
68 import edu.internet2.middleware.shibboleth.common.NameIdentifierMappingException;
69 import edu.internet2.middleware.shibboleth.common.RelyingParty;
70 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
71 import edu.internet2.middleware.shibboleth.idp.IdPProtocolHandler;
72 import edu.internet2.middleware.shibboleth.idp.IdPProtocolSupport;
73 import edu.internet2.middleware.shibboleth.idp.InvalidClientDataException;
74 import edu.internet2.middleware.shibboleth.metadata.Endpoint;
75 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
76 import edu.internet2.middleware.shibboleth.metadata.SPSSODescriptor;
77
78 /**
79  * <code>ProtocolHandler</code> implementation that responds to SSO flows as specified in "Shibboleth Architecture:
80  * Protocols and Profiles". Includes a compatibility mode for dealing with Shibboleth v1.1 SPs.
81  * 
82  * @author Walter Hoehn
83  */
84 public class ShibbolethV1SSOHandler extends SSOHandler implements IdPProtocolHandler {
85
86         private static Logger log = Logger.getLogger(ShibbolethV1SSOHandler.class.getName());
87
88         /**
89          * Required DOM-based constructor.
90          */
91         public ShibbolethV1SSOHandler(Element config) throws ShibbolethConfigurationException {
92
93                 super(config);
94         }
95
96         /*
97          * @see edu.internet2.middleware.shibboleth.idp.IdPResponder.ProtocolHandler#processRequest(javax.servlet.http.HttpServletRequest,
98          *      javax.servlet.http.HttpServletResponse)
99          */
100         public SAMLResponse processRequest(HttpServletRequest request, HttpServletResponse response,
101                         SAMLRequest samlRequest, IdPProtocolSupport support) throws SAMLException, ServletException, IOException {
102
103                 if (request == null) {
104                         log.error("Protocol Handler received a SAML Request, but is unable to handle it.");
105                         throw new SAMLException(SAMLException.RESPONDER, "General error processing request.");
106                 }
107
108                 // Set attributes that are needed by the jsp
109                 request.setAttribute("shire", request.getParameter("shire"));
110                 request.setAttribute("target", request.getParameter("target"));
111
112                 try {
113                         // Ensure that we have the required data from the servlet container
114                         validateEngineData(request);
115                         validateShibSpecificData(request);
116
117                         // Get the authN info
118                         String username = support.getIdPConfig().getAuthHeaderName().equalsIgnoreCase("REMOTE_USER") ? request
119                                         .getRemoteUser() : request.getHeader(support.getIdPConfig().getAuthHeaderName());
120                         if ((username == null) || (username.equals(""))) { throw new InvalidClientDataException(
121                                         "Unable to authenticate remote user"); }
122                         AuthNPrincipal principal = new AuthNPrincipal(username);
123
124                         // Select the appropriate Relying Party configuration for the request
125                         RelyingParty relyingParty = null;
126                         String remoteProviderId = request.getParameter("providerId");
127                         // If the target did not send a Provider Id, then assume it is a Shib
128                         // 1.1 or older target
129                         if (remoteProviderId == null) {
130                                 relyingParty = support.getServiceProviderMapper().getLegacyRelyingParty();
131                         } else if (remoteProviderId.equals("")) {
132                                 throw new InvalidClientDataException("Invalid service provider id.");
133                         } else {
134                                 log.debug("Remote provider has identified itself as: (" + remoteProviderId + ").");
135                                 relyingParty = support.getServiceProviderMapper().getRelyingParty(remoteProviderId);
136                         }
137
138                         // Grab the metadata for the provider
139                         EntityDescriptor descriptor = support.lookup(relyingParty.getProviderId());
140
141                         // Make sure that the selected relying party configuration is appropriate for this
142                         // acceptance URL
143                         String acceptanceURL = request.getParameter("shire");
144                         if (!relyingParty.isLegacyProvider()) {
145
146                                 if (descriptor == null) {
147                                         log.info("No metadata found for provider: (" + relyingParty.getProviderId() + ").");
148                                         relyingParty = support.getServiceProviderMapper().getRelyingParty(null);
149
150                                 } else {
151                                         if (isValidAssertionConsumerURL(descriptor, acceptanceURL)) {
152                                                 log.info("Supplied consumer URL validated for this provider.");
153                                         } else {
154                                                 log.error("Assertion consumer service URL (" + acceptanceURL + ") is NOT valid for provider ("
155                                                                 + relyingParty.getProviderId() + ").");
156                                                 throw new InvalidClientDataException("Invalid assertion consumer service URL.");
157                                         }
158                                 }
159                         }
160
161                         // Create SAML Name Identifier & Subject
162                         SAMLNameIdentifier nameId;
163                         try {
164                                 nameId = support.getNameMapper().getNameIdentifierName(relyingParty.getHSNameFormatId(), principal,
165                                                 relyingParty, relyingParty.getIdentityProvider());
166                         } catch (NameIdentifierMappingException e) {
167                                 log.error("Error converting principal to SAML Name Identifier: " + e);
168                                 throw new SAMLException("Error converting principal to SAML Name Identifier.", e);
169                         }
170
171                         String authenticationMethod = request.getHeader("SAMLAuthenticationMethod");
172                         if (authenticationMethod == null || authenticationMethod.equals("")) {
173                                 authenticationMethod = relyingParty.getDefaultAuthMethod().toString();
174                                 log.debug("User was authenticated via the default method for this relying party ("
175                                                 + authenticationMethod + ").");
176                         } else {
177                                 log.debug("User was authenticated via the method (" + authenticationMethod + ").");
178                         }
179
180                         // TODO Provide a mechanism for the authenticator to specify the auth time
181
182                         SAMLSubject authNSubject = new SAMLSubject(nameId, null, null, null);
183
184                         ArrayList assertions = new ArrayList();
185
186                         // Is this artifact or POST?
187                         boolean artifactProfile = useArtifactProfile(descriptor, acceptanceURL, relyingParty);
188
189                         // Package attributes for push, if necessary - don't attempt this for legacy providers (they don't support
190                         // it)
191                         if (!relyingParty.isLegacyProvider() && pushAttributes(artifactProfile, relyingParty)) {
192                                 log.info("Resolving attributes for push.");
193                                 SAMLAssertion attrAssertion = generateAttributeAssertion(support, principal, relyingParty, authNSubject);
194                                 if (attrAssertion != null) {
195                                         assertions.add(attrAssertion);
196                                 } else {
197                                         log.info("No attributes resolved.");
198                                 }
199                         }
200
201                         // SAML Artifact profile - don't even attempt this for legacy providers (they don't support it)
202                         if (!relyingParty.isLegacyProvider() && artifactProfile) {
203                                 respondWithArtifact(request, response, support, principal, relyingParty, descriptor, acceptanceURL,
204                                                 nameId, authenticationMethod, authNSubject, assertions);
205
206                                 // SAML POST profile
207                         } else {
208                                 respondWithPOST(request, response, support, principal, relyingParty, descriptor, acceptanceURL, nameId,
209                                                 authenticationMethod, authNSubject, assertions);
210                         }
211                 } catch (InvalidClientDataException e) {
212                         throw new SAMLException(SAMLException.RESPONDER, e.getMessage());
213                 }
214                 return null;
215         }
216
217         private void respondWithArtifact(HttpServletRequest request, HttpServletResponse response,
218                         IdPProtocolSupport support, AuthNPrincipal principal, RelyingParty relyingParty,
219                         EntityDescriptor descriptor, String acceptanceURL, SAMLNameIdentifier nameId, String authenticationMethod,
220                         SAMLSubject authNSubject, List assertions) throws SAMLException, IOException, UnsupportedEncodingException {
221
222                 log.debug("Responding with Artifact profile.");
223
224                 authNSubject.addConfirmationMethod(SAMLSubject.CONF_ARTIFACT);
225                 assertions.add(generateAuthNAssertion(request, relyingParty, descriptor, nameId, authenticationMethod,
226                                 new Date(System.currentTimeMillis()), authNSubject));
227
228                 // Sign the assertions, if necessary
229                 boolean metaDataIndicatesSignAssertions = false;
230                 if (descriptor != null) {
231                         SPSSODescriptor sp = descriptor.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
232                         if (sp != null) {
233                                 if (sp.getWantAssertionsSigned()) {
234                                         metaDataIndicatesSignAssertions = true;
235                                 }
236                         }
237                 }
238                 if (relyingParty.wantsAssertionsSigned() || metaDataIndicatesSignAssertions) {
239                         support.signAssertions((SAMLAssertion[]) assertions.toArray(new SAMLAssertion[0]), relyingParty);
240                 }
241
242                 // Create artifacts for each assertion
243                 ArrayList artifacts = new ArrayList();
244                 for (int i = 0; i < assertions.size(); i++) {
245                         artifacts
246                                         .add(support.getArtifactMapper().generateArtifact((SAMLAssertion) assertions.get(i), relyingParty));
247                 }
248
249                 // Assemble the query string
250                 StringBuffer destination = new StringBuffer(acceptanceURL);
251                 destination.append("?TARGET=");
252                 destination.append(URLEncoder.encode(request.getParameter("target"), "UTF-8"));
253                 Iterator iterator = artifacts.iterator();
254                 StringBuffer artifactBuffer = new StringBuffer(); // Buffer for the transaction log
255
256                 // Construct the artifact query parameter
257                 while (iterator.hasNext()) {
258                         Artifact artifact = (Artifact) iterator.next();
259                         artifactBuffer.append("(" + artifact.encode() + ")");
260                         destination.append("&SAMLart=");
261                         destination.append(URLEncoder.encode(artifact.encode(), "UTF-8"));
262                 }
263
264                 log.debug("Redirecting to (" + destination.toString() + ").");
265                 response.sendRedirect(destination.toString()); // Redirect to the artifact receiver
266                 support.getTransactionLog().info(
267                                 "Assertion artifact(s) (" + artifactBuffer.toString() + ") issued to provider ("
268                                                 + relyingParty.getIdentityProvider().getProviderId() + ") on behalf of principal ("
269                                                 + principal.getName() + "). Name Identifier: (" + nameId.getName()
270                                                 + "). Name Identifier Format: (" + nameId.getFormat() + ").");
271         }
272
273         private void respondWithPOST(HttpServletRequest request, HttpServletResponse response, IdPProtocolSupport support,
274                         AuthNPrincipal principal, RelyingParty relyingParty, EntityDescriptor descriptor, String acceptanceURL,
275                         SAMLNameIdentifier nameId, String authenticationMethod, SAMLSubject authNSubject, List assertions)
276                         throws SAMLException, IOException, ServletException {
277
278                 log.debug("Responding with POST profile.");
279
280                 authNSubject.addConfirmationMethod(SAMLSubject.CONF_BEARER);
281                 assertions.add(generateAuthNAssertion(request, relyingParty, descriptor, nameId, authenticationMethod,
282                                 new Date(System.currentTimeMillis()), authNSubject));
283
284                 // Sign the assertions, if necessary
285                 boolean metaDataIndicatesSignAssertions = false;
286                 if (descriptor != null) {
287                         SPSSODescriptor sp = descriptor.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
288                         if (sp != null) {
289                                 if (sp.getWantAssertionsSigned()) {
290                                         metaDataIndicatesSignAssertions = true;
291                                 }
292                         }
293                 }
294                 if (relyingParty.wantsAssertionsSigned() || metaDataIndicatesSignAssertions) {
295                         support.signAssertions((SAMLAssertion[]) assertions.toArray(new SAMLAssertion[0]), relyingParty);
296                 }
297
298                 // Set attributes needed by form
299                 request.setAttribute("acceptanceURL", acceptanceURL);
300                 request.setAttribute("target", request.getParameter("target"));
301
302                 SAMLResponse samlResponse = new SAMLResponse(null, acceptanceURL, assertions, null);
303
304                 support.signResponse(samlResponse, relyingParty);
305
306                 createPOSTForm(request, response, samlResponse.toBase64());
307
308                 // Make transaction log entry
309                 if (relyingParty.isLegacyProvider()) {
310                         support.getTransactionLog().info(
311                                         "Authentication assertion issued to legacy provider (SHIRE: " + request.getParameter("shire")
312                                                         + ") on behalf of principal (" + principal.getName() + ") for resource ("
313                                                         + request.getParameter("target") + "). Name Identifier: (" + nameId.getName()
314                                                         + "). Name Identifier Format: (" + nameId.getFormat() + ").");
315
316                 } else {
317                         support.getTransactionLog().info(
318                                         "Authentication assertion issued to provider ("
319                                                         + relyingParty.getIdentityProvider().getProviderId() + ") on behalf of principal ("
320                                                         + principal.getName() + "). Name Identifier: (" + nameId.getName()
321                                                         + "). Name Identifier Format: (" + nameId.getFormat() + ").");
322                 }
323         }
324
325         private SAMLAssertion generateAttributeAssertion(IdPProtocolSupport support, AuthNPrincipal principal,
326                         RelyingParty relyingParty, SAMLSubject authNSubject) throws SAMLException {
327
328                 try {
329                         SAMLAttribute[] attributes = support.getReleaseAttributes(principal, relyingParty.getProviderId(), null);
330                         log.info("Found " + attributes.length + " attribute(s) for " + principal.getName());
331
332                         // Bail if we didn't get any attributes
333                         if (attributes == null || attributes.length < 1) {
334                                 return null;
335
336                         } else {
337                                 // Reference requested subject
338                                 SAMLSubject attrSubject = (SAMLSubject) authNSubject.clone();
339
340                                 ArrayList audiences = new ArrayList();
341                                 if (relyingParty.getProviderId() != null) {
342                                         audiences.add(relyingParty.getProviderId());
343                                 }
344                                 if (relyingParty.getName() != null && !relyingParty.getName().equals(relyingParty.getProviderId())) {
345                                         audiences.add(relyingParty.getName());
346                                 }
347                                 SAMLCondition condition = new SAMLAudienceRestrictionCondition(audiences);
348
349                                 // Put all attributes into an assertion
350                                 SAMLStatement statement = new SAMLAttributeStatement(attrSubject, Arrays.asList(attributes));
351
352                                 // Set assertion expiration to longest attribute expiration
353                                 long max = 0;
354                                 for (int i = 0; i < attributes.length; i++) {
355                                         if (max < attributes[i].getLifetime()) {
356                                                 max = attributes[i].getLifetime();
357                                         }
358                                 }
359                                 Date now = new Date();
360                                 Date then = new Date(now.getTime() + (max * 1000)); // max is in seconds
361
362                                 SAMLAssertion attrAssertion = new SAMLAssertion(relyingParty.getIdentityProvider().getProviderId(),
363                                                 now, then, Collections.singleton(condition), null, Collections.singleton(statement));
364
365                                 if (log.isDebugEnabled()) {
366                                         log.debug("Dumping generated Attribute Assertion:" + System.getProperty("line.separator")
367                                                         + attrAssertion.toString());
368                                 }
369                                 return attrAssertion;
370                         }
371                 } catch (AAException e) {
372                         log.error("An error was encountered while generating assertion for attribute push: " + e);
373                         throw new SAMLException(SAMLException.RESPONDER, "General error processing request.");
374                 } catch (CloneNotSupportedException e) {
375                         log.error("An error was encountered while generating assertion for attribute push: " + e);
376                         throw new SAMLException(SAMLException.RESPONDER, "General error processing request.");
377                 }
378         }
379
380         private SAMLAssertion generateAuthNAssertion(HttpServletRequest request, RelyingParty relyingParty,
381                         EntityDescriptor descriptor, SAMLNameIdentifier nameId, String authenticationMethod, Date authTime,
382                         SAMLSubject subject) throws SAMLException, IOException {
383
384                 Document doc = org.opensaml.XML.parserPool.newDocument();
385
386                 // Determine the correct audiences
387                 ArrayList audiences = new ArrayList();
388                 if (relyingParty.getProviderId() != null) {
389                         audiences.add(relyingParty.getProviderId());
390                 }
391                 if (relyingParty.getName() != null && !relyingParty.getName().equals(relyingParty.getProviderId())) {
392                         audiences.add(relyingParty.getName());
393                 }
394
395                 // Determine the correct issuer
396                 String issuer = null;
397                 if (relyingParty.isLegacyProvider()) {
398
399                         log.debug("Service Provider is running Shibboleth <= 1.1. Using old style issuer.");
400                         if (relyingParty.getIdentityProvider().getSigningCredential() == null
401                                         || relyingParty.getIdentityProvider().getSigningCredential().getX509Certificate() == null) { throw new SAMLException(
402                                         "Cannot serve legacy style assertions without an X509 certificate"); }
403                         issuer = getHostNameFromDN(relyingParty.getIdentityProvider().getSigningCredential().getX509Certificate()
404                                         .getSubjectX500Principal());
405                         if (issuer == null || issuer.equals("")) { throw new SAMLException(
406                                         "Error parsing certificate DN while determining legacy issuer name."); }
407
408                 } else {
409                         issuer = relyingParty.getIdentityProvider().getProviderId();
410                 }
411
412                 // For compatibility with pre-1.2 shibboleth targets, include a pointer to the AA
413                 ArrayList bindings = new ArrayList();
414                 if (relyingParty.isLegacyProvider()) {
415
416                         SAMLAuthorityBinding binding = new SAMLAuthorityBinding(SAMLBinding.SOAP, relyingParty.getAAUrl()
417                                         .toString(), new QName(org.opensaml.XML.SAMLP_NS, "AttributeQuery"));
418                         bindings.add(binding);
419                 }
420
421                 // Create the assertion
422                 Vector conditions = new Vector(1);
423                 if (audiences != null && audiences.size() > 0) conditions.add(new SAMLAudienceRestrictionCondition(audiences));
424
425                 SAMLStatement[] statements = {new SAMLAuthenticationStatement(subject, authenticationMethod, authTime, request
426                                 .getRemoteAddr(), null, bindings)};
427
428                 SAMLAssertion assertion = new SAMLAssertion(issuer, new Date(System.currentTimeMillis()), new Date(System
429                                 .currentTimeMillis() + 300000), conditions, null, Arrays.asList(statements));
430
431                 if (log.isDebugEnabled()) {
432                         log.debug("Dumping generated AuthN Assertion:" + System.getProperty("line.separator")
433                                         + assertion.toString());
434                 }
435
436                 return assertion;
437         }
438
439         /*
440          * @see edu.internet2.middleware.shibboleth.idp.IdPResponder.ProtocolHandler#getHandlerName()
441          */
442         public String getHandlerName() {
443
444                 return "Shibboleth v1.x SSO";
445         }
446
447         private void validateShibSpecificData(HttpServletRequest request) throws InvalidClientDataException {
448
449                 if (request.getParameter("target") == null || request.getParameter("target").equals("")) { throw new InvalidClientDataException(
450                                 "Invalid data from Service Provider: no target URL received."); }
451                 if ((request.getParameter("shire") == null) || (request.getParameter("shire").equals(""))) { throw new InvalidClientDataException(
452                                 "Invalid data from Service Provider: No acceptance URL received."); }
453         }
454
455         private static void createPOSTForm(HttpServletRequest req, HttpServletResponse res, byte[] buf) throws IOException,
456                         ServletException {
457
458                 // Hardcoded to ASCII to ensure Base64 encoding compatibility
459                 req.setAttribute("assertion", new String(buf, "ASCII"));
460
461                 if (log.isDebugEnabled()) {
462                         try {
463                                 log.debug("Dumping generated SAML Response:" + System.getProperty("line.separator")
464                                                 + new String(new BASE64Decoder().decodeBuffer(new String(buf, "ASCII")), "UTF8"));
465                         } catch (IOException e) {
466                                 log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
467                         }
468                 }
469
470                 RequestDispatcher rd = req.getRequestDispatcher("/IdP.jsp");
471                 rd.forward(req, res);
472         }
473
474         /**
475          * Boolean indication of which browser profile is in effect. "true" indicates Artifact and "false" indicates POST.
476          */
477         private static boolean useArtifactProfile(EntityDescriptor descriptor, String acceptanceURL,
478                         RelyingParty relyingParty) {
479
480                 boolean artifactMeta = false;
481                 boolean postMeta = false;
482
483                 // Look at the metadata bindings, if we can find them
484                 if (descriptor != null) {
485                         SPSSODescriptor sp = descriptor.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
486
487                         if (sp != null) {
488
489                                 Iterator endpoints = sp.getAssertionConsumerServiceManager().getEndpoints();
490                                 while (endpoints.hasNext()) {
491                                         Endpoint ep = (Endpoint) endpoints.next();
492                                         if (acceptanceURL.equals(ep.getLocation())
493                                                         && SAMLBrowserProfile.PROFILE_POST_URI.equals(ep.getBinding())) {
494                                                 log.debug("Metadata indicates support for POST profile.");
495                                                 postMeta = true;
496                                                 continue;
497                                         }
498                                 }
499                                 endpoints = sp.getAssertionConsumerServiceManager().getEndpoints();
500                                 while (endpoints.hasNext()) {
501                                         Endpoint ep = (Endpoint) endpoints.next();
502                                         if (acceptanceURL.equals(ep.getLocation())
503                                                         && SAMLBrowserProfile.PROFILE_ARTIFACT_URI.equals(ep.getBinding())) {
504                                                 log.debug("Metadata indicates support for Artifact profile.");
505                                                 artifactMeta = true;
506                                                 continue;
507                                         }
508                                 }
509                         }
510                 }
511
512                 // If we have metadata for both, use the relying party default
513                 if (!(artifactMeta && postMeta)) {
514
515                         // If we only have metadata for one, use it
516                         if (artifactMeta) { return true; }
517                         if (postMeta) { return false; }
518
519                 }
520
521                 // If we have missing or incomplete metadata, use relying party default
522                 if (relyingParty.defaultToPOSTProfile()) {
523                         return false;
524                 } else {
525                         return true;
526                 }
527         }
528
529         /**
530          * Boolean indication of whether an assertion containing an attribute statement should be bundled in the response
531          * with the assertion containing the AuthN statement.
532          */
533         private static boolean pushAttributes(boolean artifactProfile, RelyingParty relyingParty) {
534
535                 // By default push for Artifact and don't push for POST
536                 // This can be overriden at the level of the relying party
537                 if (relyingParty.forceAttributePush()) {
538                         return true;
539                 } else if (relyingParty.forceAttributeNoPush()) {
540                         return false;
541                 } else if (artifactProfile) {
542                         return true;
543                 } else {
544                         return false;
545                 }
546         }
547
548         /**
549          * Boolean indication of whethere or not a given assertion consumer URL is valid for a given SP.
550          */
551         private static boolean isValidAssertionConsumerURL(EntityDescriptor descriptor, String shireURL)
552                         throws InvalidClientDataException {
553
554                 SPSSODescriptor sp = descriptor.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
555                 if (sp == null) {
556                         log.info("Inappropriate metadata for provider.");
557                         return false;
558                 }
559
560                 Iterator endpoints = sp.getAssertionConsumerServiceManager().getEndpoints();
561                 while (endpoints.hasNext()) {
562                         if (shireURL.equals(((Endpoint) endpoints.next()).getLocation())) { return true; }
563                 }
564                 log.info("Supplied consumer URL not found in metadata.");
565                 return false;
566         }
567 }