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