2 * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package edu.internet2.middleware.shibboleth.idp.profile.saml2;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.net.MalformedURLException;
23 import java.net.URLConnection;
24 import java.util.LinkedList;
25 import java.util.List;
27 import javax.servlet.RequestDispatcher;
28 import javax.servlet.ServletException;
29 import javax.servlet.http.HttpServletRequest;
30 import javax.servlet.http.HttpServletResponse;
31 import javax.servlet.http.HttpSession;
33 import org.apache.log4j.Logger;
34 import org.joda.time.DateTime;
35 import org.opensaml.Configuration;
36 import org.opensaml.common.SAMLObjectBuilder;
37 import org.opensaml.common.binding.ArtifactMap;
38 import org.opensaml.common.binding.SAMLArtifactFactory;
39 import org.opensaml.saml2.core.Assertion;
40 import org.opensaml.saml2.core.AuthnContext;
41 import org.opensaml.saml2.core.AuthnContextClassRef;
42 import org.opensaml.saml2.core.AuthnContextDeclRef;
43 import org.opensaml.saml2.core.AuthnRequest;
44 import org.opensaml.saml2.core.AuthnStatement;
45 import org.opensaml.saml2.core.Condition;
46 import org.opensaml.saml2.core.Conditions;
47 import org.opensaml.saml2.core.GetComplete;
48 import org.opensaml.saml2.core.IDPEntry;
49 import org.opensaml.saml2.core.IDPList;
50 import org.opensaml.saml2.core.Issuer;
51 import org.opensaml.saml2.core.RequestedAuthnContext;
52 import org.opensaml.saml2.core.Response;
53 import org.opensaml.saml2.core.Scoping;
54 import org.opensaml.saml2.core.Status;
55 import org.opensaml.saml2.core.StatusCode;
56 import org.opensaml.saml2.core.Subject;
57 import org.opensaml.saml2.core.SubjectConfirmation;
58 import org.opensaml.saml2.metadata.AssertionConsumerService;
59 import org.opensaml.saml2.metadata.SPSSODescriptor;
60 import org.opensaml.saml2.metadata.provider.MetadataProvider;
61 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
62 import org.opensaml.xml.ConfigurationException;
63 import org.opensaml.xml.io.Unmarshaller;
64 import org.opensaml.xml.io.UnmarshallingException;
65 import org.opensaml.xml.parse.BasicParserPool;
66 import org.opensaml.xml.parse.ParserPool;
67 import org.opensaml.xml.parse.XMLParserException;
68 import org.w3c.dom.Document;
69 import org.w3c.dom.Element;
71 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
72 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
73 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
74 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfigurationManager;
75 import edu.internet2.middleware.shibboleth.common.relyingparty.saml2.AbstractSAML2ProfileConfiguration;
76 import edu.internet2.middleware.shibboleth.common.relyingparty.saml2.SSOConfiguration;
77 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationManager;
78 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
79 import edu.internet2.middleware.shibboleth.idp.authn.Saml2LoginContext;
83 * Abstract SAML 2.0 Authentication Request profile handler.
85 public abstract class AbstractAuthenticationRequest extends AbstractSAML2ProfileHandler {
88 private static final Logger log = Logger.getLogger(AbstractAuthenticationRequest.class);
90 /** Key in an HttpSession for the AssertionConsumerService object. */
91 protected static final String ACS_SESSION_KEY = "AssertionConsumerService";
93 /** Key in an HttpSession for the RelyingParty. */
94 protected static final String RPCONFIG_SESSION_KEY = "RelyingPartyConfiguration";
96 /** Key in an HttpSession for the SSOConfiguration. */
97 protected static final String SSOCONFIG_SESSION_KEY = "SSOConfiguration";
99 /** Key in an HttpSession for the SPSSODescriptor. */
100 protected static final String SPSSODESC_SESSION_KEY = "SPSSODescriptor";
102 /** Key in an HttpSession for the AuthnRequest. */
103 protected static final String AUTHNREQUEST_SESSION_KEY = "AuthnRequest";
105 /** Key in an HttpSession for the Issuer. */
106 protected static final String ISSUER_SESSION_KEY = "Issuer";
108 /** Backing store for artifacts. */
109 protected ArtifactMap artifactMap;
111 /** The path to the IdP's AuthenticationManager servlet */
112 protected String authnMgrURL;
114 /** AuthenticationManager to be used */
115 protected AuthenticationManager authnMgr;
117 /** ArtifactFactory used to make artifacts. */
118 protected SAMLArtifactFactory artifactFactory;
120 /** A pool of XML parsers. */
121 protected ParserPool parserPool;
123 /** Builder for AuthnStatements. */
124 protected SAMLObjectBuilder<AuthnStatement> authnStatementBuilder;
126 /** Builder for AuthnContexts. */
127 protected SAMLObjectBuilder<AuthnContext> authnContextBuilder;
129 /** Builder for AuthnContextDeclRef's */
130 protected SAMLObjectBuilder<AuthnContextDeclRef> authnContextDeclRefBuilder;
132 /** Builder for AuthnContextClassRef's. */
133 protected SAMLObjectBuilder<AuthnContextClassRef> authnContextClassRefBuilder;
138 public AbstractAuthenticationRequest() {
140 parserPool = new BasicParserPool();
141 artifactFactory = new SAMLArtifactFactory();
142 authnStatementBuilder = (SAMLObjectBuilder<AuthnStatement>) getBuilderFactory().getBuilder(AuthnStatement.DEFAULT_ELEMENT_NAME);
143 authnContextBuilder = (SAMLObjectBuilder<AuthnContext>) getBuilderFactory().getBuilder(AuthnContext.DEFAULT_ELEMENT_NAME);
144 authnContextDeclRefBuilder = (SAMLObjectBuilder<AuthnContextDeclRef>) getBuilderFactory().getBuilder(AuthnContextDeclRef.DEFAULT_ELEMENT_NAME);
145 authnContextClassRefBuilder = (SAMLObjectBuilder<AuthnContextClassRef>) getBuilderFactory().getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
149 * Set the Authentication Mananger.
151 * @param authnManager
152 * The IdP's AuthenticationManager.
154 public void setAuthenticationManager(AuthenticationManager authnManager) {
155 this.authnMgr = authnMgr;
159 * Set the ArtifactMap.
162 * The IdP's ArtifactMap.
164 public void setArtifactMap(ArtifactMap artifactMap) {
165 this.artifactMap = artifactMap;
169 * Evaluate a SAML 2 AuthenticationRequest message.
171 * @param authnRequest
172 * A SAML 2 AuthenticationRequest
174 * The issuer of the authnRequest.
176 * The HttpSession of the request.
177 * @param relyingParty
178 * The RelyingPartyConfiguration for the request.
180 * The SSOConfiguration for the request.
181 * @param spDescriptor
182 * The SPSSODescriptor for the request.
184 * @return A Response containing a failure message or a AuthenticationStmt.
186 * @throws ServletException
189 protected Response evaluateRequest(final AuthnRequest authnRequest,
190 final Issuer issuer, final HttpSession session,
191 final RelyingPartyConfiguration relyingParty,
192 final SSOConfiguration ssoConfig, final SPSSODescriptor spDescriptor)
193 throws ServletException {
195 Response samlResponse;
198 // check if the authentication was successful.
199 Saml2LoginContext loginCtx = getLoginContext(session);
200 if (!loginCtx.getAuthenticationOK()) {
201 // if authentication failed, send the appropriate SAML error message.
202 String failureMessage = loginCtx.getAuthenticationFailureMessage();
203 Status failureStatus = buildStatus(StatusCode.RESPONDER_URI, StatusCode.AUTHN_FAILED_URI, failureMessage);
204 samlResponse = buildResponse(authnRequest.getID(), new DateTime(), relyingParty.getProviderId(),
210 // the user successfully authenticated.
211 // build an authentication assertion.
212 samlResponse = buildResponse(authnRequest.getID(), new DateTime(),
213 relyingParty.getProviderId(), buildStatus(StatusCode.SUCCESS_URI, null, null));
215 DateTime now = new DateTime();
216 Assertion assertion = buildAssertion(now, relyingParty,
217 (AbstractSAML2ProfileConfiguration) relyingParty.getProfileConfigurations().get(SSOConfiguration.PROFILE_ID));
218 assertion.setSubject(authnRequest.getSubject());
219 setAuthenticationStatement(assertion, loginCtx, authnRequest);
221 samlResponse.getAssertions().add(assertion);
223 // retrieve the AssertionConsumerService endpoint (we parsed it in
224 // verifyAuthnRequest()
225 AssertionConsumerService acsEndpoint = getACSEndpointFromSession(session);
227 } catch (AuthenticationRequestException ex) {
229 Status errorStatus = ex.getStatus();
230 if (errorStatus == null) {
231 // if no explicit status code was set,
232 // assume the error was in the message.
233 samlResponse = buildResponse(authnRequest.getID(),
234 new DateTime(), relyingParty.getProviderId(),
243 * Build a SAML 2 Response element with basic fields populated.
245 * Failure handlers can send the returned response element to the RP.
246 * Success handlers should add the assertions before sending it.
248 * @param inResponseTo
249 * The ID of the request this is in response to.
250 * @param issueInstant
251 * The timestamp of this response.
253 * The URI of the RP issuing the response.
255 * The response's status code.
257 * @return The populated Response object.
259 protected Response buildResponse(String inResponseTo,
260 final DateTime issueInstant, String issuer, final Status status) {
262 Response response = getResponseBuilder().buildObject();
264 Issuer i = getIssuerBuilder().buildObject();
267 response.setVersion(SAML_VERSION);
268 response.setID(getIdGenerator().generateIdentifier());
269 response.setInResponseTo(inResponseTo);
270 response.setIssueInstant(issueInstant);
271 response.setIssuer(i);
272 response.setStatus(status);
279 * Check that a request's issuer can be found in the metadata. If so, store
280 * the relevant metadata objects in the user's session.
283 * The issuer of the AuthnRequest.
284 * @param relyingParty
285 * The RelyingPartyConfiguration for the issuer.
287 * The SSOConfiguration for the relyingParty
288 * @param spDescriptor
289 * The SPSSODescriptor for the ssoConfig.
291 * @return <code>true</code> if Metadata was found for the issuer;
292 * otherwise, <code>false</code>.
294 protected boolean findMetadataForSSORequest(Issuer issuer,
295 RelyingPartyConfiguration relyingParty, SSOConfiguration ssoConfig,
296 SPSSODescriptor spDescriptor) {
298 MetadataProvider metadataProvider = getRelyingPartyConfigurationManager()
299 .getMetadataProvider();
300 String providerId = issuer.getSPProvidedID();
301 relyingParty = getRelyingPartyConfigurationManager()
302 .getRelyingPartyConfiguration(providerId);
303 ssoConfig = (SSOConfiguration) relyingParty.getProfileConfigurations()
304 .get(SSOConfiguration.PROFILE_ID);
307 spDescriptor = metadataProvider.getEntityDescriptor(
308 relyingParty.getRelyingPartyId()).getSPSSODescriptor(
309 SAML20_PROTOCOL_URI);
310 } catch (MetadataProviderException ex) {
312 "SAML 2 Authentication Request: Unable to locate metadata for SP "
313 + providerId + " for protocol "
314 + SAML20_PROTOCOL_URI, ex);
318 if (spDescriptor == null) {
320 .error("SAML 2 Authentication Request: Unable to locate metadata for SP "
323 + SAML20_PROTOCOL_URI);
331 * Check if the user has already been authenticated.
334 * the user's HttpSession.
336 * @return <code>true</code> if the user has been authenticated. otherwise
339 protected boolean hasUserAuthenticated(final HttpSession httpSession) {
341 // if the user has authenticated, their session will have a LoginContext
343 Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
344 return (o != null && o instanceof LoginContext);
348 * Store a user's AuthnRequest and Issuer in the session.
350 * @param authnRequest
351 * A SAML 2 AuthnRequest.
353 * The issuer of the AuthnRequest.
355 * The HttpSession in which the data should be stored.
356 * @param relyingParty
357 * The RelyingPartyConfiguration for the issuer.
359 * The SSOConfiguration for the relyingParty
360 * @param spDescriptor
361 * The SPSSODescriptor for the ssoConfig.
363 protected void storeRequestData(final HttpSession session,
364 final AuthnRequest authnRequest, final Issuer issuer,
365 final RelyingPartyConfiguration relyingParty,
366 final SSOConfiguration ssoConfig, final SPSSODescriptor spDescriptor) {
368 if (session == null) {
372 session.setAttribute(AUTHNREQUEST_SESSION_KEY, authnRequest);
373 session.setAttribute(ISSUER_SESSION_KEY, issuer);
374 session.setAttribute(RPCONFIG_SESSION_KEY, relyingParty);
375 session.setAttribute(SSOCONFIG_SESSION_KEY, ssoConfig);
376 session.setAttribute(SPSSODESC_SESSION_KEY, spDescriptor);
380 * Retrieve the AuthnRequest and Issuer from a session.
383 * The HttpSession in which the data was stored.
384 * @param authnRequest
385 * Will be populated with the AuthnRequest.
387 * Will be populated with the ssuer of the AuthnRequest.
388 * @param relyingParty
389 * Will be populated with the RelyingPartyConfiguration for the
392 * Will be populated with the SSOConfiguration for the
394 * @param spDescriptor
395 * Will be populated with the SPSSODescriptor for the ssoConfig.
397 protected void retrieveRequestData(final HttpSession session,
398 AuthnRequest authnRequest, Issuer issuer,
399 RelyingPartyConfiguration relyingParty, SSOConfiguration ssoConfig,
400 SPSSODescriptor spDescriptor) {
402 if (session == null) {
407 authnRequest = (AuthnRequest) session
408 .getAttribute(AUTHNREQUEST_SESSION_KEY);
409 issuer = (Issuer) session.getAttribute(ISSUER_SESSION_KEY);
410 relyingParty = (RelyingPartyConfiguration) session
411 .getAttribute(RPCONFIG_SESSION_KEY);
412 ssoConfig = (SSOConfiguration) session
413 .getAttribute(SSOCONFIG_SESSION_KEY);
414 spDescriptor = (SPSSODescriptor) session
415 .getAttribute(SPSSODESC_SESSION_KEY);
417 session.removeAttribute(AUTHNREQUEST_SESSION_KEY);
418 session.removeAttribute(ISSUER_SESSION_KEY);
419 session.removeAttribute(RPCONFIG_SESSION_KEY);
420 session.removeAttribute(SSOCONFIG_SESSION_KEY);
421 session.removeAttribute(SPSSODESC_SESSION_KEY);
425 * Check if the user has already been authenticated. If so, return the
426 * LoginContext. If not, redirect the user to the AuthenticationManager.
428 * @param authnRequest
429 * The SAML 2 AuthnRequest.
431 * The user's HttpSession.
433 * The user's HttpServletRequest.
435 * The user's HttpServletResponse.
437 * @return A LoginContext for the authenticated user.
439 * @throws SerlvetException
442 protected void authenticateUser(final AuthnRequest authnRequest,
443 final HttpSession httpSession, final HttpServletRequest request,
444 final HttpServletResponse response) throws ServletException {
446 // Forward the request to the AuthenticationManager.
447 // When the AuthenticationManager is done it will
448 // forward the request back to this servlet.
450 Saml2LoginContext loginCtx = new Saml2LoginContext(authnRequest);
451 loginCtx.setProfileHandlerURL(request.getPathInfo());
452 httpSession.setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginCtx);
454 RequestDispatcher dispatcher = request
455 .getRequestDispatcher(authnMgrURL);
456 dispatcher.forward(request, response);
457 } catch (IOException ex) {
458 log.error("Error forwarding SAML 2 AuthnRequest "
459 + authnRequest.getID() + " to AuthenticationManager", ex);
460 throw new ServletException("Error forwarding SAML 2 AuthnRequest "
461 + authnRequest.getID() + " to AuthenticationManager", ex);
466 * Build an AuthnStatement and add it to a Response.
469 * The Response to which the AuthnStatement will be added.
471 * The LoginContext of the sucessfully authenticated user.
472 * @param authnRequest
473 * The AuthnRequest that prompted this message.
475 * The SSOConfiguration for the RP to which we are addressing
478 * The IdP's identifier.
480 * An array of URIs restricting the audience of this assertion.
482 protected void setAuthenticationStatement(Assertion assertion,
483 final Saml2LoginContext loginContext,
484 final AuthnRequest authnRequest) throws ServletException {
486 // Build the AuthnCtx. We need to determine if the user was
488 // with an AuthnContextClassRef or a AuthnContextDeclRef
489 AuthnContext authnCtx = buildAuthnCtx(authnRequest
490 .getRequestedAuthnContext(), loginContext);
491 if (authnCtx == null) {
492 log.error("Error respond to SAML 2 AuthnRequest "
493 + authnRequest.getID()
494 + " : Unable to determine authentication method");
497 AuthnStatement stmt = authnStatementBuilder.buildObject();
498 stmt.setAuthnInstant(loginContext.getAuthenticationInstant());
499 stmt.setAuthnContext(authnCtx);
501 // add the AuthnStatement to the Assertion
502 List<AuthnStatement> authnStatements = assertion.getAuthnStatements();
503 authnStatements.add(stmt);
507 * Create the AuthnContex object.
509 * To do this, we have to walk the AuthnRequest's RequestedAuthnContext
510 * object and compare any values we find to what's set in the loginContext.
512 * @param requestedAuthnCtx
513 * The RequestedAuthnContext from the Authentication Request.
515 * The authentication method that was used.
517 * @return An AuthnCtx object on success or <code>null</code> on failure.
519 protected AuthnContext buildAuthnCtx(
520 final RequestedAuthnContext requestedAuthnCtx,
521 final Saml2LoginContext loginContext) {
523 // this method assumes that only one URI will match.
525 AuthnContext authnCtx = authnContextBuilder.buildObject();
526 String authnMethod = loginContext.getAuthenticationMethod();
528 List<AuthnContextClassRef> authnClasses = requestedAuthnCtx
529 .getAuthnContextClassRefs();
530 List<AuthnContextDeclRef> authnDeclRefs = requestedAuthnCtx
531 .getAuthnContextDeclRefs();
533 if (authnClasses != null) {
534 for (AuthnContextClassRef classRef : authnClasses) {
535 if (classRef != null) {
536 String s = classRef.getAuthnContextClassRef();
537 if (s != null && authnMethod.equals(s)) {
538 AuthnContextClassRef ref = authnContextClassRefBuilder
540 authnCtx.setAuthnContextClassRef(ref);
547 // if no AuthnContextClassRef's matched, try the DeclRefs
548 if (authnDeclRefs != null) {
549 for (AuthnContextDeclRef declRef : authnDeclRefs) {
550 if (declRef != null) {
551 String s = declRef.getAuthnContextDeclRef();
552 if (s != null && authnMethod.equals((s))) {
553 AuthnContextDeclRef ref = authnContextDeclRefBuilder
555 authnCtx.setAuthnContextDeclRef(ref);
562 // no matches were found.
567 * Get the user's LoginContext.
570 * The user's HttpSession.
572 * @return The user's LoginContext.
574 * @throws ServletException
577 protected Saml2LoginContext getLoginContext(final HttpSession httpSession)
578 throws ServletException {
580 Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
582 log.error("User's session does not contain a LoginContext object.");
583 throw new ServletException(
584 "User's session does not contain a LoginContext object.");
587 if (!(o instanceof Saml2LoginContext)) {
589 .error("Invalid login context object -- object is not an instance of Saml2LoginContext.");
590 throw new ServletException("Invalid login context object.");
593 Saml2LoginContext ctx = (Saml2LoginContext) o;
595 httpSession.removeAttribute(LoginContext.LOGIN_CONTEXT_KEY);
601 * Verify the AuthnRequest is well-formed.
603 * @param authnRequest
604 * The user's SAML 2 AuthnRequest.
606 * The Issuer of the AuthnRequest.
607 * @param relyingParty
608 * The relying party configuration for the request's originator.
610 * The user's HttpSession.
612 * @throws AuthenticationRequestException
615 protected void verifyAuthnRequest(final AuthnRequest authnRequest,
616 Issuer issuer, final RelyingPartyConfiguration relyingParty,
617 final HttpSession session) throws AuthenticationRequestException {
619 Status failureStatus;
621 // Check if we are in scope to handle this AuthnRequest
622 checkScope(authnRequest, issuer.getSPProvidedID());
624 // XXX: run signature checks on authnRequest
626 // verify that the AssertionConsumerService url is valid.
627 AssertionConsumerService acsEndpoint = getAndVerifyACSEndpoint(
628 authnRequest, relyingParty.getRelyingPartyId(),
629 getRelyingPartyConfigurationManager().getMetadataProvider());
630 session.setAttribute(ACS_SESSION_KEY, acsEndpoint);
632 // check for nameID constraints.
633 Subject subject = getAndVerifySubject(authnRequest);
637 * Get and verify the Subject element.
639 * @param authnRequest
640 * The SAML 2 AuthnRequest.
642 * @return A Subject element.
644 * @throws AuthenticationRequestException
647 protected Subject getAndVerifySubject(final AuthnRequest authnRequest)
648 throws AuthenticationRequestException {
650 Status failureStatus;
652 Subject subject = authnRequest.getSubject();
654 if (subject == null) {
655 failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
656 "SAML 2 AuthnRequest " + authnRequest.getID()
657 + " is malformed: It does not contain a Subject.");
658 throw new AuthenticationRequestException(
659 "AuthnRequest lacks a Subject", failureStatus);
662 // The Web Browser SSO profile disallows SubjectConfirmation
663 // methods in the requested subject.
664 List<SubjectConfirmation> confMethods = subject
665 .getSubjectConfirmations();
666 if (confMethods != null || confMethods.size() > 0) {
668 .error("SAML 2 AuthnRequest "
669 + authnRequest.getID()
670 + " is malformed: It contains SubjectConfirmation elements.");
671 failureStatus = buildStatus(
672 StatusCode.REQUESTER_URI,
674 "SAML 2 AuthnRequest "
675 + authnRequest.getID()
676 + " is malformed: It contains SubjectConfirmation elements.");
677 throw new AuthenticationRequestException(
678 "AuthnRequest contains SubjectConfirmation elements",
686 * Return the endpoint URL and protocol binding to use for the AuthnRequest.
688 * @param authnRequest
689 * The SAML 2 AuthnRequest.
691 * The SP's providerId.
693 * The appropriate Metadata.
695 * @return The AssertionConsumerService for the endpoint, or
696 * <code>null</code> on error.
698 * @throws AuthenticationRequestException
701 protected AssertionConsumerService getAndVerifyACSEndpoint(
702 final AuthnRequest authnRequest, String providerId,
703 final MetadataProvider metadata)
704 throws AuthenticationRequestException {
706 Status failureStatus;
708 // Either the AssertionConsumerServiceIndex must be present
709 // or AssertionConsumerServiceURL must be present.
711 Integer idx = authnRequest.getAssertionConsumerServiceIndex();
712 String acsURL = authnRequest.getAssertionConsumerServiceURL();
714 if (idx != null && acsURL != null) {
716 .error("SAML 2 AuthnRequest "
717 + authnRequest.getID()
718 + " is malformed: It contains both an AssertionConsumerServiceIndex and an AssertionConsumerServiceURL");
719 failureStatus = buildStatus(
720 StatusCode.REQUESTER_URI,
722 "SAML 2 AuthnRequest "
723 + authnRequest.getID()
724 + " is malformed: It contains both an AssertionConsumerServiceIndex and an AssertionConsumerServiceURL");
725 throw new AuthenticationRequestException("Malformed AuthnRequest",
729 SPSSODescriptor spDescriptor;
731 spDescriptor = metadata.getEntityDescriptor(providerId)
732 .getSPSSODescriptor(SAML20_PROTOCOL_URI);
733 } catch (MetadataProviderException ex) {
735 "Unable retrieve SPSSODescriptor metadata for providerId "
737 + " while processing SAML 2 AuthnRequest "
738 + authnRequest.getID(), ex);
739 failureStatus = buildStatus(StatusCode.RESPONDER_URI, null,
740 "Unable to locate metadata for " + providerId);
741 throw new AuthenticationRequestException(
742 "Unable to locate metadata", ex, failureStatus);
745 List<AssertionConsumerService> acsList = spDescriptor
746 .getAssertionConsumerServices();
748 // if the ACS index is specified, retrieve it from the metadata
751 int i = idx.intValue();
753 // if the index is out of range, return an appropriate error.
754 if (i > acsList.size()) {
755 log.error("Illegal AssertionConsumerIndex specicifed (" + i
756 + ") in SAML 2 AuthnRequest " + authnRequest.getID());
758 failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
759 "Illegal AssertionConsumerIndex specicifed (" + i
760 + ") in SAML 2 AuthnRequest "
761 + authnRequest.getID());
763 throw new AuthenticationRequestException(
764 "Illegal AssertionConsumerIndex in AuthnRequest",
768 return acsList.get(i);
771 // if the ACS endpoint is specified, validate it against the metadata
772 String protocolBinding = authnRequest.getProtocolBinding();
773 for (AssertionConsumerService acs : acsList) {
774 if (acsURL.equals(acs.getLocation())) {
775 if (protocolBinding != null) {
776 if (protocolBinding.equals(acs.getBinding())) {
784 .error("Error processing SAML 2 AuthnRequest message "
785 + authnRequest.getID()
786 + ": Unable to validate AssertionConsumerServiceURL against metadata: "
787 + acsURL + " for binding " + protocolBinding);
789 failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
790 "Unable to validate AssertionConsumerService against metadata.");
792 throw new AuthenticationRequestException(
793 "SAML 2 AuthenticationRequest: Unable to validate AssertionConsumerService against Metadata",
798 * Retrieve a parsed AssertionConsumerService endpoint from the user's
802 * The user's HttpSession.
804 * @return An AssertionConsumerServiceEndpoint object.
806 * @throws ServletException
809 protected AssertionConsumerService getACSEndpointFromSession(
810 final HttpSession session) throws ServletException {
812 Object o = session.getAttribute(ACS_SESSION_KEY);
815 .error("User's session does not contain an AssertionConsumerService object.");
816 throw new ServletException(
817 "User's session does not contain an AssertionConsumerService object.");
820 if (!(o instanceof AssertionConsumerService)) {
822 .error("Invalid session data -- object is not an instance of AssertionConsumerService.");
823 throw new ServletException(
824 "Invalid session data -- object is not an instance of AssertionConsumerService.");
827 AssertionConsumerService endpoint = (AssertionConsumerService) o;
829 session.removeAttribute(ACS_SESSION_KEY);
835 * Check if an {@link AuthnRequest} contains a {@link Scoping} element. If
836 * so, check if the specified IdP is in the {@link IDPList} element. If no
837 * Scoping element is present, this method returns <code>true</code>.
839 * @param authnRequest
840 * The {@link AuthnRequest} element to check.
842 * The IdP's ProviderID.
844 * @throws AuthenticationRequestException
847 protected void checkScope(final AuthnRequest authnRequest, String providerId)
848 throws AuthenticationRequestException {
850 Status failureStatus;
852 List<String> idpEntries = new LinkedList<String>();
854 Scoping scoping = authnRequest.getScoping();
855 if (scoping == null) {
859 // process all of the explicitly listed idp provider ids
860 IDPList idpList = scoping.getIDPList();
861 if (idpList == null) {
865 List<IDPEntry> explicitIDPEntries = idpList.getIDPEntrys();
866 if (explicitIDPEntries != null) {
867 for (IDPEntry entry : explicitIDPEntries) {
868 String s = entry.getProviderID();
875 // If the IDPList is incomplete, retrieve the complete list
876 // and add the entries to idpEntries.
877 GetComplete getComplete = idpList.getGetComplete();
878 IDPList referencedIdPs = getCompleteIDPList(getComplete);
879 if (referencedIdPs != null) {
880 List<IDPEntry> referencedIDPEntries = referencedIdPs.getIDPEntrys();
881 if (referencedIDPEntries != null) {
882 for (IDPEntry entry : referencedIDPEntries) {
883 String s = entry.getProviderID();
891 // iterate over all the IDPEntries we've gathered,
892 // and check if we're in scope.
893 for (String requestProviderId : idpEntries) {
894 if (providerId.equals(requestProviderId)) {
895 log.debug("Found Scoping match for IdP: (" + providerId + ")");
900 log.error("SAML 2 AuthnRequest " + authnRequest.getID()
901 + " contains a Scoping element which "
902 + "does not contain a providerID registered with this IdP.");
904 failureStatus = buildStatus(StatusCode.RESPONDER_URI,
905 StatusCode.NO_SUPPORTED_IDP_URI, null);
906 throw new AuthenticationRequestException(
907 "Unrecognized providerID in Scoping element", failureStatus);
911 * Retrieve an incomplete IDPlist.
913 * This only handles URL-based <GetComplete/> references.
916 * The (possibly <code>null</code>) <GetComplete/>
919 * @return an {@link IDPList} or <code>null</code> if the uri can't be
922 protected IDPList getCompleteIDPList(final GetComplete getComplete) {
924 // XXX: enhance this method to cache the url and last-modified-header
926 if (getComplete == null) {
930 String uri = getComplete.getGetComplete();
935 IDPList idpList = null;
936 InputStream istream = null;
939 URL url = new URL(uri);
940 URLConnection conn = url.openConnection();
941 istream = conn.getInputStream();
943 // convert the raw data into an XML object
944 Document doc = parserPool.parse(istream);
945 Element docElement = doc.getDocumentElement();
946 Unmarshaller unmarshaller = Configuration.getUnmarshallerFactory()
947 .getUnmarshaller(docElement);
948 idpList = (IDPList) unmarshaller.unmarshall(docElement);
950 } catch (MalformedURLException ex) {
952 "Unable to retrieve GetComplete IDPList. Unsupported URI: "
954 } catch (IOException ex) {
955 log.error("IO Error while retreieving GetComplete IDPList from "
957 } catch (ConfigurationException ex) {
959 "Internal OpenSAML error while parsing GetComplete IDPList from "
961 } catch (XMLParserException ex) {
963 "Internal OpenSAML error while parsing GetComplete IDPList from "
965 } catch (UnmarshallingException ex) {
967 "Internal OpenSAML error while unmarshalling GetComplete IDPList from "
971 if (istream != null) {
974 } catch (IOException ex) {