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