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