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