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