Provided a mechanism for AuthN systems to supply the actual time of user authenticati...
[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.bouncycastle.util.encoders.Base64;
47 import org.opensaml.SAMLAssertion;
48 import org.opensaml.SAMLAttribute;
49 import org.opensaml.SAMLAttributeStatement;
50 import org.opensaml.SAMLAudienceRestrictionCondition;
51 import org.opensaml.SAMLAuthenticationStatement;
52 import org.opensaml.SAMLAuthorityBinding;
53 import org.opensaml.SAMLBinding;
54 import org.opensaml.SAMLBrowserProfile;
55 import org.opensaml.SAMLCondition;
56 import org.opensaml.SAMLException;
57 import org.opensaml.SAMLNameIdentifier;
58 import org.opensaml.SAMLRequest;
59 import org.opensaml.SAMLResponse;
60 import org.opensaml.SAMLStatement;
61 import org.opensaml.SAMLSubject;
62 import org.opensaml.artifact.Artifact;
63 import org.w3c.dom.Document;
64 import org.w3c.dom.Element;
65
66 import edu.internet2.middleware.shibboleth.aa.AAException;
67 import edu.internet2.middleware.shibboleth.common.LocalPrincipal;
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                         LocalPrincipal principal = new LocalPrincipal(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                         SAMLSubject authNSubject = new SAMLSubject(nameId, null, null, null);
181                         ArrayList assertions = new ArrayList();
182
183                         // Is this artifact or POST?
184                         boolean artifactProfile = useArtifactProfile(descriptor, acceptanceURL, relyingParty);
185
186                         // Package attributes for push, if necessary - don't attempt this for legacy providers (they don't support
187                         // it)
188                         if (!relyingParty.isLegacyProvider() && pushAttributes(artifactProfile, relyingParty)) {
189                                 log.info("Resolving attributes for push.");
190                                 SAMLAssertion attrAssertion = generateAttributeAssertion(support, principal, relyingParty, authNSubject);
191                                 if (attrAssertion != null) {
192                                         assertions.add(attrAssertion);
193                                 } else {
194                                         log.info("No attributes resolved.");
195                                 }
196                         }
197
198                         // SAML Artifact profile - don't even attempt this for legacy providers (they don't support it)
199                         if (!relyingParty.isLegacyProvider() && artifactProfile) {
200                                 respondWithArtifact(request, response, support, principal, relyingParty, descriptor, acceptanceURL,
201                                                 nameId, authenticationMethod, authNSubject, assertions);
202
203                                 // SAML POST profile
204                         } else {
205                                 respondWithPOST(request, response, support, principal, relyingParty, descriptor, acceptanceURL, nameId,
206                                                 authenticationMethod, authNSubject, assertions);
207                         }
208                 } catch (InvalidClientDataException e) {
209                         throw new SAMLException(SAMLException.RESPONDER, e.getMessage());
210                 }
211                 return null;
212         }
213
214         private void respondWithArtifact(HttpServletRequest request, HttpServletResponse response,
215                         IdPProtocolSupport support, LocalPrincipal principal, RelyingParty relyingParty,
216                         EntityDescriptor descriptor, String acceptanceURL, SAMLNameIdentifier nameId, String authenticationMethod,
217                         SAMLSubject authNSubject, List assertions) throws SAMLException, IOException, UnsupportedEncodingException {
218
219                 log.debug("Responding with Artifact profile.");
220
221                 authNSubject.addConfirmationMethod(SAMLSubject.CONF_ARTIFACT);
222                 assertions.add(generateAuthNAssertion(request, relyingParty, descriptor, nameId, authenticationMethod,
223                                 getAuthNTime(request), authNSubject));
224
225                 // Sign the assertions, if necessary
226                 boolean metaDataIndicatesSignAssertions = false;
227                 if (descriptor != null) {
228                         SPSSODescriptor sp = descriptor.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
229                         if (sp != null) {
230                                 if (sp.getWantAssertionsSigned()) {
231                                         metaDataIndicatesSignAssertions = true;
232                                 }
233                         }
234                 }
235                 if (relyingParty.wantsAssertionsSigned() || metaDataIndicatesSignAssertions) {
236                         support.signAssertions((SAMLAssertion[]) assertions.toArray(new SAMLAssertion[0]), relyingParty);
237                 }
238
239                 // Create artifacts for each assertion
240                 ArrayList artifacts = new ArrayList();
241                 for (int i = 0; i < assertions.size(); i++) {
242                         artifacts
243                                         .add(support.getArtifactMapper().generateArtifact((SAMLAssertion) assertions.get(i), relyingParty));
244                 }
245
246                 // Assemble the query string
247                 StringBuffer destination = new StringBuffer(acceptanceURL);
248                 destination.append("?TARGET=");
249                 destination.append(URLEncoder.encode(request.getParameter("target"), "UTF-8"));
250                 Iterator iterator = artifacts.iterator();
251                 StringBuffer artifactBuffer = new StringBuffer(); // Buffer for the transaction log
252
253                 // Construct the artifact query parameter
254                 while (iterator.hasNext()) {
255                         Artifact artifact = (Artifact) iterator.next();
256                         artifactBuffer.append("(" + artifact.encode() + ")");
257                         destination.append("&SAMLart=");
258                         destination.append(URLEncoder.encode(artifact.encode(), "UTF-8"));
259                 }
260
261                 log.debug("Redirecting to (" + destination.toString() + ").");
262                 response.sendRedirect(destination.toString()); // Redirect to the artifact receiver
263                 support.getTransactionLog().info(
264                                 "Assertion artifact(s) (" + artifactBuffer.toString() + ") issued to provider ("
265                                                 + relyingParty.getIdentityProvider().getProviderId() + ") on behalf of principal ("
266                                                 + principal.getName() + "). Name Identifier: (" + nameId.getName()
267                                                 + "). Name Identifier Format: (" + nameId.getFormat() + ").");
268         }
269
270         private void respondWithPOST(HttpServletRequest request, HttpServletResponse response, IdPProtocolSupport support,
271                         LocalPrincipal principal, RelyingParty relyingParty, EntityDescriptor descriptor, String acceptanceURL,
272                         SAMLNameIdentifier nameId, String authenticationMethod, SAMLSubject authNSubject, List assertions)
273                         throws SAMLException, IOException, ServletException {
274
275                 log.debug("Responding with POST profile.");
276                 authNSubject.addConfirmationMethod(SAMLSubject.CONF_BEARER);
277                 assertions.add(generateAuthNAssertion(request, relyingParty, descriptor, nameId, authenticationMethod,
278                                 getAuthNTime(request), authNSubject));
279
280                 // Sign the assertions, if necessary
281                 boolean metaDataIndicatesSignAssertions = false;
282                 if (descriptor != null) {
283                         SPSSODescriptor sp = descriptor.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
284                         if (sp != null) {
285                                 if (sp.getWantAssertionsSigned()) {
286                                         metaDataIndicatesSignAssertions = true;
287                                 }
288                         }
289                 }
290                 if (relyingParty.wantsAssertionsSigned() || metaDataIndicatesSignAssertions) {
291                         support.signAssertions((SAMLAssertion[]) assertions.toArray(new SAMLAssertion[0]), relyingParty);
292                 }
293
294                 // Set attributes needed by form
295                 request.setAttribute("acceptanceURL", acceptanceURL);
296                 request.setAttribute("target", request.getParameter("target"));
297
298                 SAMLResponse samlResponse = new SAMLResponse(null, acceptanceURL, assertions, null);
299
300                 support.signResponse(samlResponse, relyingParty);
301
302                 createPOSTForm(request, response, samlResponse.toBase64());
303
304                 // Make transaction log entry
305                 if (relyingParty.isLegacyProvider()) {
306                         support.getTransactionLog().info(
307                                         "Authentication assertion issued to legacy provider (SHIRE: " + request.getParameter("shire")
308                                                         + ") on behalf of principal (" + principal.getName() + ") for resource ("
309                                                         + request.getParameter("target") + "). Name Identifier: (" + nameId.getName()
310                                                         + "). Name Identifier Format: (" + nameId.getFormat() + ").");
311
312                 } else {
313                         support.getTransactionLog().info(
314                                         "Authentication assertion issued to provider ("
315                                                         + relyingParty.getIdentityProvider().getProviderId() + ") on behalf of principal ("
316                                                         + principal.getName() + "). Name Identifier: (" + nameId.getName()
317                                                         + "). Name Identifier Format: (" + nameId.getFormat() + ").");
318                 }
319         }
320
321         private SAMLAssertion generateAttributeAssertion(IdPProtocolSupport support, LocalPrincipal principal,
322                         RelyingParty relyingParty, SAMLSubject authNSubject) throws SAMLException {
323
324                 try {
325                         SAMLAttribute[] attributes = support.getReleaseAttributes(principal, relyingParty, relyingParty
326                                         .getProviderId(), null);
327                         log.info("Found " + attributes.length + " attribute(s) for " + principal.getName());
328
329                         // Bail if we didn't get any attributes
330                         if (attributes == null || attributes.length < 1) {
331                                 return null;
332
333                         } else {
334                                 // Reference requested subject
335                                 SAMLSubject attrSubject = (SAMLSubject) authNSubject.clone();
336
337                                 ArrayList audiences = new ArrayList();
338                                 if (relyingParty.getProviderId() != null) {
339                                         audiences.add(relyingParty.getProviderId());
340                                 }
341                                 if (relyingParty.getName() != null && !relyingParty.getName().equals(relyingParty.getProviderId())) {
342                                         audiences.add(relyingParty.getName());
343                                 }
344                                 SAMLCondition condition = new SAMLAudienceRestrictionCondition(audiences);
345
346                                 // Put all attributes into an assertion
347                                 SAMLStatement statement = new SAMLAttributeStatement(attrSubject, Arrays.asList(attributes));
348
349                                 // Set assertion expiration to longest attribute expiration
350                                 long max = 0;
351                                 for (int i = 0; i < attributes.length; i++) {
352                                         if (max < attributes[i].getLifetime()) {
353                                                 max = attributes[i].getLifetime();
354                                         }
355                                 }
356                                 Date now = new Date();
357                                 Date then = new Date(now.getTime() + (max * 1000)); // max is in seconds
358
359                                 SAMLAssertion attrAssertion = new SAMLAssertion(relyingParty.getIdentityProvider().getProviderId(),
360                                                 now, then, Collections.singleton(condition), null, Collections.singleton(statement));
361
362                                 if (log.isDebugEnabled()) {
363                                         log.debug("Dumping generated Attribute Assertion:" + System.getProperty("line.separator")
364                                                         + attrAssertion.toString());
365                                 }
366                                 return attrAssertion;
367                         }
368                 } catch (AAException e) {
369                         log.error("An error was encountered while generating assertion for attribute push: " + e);
370                         throw new SAMLException(SAMLException.RESPONDER, "General error processing request.");
371                 } catch (CloneNotSupportedException 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                 }
375         }
376
377         private SAMLAssertion generateAuthNAssertion(HttpServletRequest request, RelyingParty relyingParty,
378                         EntityDescriptor descriptor, SAMLNameIdentifier nameId, String authenticationMethod, Date authTime,
379                         SAMLSubject subject) throws SAMLException, IOException {
380
381                 Document doc = org.opensaml.XML.parserPool.newDocument();
382
383                 // Determine the correct audiences
384                 ArrayList audiences = new ArrayList();
385                 if (relyingParty.getProviderId() != null) {
386                         audiences.add(relyingParty.getProviderId());
387                 }
388                 if (relyingParty.getName() != null && !relyingParty.getName().equals(relyingParty.getProviderId())) {
389                         audiences.add(relyingParty.getName());
390                 }
391
392                 // Determine the correct issuer
393                 String issuer = null;
394                 if (relyingParty.isLegacyProvider()) {
395
396                         log.debug("Service Provider is running Shibboleth <= 1.1. Using old style issuer.");
397                         if (relyingParty.getIdentityProvider().getSigningCredential() == null
398                                         || relyingParty.getIdentityProvider().getSigningCredential().getX509Certificate() == null) { throw new SAMLException(
399                                         "Cannot serve legacy style assertions without an X509 certificate"); }
400                         issuer = getHostNameFromDN(relyingParty.getIdentityProvider().getSigningCredential().getX509Certificate()
401                                         .getSubjectX500Principal());
402                         if (issuer == null || issuer.equals("")) { throw new SAMLException(
403                                         "Error parsing certificate DN while determining legacy issuer name."); }
404
405                 } else {
406                         issuer = relyingParty.getIdentityProvider().getProviderId();
407                 }
408
409                 // For compatibility with pre-1.2 shibboleth targets, include a pointer to the AA
410                 ArrayList bindings = new ArrayList();
411                 if (relyingParty.isLegacyProvider()) {
412
413                         SAMLAuthorityBinding binding = new SAMLAuthorityBinding(SAMLBinding.SOAP, relyingParty.getAAUrl()
414                                         .toString(), new QName(org.opensaml.XML.SAMLP_NS, "AttributeQuery"));
415                         bindings.add(binding);
416                 }
417
418                 // Create the assertion
419                 Vector conditions = new Vector(1);
420                 if (audiences != null && audiences.size() > 0) conditions.add(new SAMLAudienceRestrictionCondition(audiences));
421
422                 SAMLStatement[] statements = {new SAMLAuthenticationStatement(subject, authenticationMethod, authTime, request
423                                 .getRemoteAddr(), null, bindings)};
424
425                 SAMLAssertion assertion = new SAMLAssertion(issuer, new Date(System.currentTimeMillis()), new Date(System
426                                 .currentTimeMillis() + 300000), conditions, null, Arrays.asList(statements));
427
428                 if (log.isDebugEnabled()) {
429                         log.debug("Dumping generated AuthN Assertion:" + System.getProperty("line.separator")
430                                         + assertion.toString());
431                 }
432
433                 return assertion;
434         }
435
436         /*
437          * @see edu.internet2.middleware.shibboleth.idp.IdPResponder.ProtocolHandler#getHandlerName()
438          */
439         public String getHandlerName() {
440
441                 return "Shibboleth v1.x SSO";
442         }
443
444         private void validateShibSpecificData(HttpServletRequest request) throws InvalidClientDataException {
445
446                 if (request.getParameter("target") == null || request.getParameter("target").equals("")) { throw new InvalidClientDataException(
447                                 "Invalid data from Service Provider: no target URL received."); }
448                 if ((request.getParameter("shire") == null) || (request.getParameter("shire").equals(""))) { throw new InvalidClientDataException(
449                                 "Invalid data from Service Provider: No acceptance URL received."); }
450         }
451
452         private static void createPOSTForm(HttpServletRequest req, HttpServletResponse res, byte[] buf) throws IOException,
453                         ServletException {
454
455                 // Hardcoded to ASCII to ensure Base64 encoding compatibility
456                 req.setAttribute("assertion", new String(buf, "ASCII"));
457
458                 if (log.isDebugEnabled()) {
459                         log.debug("Dumping generated SAML Response:" + System.getProperty("line.separator")
460                                         + new String(Base64.decode(buf)));
461                 }
462
463                 RequestDispatcher rd = req.getRequestDispatcher("/IdP.jsp");
464                 rd.forward(req, res);
465         }
466
467         /**
468          * Boolean indication of which browser profile is in effect. "true" indicates Artifact and "false" indicates POST.
469          */
470         private static boolean useArtifactProfile(EntityDescriptor descriptor, String acceptanceURL,
471                         RelyingParty relyingParty) {
472
473                 boolean artifactMeta = false;
474                 boolean postMeta = false;
475
476                 // Look at the metadata bindings, if we can find them
477                 if (descriptor != null) {
478                         SPSSODescriptor sp = descriptor.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
479
480                         if (sp != null) {
481
482                                 Iterator endpoints = sp.getAssertionConsumerServiceManager().getEndpoints();
483                                 while (endpoints.hasNext()) {
484                                         Endpoint ep = (Endpoint) endpoints.next();
485                                         if (acceptanceURL.equals(ep.getLocation())
486                                                         && SAMLBrowserProfile.PROFILE_POST_URI.equals(ep.getBinding())) {
487                                                 log.debug("Metadata indicates support for POST profile.");
488                                                 postMeta = true;
489                                                 continue;
490                                         }
491                                 }
492                                 endpoints = sp.getAssertionConsumerServiceManager().getEndpoints();
493                                 while (endpoints.hasNext()) {
494                                         Endpoint ep = (Endpoint) endpoints.next();
495                                         if (acceptanceURL.equals(ep.getLocation())
496                                                         && SAMLBrowserProfile.PROFILE_ARTIFACT_URI.equals(ep.getBinding())) {
497                                                 log.debug("Metadata indicates support for Artifact profile.");
498                                                 artifactMeta = true;
499                                                 continue;
500                                         }
501                                 }
502                         }
503                 }
504
505                 // If we have metadata for both, use the relying party default
506                 if (!(artifactMeta && postMeta)) {
507
508                         // If we only have metadata for one, use it
509                         if (artifactMeta) { return true; }
510                         if (postMeta) { return false; }
511
512                 }
513
514                 // If we have missing or incomplete metadata, use relying party default
515                 if (relyingParty.defaultToPOSTProfile()) {
516                         return false;
517                 } else {
518                         return true;
519                 }
520         }
521
522         /**
523          * Boolean indication of whether an assertion containing an attribute statement should be bundled in the response
524          * with the assertion containing the AuthN statement.
525          */
526         private static boolean pushAttributes(boolean artifactProfile, RelyingParty relyingParty) {
527
528                 // By default push for Artifact and don't push for POST
529                 // This can be overriden at the level of the relying party
530                 if (relyingParty.forceAttributePush()) {
531                         return true;
532                 } else if (relyingParty.forceAttributeNoPush()) {
533                         return false;
534                 } else if (artifactProfile) {
535                         return true;
536                 } else {
537                         return false;
538                 }
539         }
540
541         /**
542          * Boolean indication of whethere or not a given assertion consumer URL is valid for a given SP.
543          */
544         private static boolean isValidAssertionConsumerURL(EntityDescriptor descriptor, String shireURL)
545                         throws InvalidClientDataException {
546
547                 SPSSODescriptor sp = descriptor.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
548                 if (sp == null) {
549                         log.info("Inappropriate metadata for provider.");
550                         return false;
551                 }
552
553                 Iterator endpoints = sp.getAssertionConsumerServiceManager().getEndpoints();
554                 while (endpoints.hasNext()) {
555                         if (shireURL.equals(((Endpoint) endpoints.next()).getLocation())) { return true; }
556                 }
557                 log.info("Supplied consumer URL not found in metadata.");
558                 return false;
559         }
560 }