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.ServletRequest;
30 import javax.servlet.ServletResponse;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
33 import javax.servlet.http.HttpSession;
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;
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;
84 * Abstract SAML 2.0 Authentication Request profile handler.
86 public abstract class AbstractAuthenticationRequest extends AbstractSAML2ProfileHandler {
89 * Represents the internal state of a SAML 2.0 Authentiation Request while it's being processed by the IdP.
91 protected class AuthenticationRequestContext {
93 /** The ProfileRequest. */
94 protected ProfileRequest<ServletRequest> profileRequest;
96 /** The ProfileResponse. */
97 protected ProfileResponse<ServletResponse> profileResponse;
99 /** The HttpServletRequest. */
100 protected HttpServletRequest servletRequest;
102 /** The HttpServletResponse. */
103 protected HttpServletResponse servletResponse;
105 /** The SAML 2.0 AuthnRequest. */
106 protected AuthnRequest authnRequest;
109 protected String issuer;
112 protected Subject subject;
115 protected Response response;
117 /** The IdP's LoginContext. */
118 protected LoginContext loginContext;
120 /** The RelyingPartyConfiguration. */
121 protected RelyingPartyConfiguration rpConfig;
123 /** The SSOConfiguration. */
124 protected SSOConfiguration ssoConfig;
126 /** The SPSSOConfiguration. */
127 protected SPSSODescriptor spDescriptor;
129 /** The AssertionConsumerService endpoint. */
130 protected AssertionConsumerService assertionConsumerService;
132 public AuthenticationRequestContext() {
135 public ProfileRequest<ServletRequest> getProfileRequest() {
136 return profileRequest;
139 public void setProfileRequest(ProfileRequest<ServletRequest> profileRequest) {
140 this.profileRequest = profileRequest;
141 this.servletRequest = (HttpServletRequest) profileRequest.getRawRequest();
144 public ProfileResponse<ServletResponse> getProfileResponse() {
145 return profileResponse;
148 public void setProfileResponse(ProfileResponse<ServletResponse> profileResponse) {
149 this.profileResponse = profileResponse;
150 this.servletResponse = (HttpServletResponse) profileResponse.getRawResponse();
153 public HttpServletRequest getServletRequest() {
154 return servletRequest;
157 public void setServletRequest(HttpServletRequest servletRequest) {
158 this.servletRequest = servletRequest;
161 public HttpServletResponse getServletResponse() {
162 return servletResponse;
165 public void setServletResponse(HttpServletResponse servletResponse) {
166 this.servletResponse = servletResponse;
169 public HttpSession getHttpSession() {
171 if (getServletRequest() != null) {
172 return getServletRequest().getSession();
178 public AuthnRequest getAuthnRequest() {
182 public void setAuthnRequest(AuthnRequest authnRequest) {
183 this.authnRequest = authnRequest;
186 public String getIssuer() {
190 public void setIssuer(String issuer) {
191 this.issuer = issuer;
194 public Subject getSubject() {
198 public void setSubject(Subject subject) {
199 this.subject = subject;
202 public LoginContext getLoginContext() {
206 public void setLoginContext(LoginContext loginContext) {
207 this.loginContext = loginContext;
210 public RelyingPartyConfiguration getRpConfig() {
214 public void setRpConfig(RelyingPartyConfiguration rpConfig) {
215 this.rpConfig = rpConfig;
218 public SSOConfiguration getSsoConfig() {
222 public void setSsoConfig(SSOConfiguration ssoConfig) {
223 this.ssoConfig = ssoConfig;
226 public SPSSODescriptor getSpDescriptor() {
230 public void setSpDescriptor(SPSSODescriptor spDescriptor) {
231 this.spDescriptor = spDescriptor;
234 public Response getResponse() {
238 public void setResponse(Response response) {
239 this.response = response;
242 public AssertionConsumerService getAssertionConsumerService() {
243 return assertionConsumerService;
246 public void setAssertionConsumerService(AssertionConsumerService assertionConsumerService) {
247 this.assertionConsumerService = assertionConsumerService;
250 public boolean equals(Object obj) {
255 if (getClass() != obj.getClass()) {
259 final edu.internet2.middleware.shibboleth.idp.profile.saml2.AbstractAuthenticationRequest.AuthenticationRequestContext other = (edu.internet2.middleware.shibboleth.idp.profile.saml2.AbstractAuthenticationRequest.AuthenticationRequestContext) obj;
261 if (this.profileRequest != other.profileRequest
262 && (this.profileRequest == null || !this.profileRequest.equals(other.profileRequest))) {
266 if (this.profileResponse != other.profileResponse
267 && (this.profileResponse == null || !this.profileResponse.equals(other.profileResponse))) {
271 if (this.servletRequest != other.servletRequest
272 && (this.servletRequest == null || !this.servletRequest.equals(other.servletRequest))) {
276 if (this.servletResponse != other.servletResponse
277 && (this.servletResponse == null || !this.servletResponse.equals(other.servletResponse))) {
281 if (this.authnRequest != other.authnRequest
282 && (this.authnRequest == null || !this.authnRequest.equals(other.authnRequest))) {
286 if (this.issuer != other.issuer && (this.issuer == null || !this.issuer.equals(other.issuer))) {
290 if (this.subject != other.subject && (this.subject == null || !this.subject.equals(other.subject))) {
294 if (this.response != other.response && (this.response == null || !this.response.equals(other.response))) {
298 if (this.loginContext != other.loginContext
299 && (this.loginContext == null || !this.loginContext.equals(other.loginContext))) {
303 if (this.rpConfig != other.rpConfig && (this.rpConfig == null || !this.rpConfig.equals(other.rpConfig))) {
307 if (this.ssoConfig != other.ssoConfig
308 && (this.ssoConfig == null || !this.ssoConfig.equals(other.ssoConfig))) {
312 if (this.spDescriptor != other.spDescriptor
313 && (this.spDescriptor == null || !this.spDescriptor.equals(other.spDescriptor))) {
317 if (this.assertionConsumerService != other.assertionConsumerService
318 && (this.assertionConsumerService == null || !this.assertionConsumerService
319 .equals(other.assertionConsumerService))) {
326 public int hashCode() {
334 private static final Logger log = Logger.getLogger(AbstractAuthenticationRequest.class);
336 /** HttpSession key for the AuthenticationRequestContext. */
337 protected static final String REQUEST_CONTEXT_SESSION_KEY = "edu.internet2.middleware.shibboleth.idp.profile.saml2.AuthenticationRequestContext";
339 /** The path to the IdP's AuthenticationManager servlet */
340 protected String authnMgrURL;
342 /** AuthenticationManager to be used */
343 protected AuthenticationManager authnMgr;
345 /** A pool of XML parsers. */
346 protected ParserPool parserPool;
348 /** Builder for AuthnStatements. */
349 protected SAMLObjectBuilder<AuthnStatement> authnStatementBuilder;
351 /** Builder for AuthnContexts. */
352 protected SAMLObjectBuilder<AuthnContext> authnContextBuilder;
354 /** Builder for AuthnContextDeclRef's */
355 protected SAMLObjectBuilder<AuthnContextDeclRef> authnContextDeclRefBuilder;
357 /** Builder for AuthnContextClassRef's. */
358 protected SAMLObjectBuilder<AuthnContextClassRef> authnContextClassRefBuilder;
363 public AbstractAuthenticationRequest() {
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);
377 * Set the Authentication Mananger.
379 * @param authnManager The IdP's AuthenticationManager.
381 public void setAuthenticationManager(AuthenticationManager authnManager) {
382 this.authnMgr = authnMgr;
386 * Evaluate a SAML 2 AuthenticationRequest message.
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.
395 * @throws ProfileException On Error.
397 protected void evaluateRequest(final AuthenticationRequestContext requestContext) throws ProfileException {
399 Response samlResponse;
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();
408 LoginContext loginCtx = requestContext.getLoginContext();
409 if (loginCtx.getAuthenticationOK()) {
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));
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);
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(),
431 requestContext.setResponse(samlResponse);
435 * Build a SAML 2 Response element with basic fields populated.
437 * Failure handlers can send the returned response element to the RP. Success handlers should add the assertions
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.
445 * @return The populated Response object.
447 protected Response buildResponse(String inResponseTo, final DateTime issueInstant, String issuer,
448 final Status status) {
450 Response response = getResponseBuilder().buildObject();
452 Issuer i = getIssuerBuilder().buildObject();
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);
466 * Check if the user has already been authenticated.
468 * @param httpSession the user's HttpSession.
470 * @return <code>true</code> if the user has been authenticated. otherwise <code>false</code>
472 protected boolean hasUserAuthenticated(final HttpSession httpSession) {
474 // if the user has authenticated, their session will have a LoginContext
476 Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
477 return (o != null && o instanceof LoginContext);
481 * Check if the user has already been authenticated. If so, return the LoginContext. If not, redirect the user to
482 * the AuthenticationManager.
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.
489 * @throws ProfileException on error.
491 protected void authenticateUser(final AuthenticationRequestContext requestContext) throws ProfileException {
493 AuthnRequest authnRequest = requestContext.getAuthnRequest();
494 HttpSession httpSession = requestContext.getHttpSession();
495 HttpServletRequest servletRequest = requestContext.getServletRequest();
496 HttpServletResponse servletResponse = requestContext.getServletResponse();
498 // Forward the request to the AuthenticationManager.
499 // When the AuthenticationManager is done it will
500 // forward the request back to this servlet.
502 // push the AuthenticationRequestContext into the session so we have it
503 // for the return leg.
504 httpSession.setAttribute(REQUEST_CONTEXT_SESSION_KEY, requestContext);
506 Saml2LoginContext loginCtx = new Saml2LoginContext(authnRequest);
507 requestContext.setLoginContext(loginCtx);
508 loginCtx.setProfileHandlerURL(servletRequest.getRequestURI());
510 // the AuthenticationManager expects the LoginContext to be in the HttpSession.
511 httpSession.setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginCtx);
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);
527 * Build an AuthnStatement and add it to an Assertion.
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.
533 * @throws ProfileException On error.
535 protected void setAuthenticationStatement(Assertion assertion, final LoginContext loginContext,
536 final AuthnRequest authnRequest) throws ProfileException {
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");
547 AuthnStatement stmt = authnStatementBuilder.buildObject();
548 stmt.setAuthnInstant(loginContext.getAuthenticationInstant());
549 stmt.setAuthnContext(authnCtx);
551 // add the AuthnStatement to the Assertion
552 List<AuthnStatement> authnStatements = assertion.getAuthnStatements();
553 authnStatements.add(stmt);
557 * Create the AuthnContex object.
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.
562 * @param requestedAuthnCtx The RequestedAuthnContext from the Authentication Request.
563 * @param loginContext The processed LoginContext (it must contain the authn method).
565 * @return An AuthnCtx object on success or <code>null</code> on failure.
567 protected AuthnContext buildAuthnCtx(final RequestedAuthnContext requestedAuthnCtx, final LoginContext loginContext) {
569 // this method assumes that only one URI will match.
571 AuthnContext authnCtx = authnContextBuilder.buildObject();
572 String authnMethod = loginContext.getAuthenticationMethod();
574 List<AuthnContextClassRef> authnClasses = requestedAuthnCtx.getAuthnContextClassRefs();
575 List<AuthnContextDeclRef> authnDeclRefs = requestedAuthnCtx.getAuthnContextDeclRefs();
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);
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);
604 // no matches were found.
609 * Verify the AuthnRequest is well-formed.
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.
616 * @throws AuthenticationRequestException on error.
618 protected void verifyAuthnRequest(final AuthenticationRequestContext requestContext)
619 throws AuthenticationRequestException {
621 final AuthnRequest authnRequest = requestContext.getAuthnRequest();
622 String issuer = requestContext.getIssuer();
623 final RelyingPartyConfiguration relyingParty = requestContext.getRpConfig();
624 final HttpSession session = requestContext.getHttpSession();
626 Status failureStatus;
628 // Check if we are in scope to handle this AuthnRequest
629 checkScope(authnRequest, issuer);
631 // verify that the AssertionConsumerService url is valid.
632 verifyAssertionConsumerService(requestContext, getRelyingPartyConfigurationManager().getMetadataProvider());
634 // check for nameID constraints.
635 verifySubject(requestContext);
639 * Get and verify the Subject element.
641 * @param requestContext The context for the current request.
643 * @throws AuthenticationRequestException on error.
645 protected void verifySubject(final AuthenticationRequestContext requestContext)
646 throws AuthenticationRequestException {
648 final AuthnRequest authnRequest = requestContext.getAuthnRequest();
650 Status failureStatus;
652 Subject subject = authnRequest.getSubject();
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);
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",
672 requestContext.setSubject(subject);
678 * Ensure that metadata can be found for the SP that issued the AuthnRequest.
680 * If found, the request context is updated to reflect the appropriate entries.
682 * Before this method may be called, the request context must have an issuer set.
684 * @param requestContext The context for the current request.
686 * @throws AuthenticationRequestException On error.
688 protected void validateRequestAgainstMetadata(final AuthenticationRequestContext requestContext)
689 throws AuthenticationRequestException {
691 RelyingPartyConfiguration relyingParty = null;
692 SSOConfiguration ssoConfig = null;
693 SPSSODescriptor spDescriptor = null;
695 // check that we have metadata for the RP
696 relyingParty = getRelyingPartyConfigurationManager().getRelyingPartyConfiguration(requestContext.getIssuer());
698 ProfileConfiguration temp = relyingParty.getProfileConfigurations().get(SSOConfiguration.PROFILE_ID);
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);
706 ssoConfig = (SSOConfiguration) temp;
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);
715 Status failureStatus = buildStatus(StatusCode.REQUESTER_URI, null, "No metadata available for "
716 + relyingParty.getRelyingPartyId());
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);
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);
727 Status failureStatus = buildStatus(StatusCode.REQUESTER_URI, null, "No metadata available for "
728 + relyingParty.getRelyingPartyId());
730 throw new AuthenticationRequestException(
731 "SAML 2 Authentication Request: Unable to locate SPSSODescriptor for SP "
732 + requestContext.getIssuer() + " for protocol " + SAML20_PROTOCOL_URI, failureStatus);
735 // if all metadata was found, update the request context.
736 requestContext.setRpConfig(relyingParty);
737 requestContext.setSsoConfig(ssoConfig);
738 requestContext.setSpDescriptor(spDescriptor);
742 * Return the endpoint URL and protocol binding to use for the AuthnRequest.
744 * @param requestContext The context for the current request.
746 * @param metadata The appropriate Metadata.
748 * @throws AuthenticationRequestException On error.
750 protected void verifyAssertionConsumerService(final AuthenticationRequestContext requestContext,
752 final MetadataProvider metadata) throws AuthenticationRequestException {
754 Status failureStatus;
756 final AuthnRequest authnRequest = requestContext.getAuthnRequest();
757 String providerId = requestContext.getRpConfig().getRelyingPartyId();
759 // Either the AssertionConsumerServiceIndex must be present
760 // or AssertionConsumerServiceURL must be present.
762 Integer idx = authnRequest.getAssertionConsumerServiceIndex();
763 String acsURL = authnRequest.getAssertionConsumerServiceURL();
765 if (idx != null && acsURL != null) {
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,
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);
779 SPSSODescriptor spDescriptor;
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);
789 List<AssertionConsumerService> acsList = spDescriptor.getAssertionConsumerServices();
791 // if the ACS index is specified, retrieve it from the metadata
794 int i = idx.intValue();
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());
801 failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
802 "Illegal AssertionConsumerIndex specicifed (" + i + ") in SAML 2 AuthnRequest "
803 + authnRequest.getID());
805 throw new AuthenticationRequestException("Illegal AssertionConsumerIndex in AuthnRequest",
809 requestContext.setAssertionConsumerService(acsList.get(i));
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);
826 log.error("Error processing SAML 2 AuthnRequest message " + authnRequest.getID()
827 + ": Unable to validate AssertionConsumerServiceURL against metadata: " + acsURL + " for binding "
830 failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
831 "Unable to validate AssertionConsumerService against metadata.");
833 throw new AuthenticationRequestException(
834 "SAML 2 AuthenticationRequest: Unable to validate AssertionConsumerService against Metadata",
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>.
842 * @param authnRequest The {@link AuthnRequest} element to check.
843 * @param providerId The IdP's ProviderID.
845 * @throws AuthenticationRequestException on error.
847 protected void checkScope(final AuthnRequest authnRequest, String providerId) throws AuthenticationRequestException {
849 Status failureStatus;
851 List<String> idpEntries = new LinkedList<String>();
853 Scoping scoping = authnRequest.getScoping();
854 if (scoping == null) {
858 // process all of the explicitly listed idp provider ids
859 IDPList idpList = scoping.getIDPList();
860 if (idpList == null) {
864 List<IDPEntry> explicitIDPEntries = idpList.getIDPEntrys();
865 if (explicitIDPEntries != null) {
866 for (IDPEntry entry : explicitIDPEntries) {
867 String s = entry.getProviderID();
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();
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 + ")");
899 log.error("SAML 2 AuthnRequest " + authnRequest.getID() + " contains a Scoping element which "
900 + "does not contain a providerID registered with this IdP.");
902 failureStatus = buildStatus(StatusCode.RESPONDER_URI, StatusCode.NO_SUPPORTED_IDP_URI, null);
903 throw new AuthenticationRequestException("Unrecognized providerID in Scoping element", failureStatus);
907 * Retrieve an incomplete IDPlist.
909 * This only handles URL-based <GetComplete/> references.
911 * @param getComplete The (possibly <code>null</code>) <GetComplete/> element
913 * @return an {@link IDPList} or <code>null</code> if the uri can't be dereferenced.
915 protected IDPList getCompleteIDPList(final GetComplete getComplete) {
917 // XXX: enhance this method to cache the url and last-modified-header
919 if (getComplete == null) {
923 String uri = getComplete.getGetComplete();
928 IDPList idpList = null;
929 InputStream istream = null;
932 URL url = new URL(uri);
933 URLConnection conn = url.openConnection();
934 istream = conn.getInputStream();
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);
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);
952 if (istream != null) {
955 } catch (IOException ex) {
965 * Encode a SAML Response.
967 * @param binding The SAML protocol binding to use.
968 * @param requestContext The context for the request.
970 * @throws ProfileException On error.
972 protected void encodeResponse(String binding, final AuthenticationRequestContext requestContext)
973 throws ProfileException {
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();
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);
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);
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);