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