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