02471da7847d2fa72bef5fb81379de525ef055c3
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / saml2 / AbstractAuthenticationRequest.java
1 /*
2  * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.idp.profile.saml2;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.net.MalformedURLException;
22 import java.net.URL;
23 import java.net.URLConnection;
24 import java.util.LinkedList;
25 import java.util.List;
26
27 import javax.servlet.RequestDispatcher;
28 import javax.servlet.ServletResponse;
29 import javax.servlet.http.HttpServletRequest;
30 import javax.servlet.http.HttpServletResponse;
31 import javax.servlet.http.HttpSession;
32
33 import org.apache.log4j.Logger;
34 import org.joda.time.DateTime;
35 import org.opensaml.Configuration;
36 import org.opensaml.common.SAMLObjectBuilder;
37 import org.opensaml.common.binding.BindingException;
38 import org.opensaml.common.binding.encoding.MessageEncoder;
39 import org.opensaml.saml2.core.Assertion;
40 import org.opensaml.saml2.core.AuthnContext;
41 import org.opensaml.saml2.core.AuthnContextClassRef;
42 import org.opensaml.saml2.core.AuthnContextDeclRef;
43 import org.opensaml.saml2.core.AuthnRequest;
44 import org.opensaml.saml2.core.AuthnStatement;
45 import org.opensaml.saml2.core.GetComplete;
46 import org.opensaml.saml2.core.IDPEntry;
47 import org.opensaml.saml2.core.IDPList;
48 import org.opensaml.saml2.core.Issuer;
49 import org.opensaml.saml2.core.RequestedAuthnContext;
50 import org.opensaml.saml2.core.Response;
51 import org.opensaml.saml2.core.Scoping;
52 import org.opensaml.saml2.core.Status;
53 import org.opensaml.saml2.core.StatusCode;
54 import org.opensaml.saml2.core.Subject;
55 import org.opensaml.saml2.core.SubjectConfirmation;
56 import org.opensaml.saml2.metadata.AssertionConsumerService;
57 import org.opensaml.saml2.metadata.SPSSODescriptor;
58 import org.opensaml.saml2.metadata.provider.MetadataProvider;
59 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
60 import org.opensaml.xml.io.Unmarshaller;
61 import org.opensaml.xml.io.UnmarshallingException;
62 import org.opensaml.xml.parse.BasicParserPool;
63 import org.opensaml.xml.parse.ParserPool;
64 import org.opensaml.xml.parse.XMLParserException;
65 import org.w3c.dom.Document;
66 import org.w3c.dom.Element;
67
68
69 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
70 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
71 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
72 import edu.internet2.middleware.shibboleth.common.relyingparty.saml2.AbstractSAML2ProfileConfiguration;
73 import edu.internet2.middleware.shibboleth.common.relyingparty.saml2.SSOConfiguration;
74 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationManager;
75 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
76 import edu.internet2.middleware.shibboleth.idp.authn.Saml2LoginContext;
77 import javax.servlet.ServletException;
78
79
80 /**
81  * Abstract SAML 2.0 Authentication Request profile handler.
82  */
83 public abstract class AbstractAuthenticationRequest extends AbstractSAML2ProfileHandler {
84     
85     /** Class logger. */
86     private static final Logger log = Logger.getLogger(AbstractAuthenticationRequest.class);
87     
88     /** Key in an HttpSession for the AssertionConsumerService object. */
89     protected static final String ACS_SESSION_KEY = "AssertionConsumerService";
90     
91     /** Key in an HttpSession for the RelyingParty. */
92     protected static final String RPCONFIG_SESSION_KEY = "RelyingPartyConfiguration";
93     
94     /** Key in an HttpSession for the SSOConfiguration. */
95     protected static final String SSOCONFIG_SESSION_KEY = "SSOConfiguration";
96     
97     /** Key in an HttpSession for the SPSSODescriptor. */
98     protected static final String SPSSODESC_SESSION_KEY = "SPSSODescriptor";
99     
100     /** Key in an HttpSession for the AuthnRequest. */
101     protected static final String AUTHNREQUEST_SESSION_KEY = "AuthnRequest";
102     
103     /** Key in an HttpSession for the Issuer. */
104     protected static final String ISSUER_SESSION_KEY = "Issuer";
105     
106     
107     /** The path to the IdP's AuthenticationManager servlet */
108     protected String authnMgrURL;
109     
110     /** AuthenticationManager to be used */
111     protected AuthenticationManager authnMgr;
112     
113     /** A pool of XML parsers. */
114     protected ParserPool parserPool;
115     
116     /** Builder for AuthnStatements. */
117     protected SAMLObjectBuilder<AuthnStatement> authnStatementBuilder;
118     
119     /** Builder for AuthnContexts. */
120     protected SAMLObjectBuilder<AuthnContext> authnContextBuilder;
121     
122     /** Builder for AuthnContextDeclRef's */
123     protected SAMLObjectBuilder<AuthnContextDeclRef> authnContextDeclRefBuilder;
124     
125     /** Builder for AuthnContextClassRef's. */
126     protected SAMLObjectBuilder<AuthnContextClassRef> authnContextClassRefBuilder;
127     
128     /**
129      * Constructor.
130      */
131     public AbstractAuthenticationRequest() {
132         
133         parserPool = new BasicParserPool();
134         authnStatementBuilder = (SAMLObjectBuilder<AuthnStatement>) getBuilderFactory().getBuilder(AuthnStatement.DEFAULT_ELEMENT_NAME);
135         authnContextBuilder = (SAMLObjectBuilder<AuthnContext>) getBuilderFactory().getBuilder(AuthnContext.DEFAULT_ELEMENT_NAME);
136         authnContextDeclRefBuilder = (SAMLObjectBuilder<AuthnContextDeclRef>) getBuilderFactory().getBuilder(AuthnContextDeclRef.DEFAULT_ELEMENT_NAME);
137         authnContextClassRefBuilder = (SAMLObjectBuilder<AuthnContextClassRef>) getBuilderFactory().getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
138     }
139     
140     /**
141      * Set the Authentication Mananger.
142      *
143      * @param authnManager
144      *            The IdP's AuthenticationManager.
145      */
146     public void setAuthenticationManager(AuthenticationManager authnManager) {
147         this.authnMgr = authnMgr;
148     }
149     
150     /**
151      * Evaluate a SAML 2 AuthenticationRequest message.
152      *
153      * @param authnRequest
154      *            A SAML 2 AuthenticationRequest
155      * @param issuer
156      *            The issuer of the authnRequest.
157      * @param session
158      *            The HttpSession of the request.
159      * @param relyingParty
160      *            The RelyingPartyConfiguration for the request.
161      * @param ssoConfig
162      *            The SSOConfiguration for the request.
163      * @param spDescriptor
164      *            The SPSSODescriptor for the request.
165      *
166      * @return A Response containing a failure message or a AuthenticationStmt.
167      *
168      * @throws ProfileException
169      *             On Error.
170      */
171     protected Response evaluateRequest(final AuthnRequest authnRequest,
172             final Issuer issuer, final HttpSession session,
173             final RelyingPartyConfiguration relyingParty,
174             final SSOConfiguration ssoConfig, final SPSSODescriptor spDescriptor)
175             throws ProfileException {
176         
177         Response samlResponse;
178         
179         // check if the authentication was successful.
180         Saml2LoginContext loginCtx = getLoginContext(session);
181         if (!loginCtx.getAuthenticationOK()) {
182             // if authentication failed, send the appropriate SAML error message.
183             String failureMessage = loginCtx.getAuthenticationFailureMessage();
184             Status failureStatus = buildStatus(StatusCode.RESPONDER_URI, StatusCode.AUTHN_FAILED_URI, failureMessage);
185             samlResponse = buildResponse(authnRequest.getID(), new DateTime(), relyingParty.getProviderId(),
186                     failureStatus);
187             
188             return samlResponse;
189         }
190         
191         // the user successfully authenticated.
192         // build an authentication assertion.
193         samlResponse = buildResponse(authnRequest.getID(), new DateTime(),
194                 relyingParty.getProviderId(), buildStatus(StatusCode.SUCCESS_URI, null, null));
195         
196         DateTime now = new DateTime();
197         Assertion assertion = buildAssertion(now, relyingParty,
198                 (AbstractSAML2ProfileConfiguration) relyingParty.getProfileConfigurations().get(SSOConfiguration.PROFILE_ID));
199         assertion.setSubject(authnRequest.getSubject());
200         setAuthenticationStatement(assertion, loginCtx, authnRequest);
201         
202         samlResponse.getAssertions().add(assertion);
203         
204         // retrieve the AssertionConsumerService endpoint (we parsed it in
205         // verifyAuthnRequest()
206         AssertionConsumerService acsEndpoint = getACSEndpointFromSession(session);
207         
208         
209         
210         return samlResponse;
211     }
212     
213     /**
214      * Build a SAML 2 Response element with basic fields populated.
215      *
216      * Failure handlers can send the returned response element to the RP.
217      * Success handlers should add the assertions before sending it.
218      *
219      * @param inResponseTo
220      *            The ID of the request this is in response to.
221      * @param issueInstant
222      *            The timestamp of this response.
223      * @param issuer
224      *            The URI of the RP issuing the response.
225      * @param status
226      *            The response's status code.
227      *
228      * @return The populated Response object.
229      */
230     protected Response buildResponse(String inResponseTo,
231             final DateTime issueInstant, String issuer, final Status status) {
232         
233         Response response = getResponseBuilder().buildObject();
234         
235         Issuer i = getIssuerBuilder().buildObject();
236         i.setValue(issuer);
237         
238         response.setVersion(SAML_VERSION);
239         response.setID(getIdGenerator().generateIdentifier());
240         response.setInResponseTo(inResponseTo);
241         response.setIssueInstant(issueInstant);
242         response.setIssuer(i);
243         response.setStatus(status);
244         
245         return response;
246     }
247     
248     /**
249      * Check if the user has already been authenticated.
250      *
251      * @param httpSession
252      *            the user's HttpSession.
253      *
254      * @return <code>true</code> if the user has been authenticated. otherwise
255      *         <code>false</code>
256      */
257     protected boolean hasUserAuthenticated(final HttpSession httpSession) {
258         
259         // if the user has authenticated, their session will have a LoginContext
260         
261         Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
262         return (o != null && o instanceof LoginContext);
263     }
264     
265     /**
266      * Store a user's AuthnRequest and Issuer in the session.
267      *
268      * @param authnRequest
269      *            A SAML 2 AuthnRequest.
270      * @param issuer
271      *            The issuer of the AuthnRequest.
272      * @param session
273      *            The HttpSession in which the data should be stored.
274      * @param relyingParty
275      *            The RelyingPartyConfiguration for the issuer.
276      * @param ssoConfig
277      *            The SSOConfiguration for the relyingParty
278      * @param spDescriptor
279      *            The SPSSODescriptor for the ssoConfig.
280      */
281     protected void storeRequestData(final HttpSession session,
282             final AuthnRequest authnRequest, final Issuer issuer,
283             final RelyingPartyConfiguration relyingParty,
284             final SSOConfiguration ssoConfig, final SPSSODescriptor spDescriptor) {
285         
286         if (session == null) {
287             return;
288         }
289         
290         session.setAttribute(AUTHNREQUEST_SESSION_KEY, authnRequest);
291         session.setAttribute(ISSUER_SESSION_KEY, issuer);
292         session.setAttribute(RPCONFIG_SESSION_KEY, relyingParty);
293         session.setAttribute(SSOCONFIG_SESSION_KEY, ssoConfig);
294         session.setAttribute(SPSSODESC_SESSION_KEY, spDescriptor);
295     }
296     
297     /**
298      * Retrieve the AuthnRequest and Issuer from a session.
299      *
300      * @param session
301      *            The HttpSession in which the data was stored.
302      * @param authnRequest
303      *            Will be populated with the AuthnRequest.
304      * @param issuer
305      *            Will be populated with the ssuer of the AuthnRequest.
306      * @param relyingParty
307      *            Will be populated with the RelyingPartyConfiguration for the
308      *            issuer.
309      * @param ssoConfig
310      *            Will be populated with the SSOConfiguration for the
311      *            relyingParty
312      * @param spDescriptor
313      *            Will be populated with the SPSSODescriptor for the ssoConfig.
314      */
315     protected void retrieveRequestData(final HttpSession session,
316             AuthnRequest authnRequest, Issuer issuer,
317             RelyingPartyConfiguration relyingParty, SSOConfiguration ssoConfig,
318             SPSSODescriptor spDescriptor) {
319         
320         if (session == null) {
321             authnRequest = null;
322             issuer = null;
323         }
324         
325         authnRequest = (AuthnRequest) session
326                 .getAttribute(AUTHNREQUEST_SESSION_KEY);
327         issuer = (Issuer) session.getAttribute(ISSUER_SESSION_KEY);
328         relyingParty = (RelyingPartyConfiguration) session
329                 .getAttribute(RPCONFIG_SESSION_KEY);
330         ssoConfig = (SSOConfiguration) session
331                 .getAttribute(SSOCONFIG_SESSION_KEY);
332         spDescriptor = (SPSSODescriptor) session
333                 .getAttribute(SPSSODESC_SESSION_KEY);
334         
335         session.removeAttribute(AUTHNREQUEST_SESSION_KEY);
336         session.removeAttribute(ISSUER_SESSION_KEY);
337         session.removeAttribute(RPCONFIG_SESSION_KEY);
338         session.removeAttribute(SSOCONFIG_SESSION_KEY);
339         session.removeAttribute(SPSSODESC_SESSION_KEY);
340     }
341     
342     /**
343      * Check if the user has already been authenticated. If so, return the
344      * LoginContext. If not, redirect the user to the AuthenticationManager.
345      *
346      * @param authnRequest
347      *            The SAML 2 AuthnRequest.
348      * @param httpSession
349      *            The user's HttpSession.
350      * @param request
351      *            The user's HttpServletRequest.
352      * @param response
353      *            The user's HttpServletResponse.
354      *
355      * @throws ProfileException
356      *             on error.
357      */
358     protected void authenticateUser(final AuthnRequest authnRequest,
359             final HttpSession httpSession, final HttpServletRequest request,
360             final HttpServletResponse response) throws ProfileException {
361         
362         // Forward the request to the AuthenticationManager.
363         // When the AuthenticationManager is done it will
364         // forward the request back to this servlet.
365         
366         Saml2LoginContext loginCtx = new Saml2LoginContext(authnRequest);
367         loginCtx.setProfileHandlerURL(request.getRequestURI());
368         httpSession.setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginCtx);
369         try {
370             RequestDispatcher dispatcher = request
371                     .getRequestDispatcher(authnMgrURL);
372             dispatcher.forward(request, response);
373         } catch (IOException ex) {
374             log.error("Error forwarding SAML 2 AuthnRequest "
375                     + authnRequest.getID() + " to AuthenticationManager", ex);
376             throw new ProfileException("Error forwarding SAML 2 AuthnRequest "
377                     + authnRequest.getID() + " to AuthenticationManager", ex);
378         } catch (ServletException ex) {
379             log.error("Error forwarding SAML 2 AuthnRequest "
380                     + authnRequest.getID() + " to AuthenticationManager", ex);
381             throw new ProfileException("Error forwarding SAML 2 AuthnRequest "
382                     + authnRequest.getID() + " to AuthenticationManager", ex);
383         }
384     }
385     
386     /**
387      * Build an AuthnStatement and add it to an Assertion.
388      *
389      * @param assertion An empty SAML 2 Assertion object.
390      * @param loginContext The processed login context for the AuthnRequest.
391      * @param authnRequest The AuthnRequest to which this is in response.
392      *
393      * @throws ProfileException On error.
394      */
395     protected void setAuthenticationStatement(Assertion assertion,
396             final Saml2LoginContext loginContext,
397             final AuthnRequest authnRequest) throws ProfileException {
398         
399         // Build the AuthnCtx.
400         // We need to determine if the user was authenticated
401         // with an AuthnContextClassRef or a AuthnContextDeclRef
402         AuthnContext authnCtx = buildAuthnCtx(authnRequest.getRequestedAuthnContext(), loginContext);
403         if (authnCtx == null) {
404             log.error("Error respond to SAML 2 AuthnRequest "
405                     + authnRequest.getID()
406                     + " : Unable to determine authentication method");
407         }
408         
409         AuthnStatement stmt = authnStatementBuilder.buildObject();
410         stmt.setAuthnInstant(loginContext.getAuthenticationInstant());
411         stmt.setAuthnContext(authnCtx);
412         
413         // add the AuthnStatement to the Assertion
414         List<AuthnStatement> authnStatements = assertion.getAuthnStatements();
415         authnStatements.add(stmt);
416     }
417     
418     /**
419      * Create the AuthnContex object.
420      *
421      * To do this, we have to walk the AuthnRequest's RequestedAuthnContext
422      * object and compare any values we find to what's set in the loginContext.
423      *
424      * @param requestedAuthnCtx
425      *            The RequestedAuthnContext from the Authentication Request.
426      * @param loginContext
427      *            The processed LoginContext (it must contain the authn method).
428      *
429      * @return An AuthnCtx object on success or <code>null</code> on failure.
430      */
431     protected AuthnContext buildAuthnCtx(
432             final RequestedAuthnContext requestedAuthnCtx,
433             final Saml2LoginContext loginContext) {
434         
435         // this method assumes that only one URI will match.
436         
437         AuthnContext authnCtx = authnContextBuilder.buildObject();
438         String authnMethod = loginContext.getAuthenticationMethod();
439         
440         List<AuthnContextClassRef> authnClasses = requestedAuthnCtx
441                 .getAuthnContextClassRefs();
442         List<AuthnContextDeclRef> authnDeclRefs = requestedAuthnCtx
443                 .getAuthnContextDeclRefs();
444         
445         if (authnClasses != null) {
446             for (AuthnContextClassRef classRef : authnClasses) {
447                 if (classRef != null) {
448                     String s = classRef.getAuthnContextClassRef();
449                     if (s != null && authnMethod.equals(s)) {
450                         AuthnContextClassRef ref = authnContextClassRefBuilder
451                                 .buildObject();
452                         authnCtx.setAuthnContextClassRef(ref);
453                         return authnCtx;
454                     }
455                 }
456             }
457         }
458         
459         // if no AuthnContextClassRef's matched, try the DeclRefs
460         if (authnDeclRefs != null) {
461             for (AuthnContextDeclRef declRef : authnDeclRefs) {
462                 if (declRef != null) {
463                     String s = declRef.getAuthnContextDeclRef();
464                     if (s != null && authnMethod.equals((s))) {
465                         AuthnContextDeclRef ref = authnContextDeclRefBuilder
466                                 .buildObject();
467                         authnCtx.setAuthnContextDeclRef(ref);
468                         return authnCtx;
469                     }
470                 }
471             }
472         }
473         
474         // no matches were found.
475         return null;
476     }
477     
478     /**
479      * Get the user's LoginContext.
480      *
481      * @param httpSession
482      *            The user's HttpSession.
483      *
484      * @return The user's LoginContext.
485      *
486      * @throws ProfileException
487      *             On error.
488      */
489     protected Saml2LoginContext getLoginContext(final HttpSession httpSession)
490             throws ProfileException {
491         
492         Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
493         if (o == null) {
494             log.error("User's session does not contain a LoginContext object.");
495             throw new ProfileException(
496                     "User's session does not contain a LoginContext object.");
497         }
498         
499         if (!(o instanceof Saml2LoginContext)) {
500             log
501                     .error("Invalid login context object -- object is not an instance of Saml2LoginContext.");
502             throw new ProfileException("Invalid login context object.");
503         }
504         
505         Saml2LoginContext ctx = (Saml2LoginContext) o;
506         
507         httpSession.removeAttribute(LoginContext.LOGIN_CONTEXT_KEY);
508         
509         return ctx;
510     }
511     
512     /**
513      * Verify the AuthnRequest is well-formed.
514      *
515      * @param authnRequest
516      *            The user's SAML 2 AuthnRequest.
517      * @param issuer
518      *            The Issuer of the AuthnRequest.
519      * @param relyingParty
520      *            The relying party configuration for the request's originator.
521      * @param session
522      *            The user's HttpSession.
523      *
524      * @throws AuthenticationRequestException
525      *             on error.
526      */
527     protected void verifyAuthnRequest(final AuthnRequest authnRequest,
528             Issuer issuer, final RelyingPartyConfiguration relyingParty,
529             final HttpSession session) throws AuthenticationRequestException {
530         
531         Status failureStatus;
532         
533         // Check if we are in scope to handle this AuthnRequest
534         checkScope(authnRequest, issuer.getSPProvidedID());
535         
536         // XXX: run signature checks on authnRequest
537         
538         // verify that the AssertionConsumerService url is valid.
539         AssertionConsumerService acsEndpoint = getAndVerifyACSEndpoint(
540                 authnRequest, relyingParty.getRelyingPartyId(),
541                 getRelyingPartyConfigurationManager().getMetadataProvider());
542         session.setAttribute(ACS_SESSION_KEY, acsEndpoint);
543         
544         // check for nameID constraints.
545         Subject subject = getAndVerifySubject(authnRequest);
546     }
547     
548     /**
549      * Get and verify the Subject element.
550      *
551      * @param authnRequest
552      *            The SAML 2 AuthnRequest.
553      *
554      * @return A Subject element.
555      *
556      * @throws AuthenticationRequestException
557      *             on error.
558      */
559     protected Subject getAndVerifySubject(final AuthnRequest authnRequest)
560             throws AuthenticationRequestException {
561         
562         Status failureStatus;
563         
564         Subject subject = authnRequest.getSubject();
565         
566         if (subject == null) {
567             failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
568                     "SAML 2 AuthnRequest " + authnRequest.getID()
569                     + " is malformed: It does not contain a Subject.");
570             throw new AuthenticationRequestException(
571                     "AuthnRequest lacks a Subject", failureStatus);
572         }
573         
574         // The Web Browser SSO profile disallows SubjectConfirmation
575         // methods in the requested subject.
576         List<SubjectConfirmation> confMethods = subject
577                 .getSubjectConfirmations();
578         if (confMethods != null || confMethods.size() > 0) {
579             log
580                     .error("SAML 2 AuthnRequest "
581                     + authnRequest.getID()
582                     + " is malformed: It contains SubjectConfirmation elements.");
583             failureStatus = buildStatus(
584                     StatusCode.REQUESTER_URI,
585                     null,
586                     "SAML 2 AuthnRequest "
587                     + authnRequest.getID()
588                     + " is malformed: It contains SubjectConfirmation elements.");
589             throw new AuthenticationRequestException(
590                     "AuthnRequest contains SubjectConfirmation elements",
591                     failureStatus);
592         }
593         
594         return subject;
595     }
596     
597     /**
598      * Return the endpoint URL and protocol binding to use for the AuthnRequest.
599      *
600      * @param authnRequest
601      *            The SAML 2 AuthnRequest.
602      * @param providerId
603      *            The SP's providerId.
604      * @param metadata
605      *            The appropriate Metadata.
606      *
607      * @return The AssertionConsumerService for the endpoint, or
608      *         <code>null</code> on error.
609      *
610      * @throws AuthenticationRequestException
611      *             On error.
612      */
613     protected AssertionConsumerService getAndVerifyACSEndpoint(
614             final AuthnRequest authnRequest, String providerId,
615             final MetadataProvider metadata)
616             throws AuthenticationRequestException {
617         
618         Status failureStatus;
619         
620         // Either the AssertionConsumerServiceIndex must be present
621         // or AssertionConsumerServiceURL must be present.
622         
623         Integer idx = authnRequest.getAssertionConsumerServiceIndex();
624         String acsURL = authnRequest.getAssertionConsumerServiceURL();
625         
626         if (idx != null && acsURL != null) {
627             log
628                     .error("SAML 2 AuthnRequest "
629                     + authnRequest.getID()
630                     + " is malformed: It contains both an AssertionConsumerServiceIndex and an AssertionConsumerServiceURL");
631             failureStatus = buildStatus(
632                     StatusCode.REQUESTER_URI,
633                     null,
634                     "SAML 2 AuthnRequest "
635                     + authnRequest.getID()
636                     + " is malformed: It contains both an AssertionConsumerServiceIndex and an AssertionConsumerServiceURL");
637             throw new AuthenticationRequestException("Malformed AuthnRequest",
638                     failureStatus);
639         }
640         
641         SPSSODescriptor spDescriptor;
642         try {
643             spDescriptor = metadata.getEntityDescriptor(providerId)
644                     .getSPSSODescriptor(SAML20_PROTOCOL_URI);
645         } catch (MetadataProviderException ex) {
646             log.error(
647                     "Unable retrieve SPSSODescriptor metadata for providerId "
648                     + providerId
649                     + " while processing SAML 2 AuthnRequest "
650                     + authnRequest.getID(), ex);
651             failureStatus = buildStatus(StatusCode.RESPONDER_URI, null,
652                     "Unable to locate metadata for " + providerId);
653             throw new AuthenticationRequestException(
654                     "Unable to locate metadata", ex, failureStatus);
655         }
656         
657         List<AssertionConsumerService> acsList = spDescriptor
658                 .getAssertionConsumerServices();
659         
660         // if the ACS index is specified, retrieve it from the metadata
661         if (idx != null) {
662             
663             int i = idx.intValue();
664             
665             // if the index is out of range, return an appropriate error.
666             if (i > acsList.size()) {
667                 log.error("Illegal AssertionConsumerIndex specicifed (" + i
668                         + ") in SAML 2 AuthnRequest " + authnRequest.getID());
669                 
670                 failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
671                         "Illegal AssertionConsumerIndex specicifed (" + i
672                         + ") in SAML 2 AuthnRequest "
673                         + authnRequest.getID());
674                 
675                 throw new AuthenticationRequestException(
676                         "Illegal AssertionConsumerIndex in AuthnRequest",
677                         failureStatus);
678             }
679             
680             return acsList.get(i);
681         }
682         
683         // if the ACS endpoint is specified, validate it against the metadata
684         String protocolBinding = authnRequest.getProtocolBinding();
685         for (AssertionConsumerService acs : acsList) {
686             if (acsURL.equals(acs.getLocation())) {
687                 if (protocolBinding != null) {
688                     if (protocolBinding.equals(acs.getBinding())) {
689                         return acs;
690                     }
691                 }
692             }
693         }
694         
695         log
696                 .error("Error processing SAML 2 AuthnRequest message "
697                 + authnRequest.getID()
698                 + ": Unable to validate AssertionConsumerServiceURL against metadata: "
699                 + acsURL + " for binding " + protocolBinding);
700         
701         failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
702                 "Unable to validate AssertionConsumerService against metadata.");
703         
704         throw new AuthenticationRequestException(
705                 "SAML 2 AuthenticationRequest: Unable to validate AssertionConsumerService against Metadata",
706                 failureStatus);
707     }
708     
709     /**
710      * Retrieve a parsed AssertionConsumerService endpoint from the user's
711      * session.
712      *
713      * @param session
714      *            The user's HttpSession.
715      *
716      * @return An AssertionConsumerServiceEndpoint object.
717      *
718      * @throws ProfileException
719      *             On error.
720      */
721     protected AssertionConsumerService getACSEndpointFromSession(
722             final HttpSession session) throws ProfileException {
723         
724         Object o = session.getAttribute(ACS_SESSION_KEY);
725         if (o == null) {
726             log
727                     .error("User's session does not contain an AssertionConsumerService object.");
728             throw new ProfileException(
729                     "User's session does not contain an AssertionConsumerService object.");
730         }
731         
732         if (!(o instanceof AssertionConsumerService)) {
733             log
734                     .error("Invalid session data -- object is not an instance of AssertionConsumerService.");
735             throw new ProfileException(
736                     "Invalid session data -- object is not an instance of AssertionConsumerService.");
737         }
738         
739         AssertionConsumerService endpoint = (AssertionConsumerService) o;
740         
741         session.removeAttribute(ACS_SESSION_KEY);
742         
743         return endpoint;
744     }
745     
746     /**
747      * Check if an {@link AuthnRequest} contains a {@link Scoping} element. If
748      * so, check if the specified IdP is in the {@link IDPList} element. If no
749      * Scoping element is present, this method returns <code>true</code>.
750      *
751      * @param authnRequest
752      *            The {@link AuthnRequest} element to check.
753      * @param providerId
754      *            The IdP's ProviderID.
755      *
756      * @throws AuthenticationRequestException
757      *             on error.
758      */
759     protected void checkScope(final AuthnRequest authnRequest, String providerId)
760             throws AuthenticationRequestException {
761         
762         Status failureStatus;
763         
764         List<String> idpEntries = new LinkedList<String>();
765         
766         Scoping scoping = authnRequest.getScoping();
767         if (scoping == null) {
768             return;
769         }
770         
771         // process all of the explicitly listed idp provider ids
772         IDPList idpList = scoping.getIDPList();
773         if (idpList == null) {
774             return;
775         }
776         
777         List<IDPEntry> explicitIDPEntries = idpList.getIDPEntrys();
778         if (explicitIDPEntries != null) {
779             for (IDPEntry entry : explicitIDPEntries) {
780                 String s = entry.getProviderID();
781                 if (s != null) {
782                     idpEntries.add(s);
783                 }
784             }
785         }
786         
787         // If the IDPList is incomplete, retrieve the complete list
788         // and add the entries to idpEntries.
789         GetComplete getComplete = idpList.getGetComplete();
790         IDPList referencedIdPs = getCompleteIDPList(getComplete);
791         if (referencedIdPs != null) {
792             List<IDPEntry> referencedIDPEntries = referencedIdPs.getIDPEntrys();
793             if (referencedIDPEntries != null) {
794                 for (IDPEntry entry : referencedIDPEntries) {
795                     String s = entry.getProviderID();
796                     if (s != null) {
797                         idpEntries.add(s);
798                     }
799                 }
800             }
801         }
802         
803         // iterate over all the IDPEntries we've gathered,
804         // and check if we're in scope.
805         for (String requestProviderId : idpEntries) {
806             if (providerId.equals(requestProviderId)) {
807                 log.debug("Found Scoping match for IdP: (" + providerId + ")");
808                 return;
809             }
810         }
811         
812         log.error("SAML 2 AuthnRequest " + authnRequest.getID()
813                 + " contains a Scoping element which "
814                 + "does not contain a providerID registered with this IdP.");
815         
816         failureStatus = buildStatus(StatusCode.RESPONDER_URI,
817                 StatusCode.NO_SUPPORTED_IDP_URI, null);
818         throw new AuthenticationRequestException(
819                 "Unrecognized providerID in Scoping element", failureStatus);
820     }
821     
822     /**
823      * Retrieve an incomplete IDPlist.
824      *
825      * This only handles URL-based <GetComplete/> references.
826      *
827      * @param getComplete
828      *            The (possibly <code>null</code>) &lt;GetComplete/&gt;
829      *            element
830      *
831      * @return an {@link IDPList} or <code>null</code> if the uri can't be
832      *         dereferenced.
833      */
834     protected IDPList getCompleteIDPList(final GetComplete getComplete) {
835         
836         // XXX: enhance this method to cache the url and last-modified-header
837         
838         if (getComplete == null) {
839             return null;
840         }
841         
842         String uri = getComplete.getGetComplete();
843         if (uri != null) {
844             return null;
845         }
846         
847         IDPList idpList = null;
848         InputStream istream = null;
849         
850         try {
851             URL url = new URL(uri);
852             URLConnection conn = url.openConnection();
853             istream = conn.getInputStream();
854             
855             // convert the raw data into an XML object
856             Document doc = parserPool.parse(istream);
857             Element docElement = doc.getDocumentElement();
858             Unmarshaller unmarshaller = Configuration.getUnmarshallerFactory()
859                     .getUnmarshaller(docElement);
860             idpList = (IDPList) unmarshaller.unmarshall(docElement);
861             
862         } catch (MalformedURLException ex) {
863             log.error(
864                     "Unable to retrieve GetComplete IDPList. Unsupported URI: "
865                     + uri, ex);
866         } catch (IOException ex) {
867             log.error("IO Error while retreieving GetComplete IDPList from "
868                     + uri, ex);
869         } catch (XMLParserException ex) {
870             log.error(
871                     "Internal OpenSAML error while parsing GetComplete IDPList from "
872                     + uri, ex);
873         } catch (UnmarshallingException ex) {
874             log.error(
875                     "Internal OpenSAML error while unmarshalling GetComplete IDPList from "
876                     + uri, ex);
877         } finally {
878             
879             if (istream != null) {
880                 try {
881                     istream.close();
882                 } catch (IOException ex) {
883                     // pass
884                 }
885             }
886         }
887         
888         return idpList;
889     }
890     
891     protected void encodeResponse(String binding, final ProfileResponse<ServletResponse> response,
892             final Response samlResponse,
893             final RelyingPartyConfiguration relyingParty,
894             final SSOConfiguration ssoConfig, final SPSSODescriptor spDescriptor) throws ProfileException {
895         
896         MessageEncoder<ServletResponse> encoder = getMessageEncoderFactory().getMessageEncoder(binding);
897         if (encoder == null) {
898             log.error("SAML 2 Authentication Request: No MessageEncoder registered for " + binding);
899             throw new ProfileException("SAML 2 Authentication Request: No MessageEncoder registered for " + binding);
900         }
901         
902         encoder.setResponse(response.getRawResponse());
903         encoder.setIssuer(relyingParty.getProviderId());
904         encoder.setMetadataProvider(getRelyingPartyConfigurationManager().getMetadataProvider());
905         encoder.setRelyingPartyRole(spDescriptor);
906         encoder.setSigningCredential(relyingParty.getDefaultSigningCredential());
907         encoder.setSamlMessage(samlResponse);
908         encoder.setRelyingPartyEndpoint(spDescriptor.getDefaultAssertionConsumerService());
909         
910         try {
911             encoder.encode();
912         } catch (BindingException ex) {
913             log.error("Unable to encode response the relying party: " + relyingParty.getRelyingPartyId(), ex);
914             throw new ProfileException("Unable to encode response the relying party: "
915                     + relyingParty.getRelyingPartyId(), ex);
916         }
917         
918     }
919     
920 }