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