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;
36 import org.apache.log4j.Logger;
37 import org.joda.time.DateTime;
38 import org.opensaml.Configuration;
39 import org.opensaml.common.SAMLObjectBuilder;
40 import org.opensaml.common.binding.BindingException;
41 import org.opensaml.common.binding.encoding.MessageEncoder;
42 import org.opensaml.saml2.core.Assertion;
43 import org.opensaml.saml2.core.AuthnContext;
44 import org.opensaml.saml2.core.AuthnContextClassRef;
45 import org.opensaml.saml2.core.AuthnContextDeclRef;
46 import org.opensaml.saml2.core.AuthnRequest;
47 import org.opensaml.saml2.core.AuthnStatement;
48 import org.opensaml.saml2.core.GetComplete;
49 import org.opensaml.saml2.core.IDPEntry;
50 import org.opensaml.saml2.core.IDPList;
51 import org.opensaml.saml2.core.Issuer;
52 import org.opensaml.saml2.core.RequestedAuthnContext;
53 import org.opensaml.saml2.core.Response;
54 import org.opensaml.saml2.core.Scoping;
55 import org.opensaml.saml2.core.Status;
56 import org.opensaml.saml2.core.StatusCode;
57 import org.opensaml.saml2.core.Subject;
58 import org.opensaml.saml2.core.SubjectConfirmation;
59 import org.opensaml.saml2.metadata.AssertionConsumerService;
60 import org.opensaml.saml2.metadata.Endpoint;
61 import org.opensaml.saml2.metadata.RoleDescriptor;
62 import org.opensaml.saml2.metadata.SPSSODescriptor;
63 import org.opensaml.saml2.metadata.provider.MetadataProvider;
64 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
65 import org.opensaml.xml.io.Unmarshaller;
66 import org.opensaml.xml.io.UnmarshallingException;
67 import org.opensaml.xml.parse.BasicParserPool;
68 import org.opensaml.xml.parse.ParserPool;
69 import org.opensaml.xml.parse.XMLParserException;
70 import org.w3c.dom.Document;
71 import org.w3c.dom.Element;
73 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
74 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
75 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
76 import edu.internet2.middleware.shibboleth.common.relyingparty.ProfileConfiguration;
77 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
78 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AbstractSAML2ProfileConfiguration;
79 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.SSOConfiguration;
80 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationManager;
81 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
82 import edu.internet2.middleware.shibboleth.idp.authn.Saml2LoginContext;
85 * Abstract SAML 2.0 Authentication Request profile handler.
87 public abstract class AbstractAuthenticationRequest extends AbstractSAML2ProfileHandler {
91 * Represents the internal state of a SAML 2.0 Authentiation Request while it's being processed by the IdP.
93 protected class AuthenticationRequestContext {
95 /** The ProfileRequest. */
96 protected ProfileRequest<ServletRequest> profileRequest;
98 /** The ProfileResponse. */
99 protected ProfileResponse<ServletResponse> profileResponse;
101 /** The HttpServletRequest. */
102 protected HttpServletRequest servletRequest;
104 /** The HttpServletResponse. */
105 protected HttpServletResponse servletResponse;
107 /** The SAML 2.0 AuthnRequest. */
108 protected AuthnRequest authnRequest;
111 protected String issuer;
114 protected Subject subject;
117 protected Response response;
119 /** The IdP's LoginContext. */
120 protected LoginContext loginContext;
122 /** The RelyingPartyConfiguration. */
123 protected RelyingPartyConfiguration rpConfig;
125 /** The SSOConfiguration. */
126 protected SSOConfiguration ssoConfig;
128 /** The SPSSOConfiguration. */
129 protected SPSSODescriptor spDescriptor;
131 /** The AssertionConsumerService endpoint. */
132 protected AssertionConsumerService assertionConsumerService;
134 public AuthenticationRequestContext() {
137 public ProfileRequest<ServletRequest> getProfileRequest() {
138 return profileRequest;
141 public void setProfileRequest(ProfileRequest<ServletRequest> profileRequest) {
142 this.profileRequest = profileRequest;
143 this.servletRequest = (HttpServletRequest) profileRequest.getRawRequest();
146 public ProfileResponse<ServletResponse> getProfileResponse() {
147 return profileResponse;
150 public void setProfileResponse(ProfileResponse<ServletResponse> profileResponse) {
151 this.profileResponse = profileResponse;
152 this.servletResponse = (HttpServletResponse) profileResponse.getRawResponse();
155 public HttpServletRequest getServletRequest() {
156 return servletRequest;
159 public void setServletRequest(HttpServletRequest servletRequest) {
160 this.servletRequest = servletRequest;
163 public HttpServletResponse getServletResponse() {
164 return servletResponse;
167 public void setServletResponse(HttpServletResponse servletResponse) {
168 this.servletResponse = servletResponse;
171 public HttpSession getHttpSession() {
173 if (getServletRequest() != null) {
174 return getServletRequest().getSession();
180 public AuthnRequest getAuthnRequest() {
184 public void setAuthnRequest(AuthnRequest authnRequest) {
185 this.authnRequest = authnRequest;
188 public String getIssuer() {
192 public void setIssuer(String issuer) {
193 this.issuer = issuer;
196 public Subject getSubject() {
200 public void setSubject(Subject subject) {
201 this.subject = subject;
204 public LoginContext getLoginContext() {
208 public void setLoginContext(LoginContext loginContext) {
209 this.loginContext = loginContext;
212 public RelyingPartyConfiguration getRpConfig() {
216 public void setRpConfig(RelyingPartyConfiguration rpConfig) {
217 this.rpConfig = rpConfig;
220 public SSOConfiguration getSsoConfig() {
224 public void setSsoConfig(SSOConfiguration ssoConfig) {
225 this.ssoConfig = ssoConfig;
228 public SPSSODescriptor getSpDescriptor() {
232 public void setSpDescriptor(SPSSODescriptor spDescriptor) {
233 this.spDescriptor = spDescriptor;
236 public Response getResponse() {
240 public void setResponse(Response response) {
241 this.response = response;
244 public AssertionConsumerService getAssertionConsumerService() {
245 return assertionConsumerService;
248 public void setAssertionConsumerService(AssertionConsumerService assertionConsumerService) {
249 this.assertionConsumerService = assertionConsumerService;
252 public boolean equals(Object obj) {
257 if (getClass() != obj.getClass()) {
261 final edu.internet2.middleware.shibboleth.idp.profile.saml2.AbstractAuthenticationRequest.AuthenticationRequestContext other = (edu.internet2.middleware.shibboleth.idp.profile.saml2.AbstractAuthenticationRequest.AuthenticationRequestContext) obj;
263 if (this.profileRequest != other.profileRequest && (this.profileRequest == null || !this.profileRequest.equals(other.profileRequest))) {
267 if (this.profileResponse != other.profileResponse && (this.profileResponse == null || !this.profileResponse.equals(other.profileResponse))) {
271 if (this.servletRequest != other.servletRequest && (this.servletRequest == null || !this.servletRequest.equals(other.servletRequest))) {
275 if (this.servletResponse != other.servletResponse && (this.servletResponse == null || !this.servletResponse.equals(other.servletResponse))) {
279 if (this.authnRequest != other.authnRequest && (this.authnRequest == null || !this.authnRequest.equals(other.authnRequest))) {
283 if (this.issuer != other.issuer && (this.issuer == null || !this.issuer.equals(other.issuer))) {
287 if (this.subject != other.subject && (this.subject == null || !this.subject.equals(other.subject))) {
291 if (this.response != other.response && (this.response == null || !this.response.equals(other.response))) {
295 if (this.loginContext != other.loginContext && (this.loginContext == null || !this.loginContext.equals(other.loginContext))) {
299 if (this.rpConfig != other.rpConfig && (this.rpConfig == null || !this.rpConfig.equals(other.rpConfig))) {
303 if (this.ssoConfig != other.ssoConfig && (this.ssoConfig == null || !this.ssoConfig.equals(other.ssoConfig))) {
307 if (this.spDescriptor != other.spDescriptor && (this.spDescriptor == null || !this.spDescriptor.equals(other.spDescriptor))) {
311 if (this.assertionConsumerService != other.assertionConsumerService && (this.assertionConsumerService == null || !this.assertionConsumerService.equals(other.assertionConsumerService))) {
318 public int hashCode() {
328 private static final Logger log = Logger.getLogger(AbstractAuthenticationRequest.class);
330 /** HttpSession key for the AuthenticationRequestContext. */
331 protected static final String REQUEST_CONTEXT_SESSION_KEY = "edu.internet2.middleware.shibboleth.idp.profile.saml2.AuthenticationRequestContext";
333 /** The path to the IdP's AuthenticationManager servlet */
334 protected String authnMgrURL;
336 /** AuthenticationManager to be used */
337 protected AuthenticationManager authnMgr;
339 /** A pool of XML parsers. */
340 protected ParserPool parserPool;
342 /** Builder for AuthnStatements. */
343 protected SAMLObjectBuilder<AuthnStatement> authnStatementBuilder;
345 /** Builder for AuthnContexts. */
346 protected SAMLObjectBuilder<AuthnContext> authnContextBuilder;
348 /** Builder for AuthnContextDeclRef's */
349 protected SAMLObjectBuilder<AuthnContextDeclRef> authnContextDeclRefBuilder;
351 /** Builder for AuthnContextClassRef's. */
352 protected SAMLObjectBuilder<AuthnContextClassRef> authnContextClassRefBuilder;
357 public AbstractAuthenticationRequest() {
359 parserPool = new BasicParserPool();
360 authnStatementBuilder = (SAMLObjectBuilder<AuthnStatement>) getBuilderFactory().getBuilder(AuthnStatement.DEFAULT_ELEMENT_NAME);
361 authnContextBuilder = (SAMLObjectBuilder<AuthnContext>) getBuilderFactory().getBuilder(AuthnContext.DEFAULT_ELEMENT_NAME);
362 authnContextDeclRefBuilder = (SAMLObjectBuilder<AuthnContextDeclRef>) getBuilderFactory().getBuilder(AuthnContextDeclRef.DEFAULT_ELEMENT_NAME);
363 authnContextClassRefBuilder = (SAMLObjectBuilder<AuthnContextClassRef>) getBuilderFactory().getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
367 * Set the Authentication Mananger.
369 * @param authnManager
370 * The IdP's AuthenticationManager.
372 public void setAuthenticationManager(AuthenticationManager authnManager) {
373 this.authnMgr = authnMgr;
377 * Evaluate a SAML 2 AuthenticationRequest message.
379 * @param authnRequest
380 * A SAML 2 AuthenticationRequest
382 * The issuer of the authnRequest.
384 * The HttpSession of the request.
385 * @param relyingParty
386 * The RelyingPartyConfiguration for the request.
388 * The SSOConfiguration for the request.
389 * @param spDescriptor
390 * The SPSSODescriptor for the request.
392 * @throws ProfileException
395 protected void evaluateRequest(final AuthenticationRequestContext requestContext) throws ProfileException {
397 Response samlResponse;
399 final AuthnRequest authnRequest = requestContext.getAuthnRequest();
400 String issuer = requestContext.getIssuer();
401 final HttpSession session = requestContext.getHttpSession();
402 final RelyingPartyConfiguration relyingParty = requestContext.getRpConfig();
403 final SSOConfiguration ssoConfig = requestContext.getSsoConfig();
404 final SPSSODescriptor spDescriptor = requestContext.getSpDescriptor();
406 LoginContext loginCtx = requestContext.getLoginContext();
407 if (loginCtx.getAuthenticationOK()) {
409 // the user successfully authenticated.
410 // build an authentication assertion.
411 samlResponse = buildResponse(authnRequest.getID(), new DateTime(),
412 relyingParty.getProviderId(), buildStatus(StatusCode.SUCCESS_URI, null, null));
414 DateTime now = new DateTime();
415 Assertion assertion = buildAssertion(now, relyingParty, (AbstractSAML2ProfileConfiguration) ssoConfig);
416 assertion.setSubject(requestContext.getSubject());
417 setAuthenticationStatement(assertion, loginCtx, authnRequest);
418 samlResponse.getAssertions().add(assertion);
422 // if authentication failed, encode the appropriate SAML error message.
423 String failureMessage = loginCtx.getAuthenticationFailureMessage();
424 Status failureStatus = buildStatus(StatusCode.RESPONDER_URI, StatusCode.AUTHN_FAILED_URI, failureMessage);
425 samlResponse = buildResponse(authnRequest.getID(), new DateTime(), relyingParty.getProviderId(),
429 requestContext.setResponse(samlResponse);
433 * Build a SAML 2 Response element with basic fields populated.
435 * Failure handlers can send the returned response element to the RP.
436 * Success handlers should add the assertions before sending it.
438 * @param inResponseTo
439 * The ID of the request this is in response to.
440 * @param issueInstant
441 * The timestamp of this response.
443 * The URI of the RP issuing the response.
445 * The response's status code.
447 * @return The populated Response object.
449 protected Response buildResponse(String inResponseTo,
450 final DateTime issueInstant, String issuer, final Status status) {
452 Response response = getResponseBuilder().buildObject();
454 Issuer i = getIssuerBuilder().buildObject();
457 response.setVersion(SAML_VERSION);
458 response.setID(getIdGenerator().generateIdentifier());
459 response.setInResponseTo(inResponseTo);
460 response.setIssueInstant(issueInstant);
461 response.setIssuer(i);
462 response.setStatus(status);
468 * Check if the user has already been authenticated.
471 * the user's HttpSession.
473 * @return <code>true</code> if the user has been authenticated. otherwise
476 protected boolean hasUserAuthenticated(final HttpSession httpSession) {
478 // if the user has authenticated, their session will have a LoginContext
480 Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
481 return (o != null && o instanceof LoginContext);
485 * Check if the user has already been authenticated. If so, return the
486 * LoginContext. If not, redirect the user to the AuthenticationManager.
488 * @param authnRequest
489 * The SAML 2 AuthnRequest.
491 * The user's HttpSession.
493 * The user's HttpServletRequest.
495 * The user's HttpServletResponse.
497 * @throws ProfileException
500 protected void authenticateUser(final AuthenticationRequestContext requestContext) throws ProfileException {
502 AuthnRequest authnRequest = requestContext.getAuthnRequest();
503 HttpSession httpSession = requestContext.getHttpSession();
504 HttpServletRequest servletRequest = requestContext.getServletRequest();
505 HttpServletResponse servletResponse = requestContext.getServletResponse();
507 // Forward the request to the AuthenticationManager.
508 // When the AuthenticationManager is done it will
509 // forward the request back to this servlet.
511 // push the AuthenticationRequestContext into the session so we have it
512 // for the return leg.
513 httpSession.setAttribute(REQUEST_CONTEXT_SESSION_KEY, requestContext);
515 Saml2LoginContext loginCtx = new Saml2LoginContext(authnRequest);
516 requestContext.setLoginContext(loginCtx);
517 loginCtx.setProfileHandlerURL(servletRequest.getRequestURI());
519 // the AuthenticationManager expects the LoginContext to be in the HttpSession.
520 httpSession.setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginCtx);
522 RequestDispatcher dispatcher = servletRequest
523 .getRequestDispatcher(authnMgrURL);
524 dispatcher.forward(servletRequest,servletResponse);
525 } catch (IOException ex) {
526 log.error("Error forwarding SAML 2 AuthnRequest "
527 + authnRequest.getID() + " to AuthenticationManager", ex);
528 throw new ProfileException("Error forwarding SAML 2 AuthnRequest "
529 + authnRequest.getID() + " to AuthenticationManager", ex);
530 } catch (ServletException ex) {
531 log.error("Error forwarding SAML 2 AuthnRequest "
532 + authnRequest.getID() + " to AuthenticationManager", ex);
533 throw new ProfileException("Error forwarding SAML 2 AuthnRequest "
534 + authnRequest.getID() + " to AuthenticationManager", ex);
539 * Build an AuthnStatement and add it to an Assertion.
541 * @param assertion An empty SAML 2 Assertion object.
542 * @param loginContext The processed login context for the AuthnRequest.
543 * @param authnRequest The AuthnRequest to which this is in response.
545 * @throws ProfileException On error.
547 protected void setAuthenticationStatement(Assertion assertion,
548 final LoginContext loginContext,
549 final AuthnRequest authnRequest) throws ProfileException {
551 // Build the AuthnCtx.
552 // We need to determine if the user was authenticated
553 // with an AuthnContextClassRef or a AuthnContextDeclRef
554 AuthnContext authnCtx = buildAuthnCtx(authnRequest.getRequestedAuthnContext(), loginContext);
555 if (authnCtx == null) {
556 log.error("Error respond to SAML 2 AuthnRequest "
557 + authnRequest.getID()
558 + " : Unable to determine authentication method");
561 AuthnStatement stmt = authnStatementBuilder.buildObject();
562 stmt.setAuthnInstant(loginContext.getAuthenticationInstant());
563 stmt.setAuthnContext(authnCtx);
565 // add the AuthnStatement to the Assertion
566 List<AuthnStatement> authnStatements = assertion.getAuthnStatements();
567 authnStatements.add(stmt);
571 * Create the AuthnContex object.
573 * To do this, we have to walk the AuthnRequest's RequestedAuthnContext
574 * object and compare any values we find to what's set in the loginContext.
576 * @param requestedAuthnCtx
577 * The RequestedAuthnContext from the Authentication Request.
578 * @param loginContext
579 * The processed LoginContext (it must contain the authn method).
581 * @return An AuthnCtx object on success or <code>null</code> on failure.
583 protected AuthnContext buildAuthnCtx(
584 final RequestedAuthnContext requestedAuthnCtx,
585 final LoginContext loginContext) {
587 // this method assumes that only one URI will match.
589 AuthnContext authnCtx = authnContextBuilder.buildObject();
590 String authnMethod = loginContext.getAuthenticationMethod();
592 List<AuthnContextClassRef> authnClasses = requestedAuthnCtx
593 .getAuthnContextClassRefs();
594 List<AuthnContextDeclRef> authnDeclRefs = requestedAuthnCtx
595 .getAuthnContextDeclRefs();
597 if (authnClasses != null) {
598 for (AuthnContextClassRef classRef : authnClasses) {
599 if (classRef != null) {
600 String s = classRef.getAuthnContextClassRef();
601 if (s != null && authnMethod.equals(s)) {
602 AuthnContextClassRef ref = authnContextClassRefBuilder
604 authnCtx.setAuthnContextClassRef(ref);
611 // if no AuthnContextClassRef's matched, try the DeclRefs
612 if (authnDeclRefs != null) {
613 for (AuthnContextDeclRef declRef : authnDeclRefs) {
614 if (declRef != null) {
615 String s = declRef.getAuthnContextDeclRef();
616 if (s != null && authnMethod.equals((s))) {
617 AuthnContextDeclRef ref = authnContextDeclRefBuilder
619 authnCtx.setAuthnContextDeclRef(ref);
626 // no matches were found.
631 * Verify the AuthnRequest is well-formed.
633 * @param authnRequest
634 * The user's SAML 2 AuthnRequest.
636 * The Issuer of the AuthnRequest.
637 * @param relyingParty
638 * The relying party configuration for the request's originator.
640 * The user's HttpSession.
642 * @throws AuthenticationRequestException
645 protected void verifyAuthnRequest(final AuthenticationRequestContext requestContext) throws AuthenticationRequestException {
647 final AuthnRequest authnRequest = requestContext.getAuthnRequest();
648 String issuer = requestContext.getIssuer();
649 final RelyingPartyConfiguration relyingParty = requestContext.getRpConfig();
650 final HttpSession session = requestContext.getHttpSession();
652 Status failureStatus;
654 // Check if we are in scope to handle this AuthnRequest
655 checkScope(authnRequest, issuer);
657 // verify that the AssertionConsumerService url is valid.
658 verifyAssertionConsumerService(requestContext,
659 getRelyingPartyConfigurationManager().getMetadataProvider());
661 // check for nameID constraints.
662 verifySubject(requestContext);
666 * Get and verify the Subject element.
668 * @param requestContext The context for the current request.
670 * @throws AuthenticationRequestException
673 protected void verifySubject(final AuthenticationRequestContext requestContext)
674 throws AuthenticationRequestException {
676 final AuthnRequest authnRequest = requestContext.getAuthnRequest();
678 Status failureStatus;
680 Subject subject = authnRequest.getSubject();
682 if (subject == null) {
683 failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
684 "SAML 2 AuthnRequest " + authnRequest.getID()
685 + " is malformed: It does not contain a Subject.");
686 throw new AuthenticationRequestException(
687 "AuthnRequest lacks a Subject", failureStatus);
690 // The Web Browser SSO profile disallows SubjectConfirmation
691 // methods in the requested subject.
692 List<SubjectConfirmation> confMethods = subject
693 .getSubjectConfirmations();
694 if (confMethods != null || confMethods.size() > 0) {
696 .error("SAML 2 AuthnRequest "
697 + authnRequest.getID()
698 + " is malformed: It contains SubjectConfirmation elements.");
699 failureStatus = buildStatus(
700 StatusCode.REQUESTER_URI,
702 "SAML 2 AuthnRequest "
703 + authnRequest.getID()
704 + " is malformed: It contains SubjectConfirmation elements.");
705 throw new AuthenticationRequestException(
706 "AuthnRequest contains SubjectConfirmation elements",
710 requestContext.setSubject(subject);
716 * Ensure that metadata can be found for the SP that issued
719 * If found, the request context is updated to reflect the appropriate entries.
721 * Before this method may be called, the request context must have an issuer set.
723 * @param requestContext The context for the current request.
725 * @throws AuthenticationRequestException On error.
727 protected void validateRequestAgainstMetadata(final AuthenticationRequestContext requestContext) throws AuthenticationRequestException {
729 RelyingPartyConfiguration relyingParty = null;
730 SSOConfiguration ssoConfig = null;
731 SPSSODescriptor spDescriptor = null;
733 // check that we have metadata for the RP
734 relyingParty = getRelyingPartyConfigurationManager().getRelyingPartyConfiguration(requestContext.getIssuer());
736 ProfileConfiguration temp = relyingParty.getProfileConfigurations().get(SSOConfiguration.PROFILE_ID);
738 log.error("SAML 2 Authentication Request: No profile configuration registered for " + SSOConfiguration.PROFILE_ID);
739 throw new AuthenticationRequestException("No profile configuration registered for " + SSOConfiguration.PROFILE_ID);
742 ssoConfig = (SSOConfiguration) temp;
745 spDescriptor = getMetadataProvider().getEntityDescriptor(
746 relyingParty.getRelyingPartyId()).getSPSSODescriptor(
747 SAML20_PROTOCOL_URI);
748 } catch (MetadataProviderException ex) {
750 "SAML 2 Authentication Request: Unable to locate SPSSODescriptor for SP "
751 + requestContext.getIssuer() + " for protocol " + SAML20_PROTOCOL_URI, ex);
753 Status failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
754 "No metadata available for " + relyingParty.getRelyingPartyId());
756 throw new AuthenticationRequestException("SAML 2 Authentication Request: Unable to locate SPSSODescriptor for SP "
757 + requestContext.getIssuer() + " for protocol " + SAML20_PROTOCOL_URI, ex, failureStatus);
760 if (spDescriptor == null) {
761 log.error("SAML 2 Authentication Request: Unable to locate SPSSODescriptor for SP "
762 + requestContext.getIssuer() + " for protocol " + SAML20_PROTOCOL_URI);
764 Status failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
765 "No metadata available for " + relyingParty.getRelyingPartyId());
767 throw new AuthenticationRequestException("SAML 2 Authentication Request: Unable to locate SPSSODescriptor for SP "
768 + requestContext.getIssuer() + " for protocol " + SAML20_PROTOCOL_URI, failureStatus);
771 // if all metadata was found, update the request context.
772 requestContext.setRpConfig(relyingParty);
773 requestContext.setSsoConfig(ssoConfig);
774 requestContext.setSpDescriptor(spDescriptor);
778 * Return the endpoint URL and protocol binding to use for the AuthnRequest.
780 * @param requestContext The context for the current request.
783 * The appropriate Metadata.
785 * @throws AuthenticationRequestException
788 protected void verifyAssertionConsumerService(
789 final AuthenticationRequestContext requestContext,
791 final MetadataProvider metadata)
792 throws AuthenticationRequestException {
794 Status failureStatus;
796 final AuthnRequest authnRequest = requestContext.getAuthnRequest();
797 String providerId = requestContext.getRpConfig().getRelyingPartyId();
799 // Either the AssertionConsumerServiceIndex must be present
800 // or AssertionConsumerServiceURL must be present.
802 Integer idx = authnRequest.getAssertionConsumerServiceIndex();
803 String acsURL = authnRequest.getAssertionConsumerServiceURL();
805 if (idx != null && acsURL != null) {
807 .error("SAML 2 AuthnRequest "
808 + authnRequest.getID()
809 + " is malformed: It contains both an AssertionConsumerServiceIndex and an AssertionConsumerServiceURL");
810 failureStatus = buildStatus(
811 StatusCode.REQUESTER_URI,
813 "SAML 2 AuthnRequest "
814 + authnRequest.getID()
815 + " is malformed: It contains both an AssertionConsumerServiceIndex and an AssertionConsumerServiceURL");
816 throw new AuthenticationRequestException("Malformed AuthnRequest",
820 SPSSODescriptor spDescriptor;
822 spDescriptor = metadata.getEntityDescriptor(providerId)
823 .getSPSSODescriptor(SAML20_PROTOCOL_URI);
824 } catch (MetadataProviderException ex) {
826 "Unable retrieve SPSSODescriptor metadata for providerId "
828 + " while processing SAML 2 AuthnRequest "
829 + authnRequest.getID(), ex);
830 failureStatus = buildStatus(StatusCode.RESPONDER_URI, null,
831 "Unable to locate metadata for " + providerId);
832 throw new AuthenticationRequestException(
833 "Unable to locate metadata", ex, failureStatus);
836 List<AssertionConsumerService> acsList = spDescriptor
837 .getAssertionConsumerServices();
839 // if the ACS index is specified, retrieve it from the metadata
842 int i = idx.intValue();
844 // if the index is out of range, return an appropriate error.
845 if (i > acsList.size()) {
846 log.error("Illegal AssertionConsumerIndex specicifed (" + i
847 + ") in SAML 2 AuthnRequest " + authnRequest.getID());
849 failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
850 "Illegal AssertionConsumerIndex specicifed (" + i
851 + ") in SAML 2 AuthnRequest "
852 + authnRequest.getID());
854 throw new AuthenticationRequestException(
855 "Illegal AssertionConsumerIndex in AuthnRequest",
859 requestContext.setAssertionConsumerService(acsList.get(i));
863 // if the ACS endpoint is specified, validate it against the metadata
864 String protocolBinding = authnRequest.getProtocolBinding();
865 for (AssertionConsumerService acs : acsList) {
866 if (acsURL.equals(acs.getLocation())) {
867 if (protocolBinding != null) {
868 if (protocolBinding.equals(acs.getBinding())) {
869 requestContext.setAssertionConsumerService(acs);
877 .error("Error processing SAML 2 AuthnRequest message "
878 + authnRequest.getID()
879 + ": Unable to validate AssertionConsumerServiceURL against metadata: "
880 + acsURL + " for binding " + protocolBinding);
882 failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
883 "Unable to validate AssertionConsumerService against metadata.");
885 throw new AuthenticationRequestException(
886 "SAML 2 AuthenticationRequest: Unable to validate AssertionConsumerService against Metadata",
891 * Check if an {@link AuthnRequest} contains a {@link Scoping} element. If
892 * so, check if the specified IdP is in the {@link IDPList} element. If no
893 * Scoping element is present, this method returns <code>true</code>.
895 * @param authnRequest
896 * The {@link AuthnRequest} element to check.
898 * The IdP's ProviderID.
900 * @throws AuthenticationRequestException
903 protected void checkScope(final AuthnRequest authnRequest, String providerId)
904 throws AuthenticationRequestException {
906 Status failureStatus;
908 List<String> idpEntries = new LinkedList<String>();
910 Scoping scoping = authnRequest.getScoping();
911 if (scoping == null) {
915 // process all of the explicitly listed idp provider ids
916 IDPList idpList = scoping.getIDPList();
917 if (idpList == null) {
921 List<IDPEntry> explicitIDPEntries = idpList.getIDPEntrys();
922 if (explicitIDPEntries != null) {
923 for (IDPEntry entry : explicitIDPEntries) {
924 String s = entry.getProviderID();
931 // If the IDPList is incomplete, retrieve the complete list
932 // and add the entries to idpEntries.
933 GetComplete getComplete = idpList.getGetComplete();
934 IDPList referencedIdPs = getCompleteIDPList(getComplete);
935 if (referencedIdPs != null) {
936 List<IDPEntry> referencedIDPEntries = referencedIdPs.getIDPEntrys();
937 if (referencedIDPEntries != null) {
938 for (IDPEntry entry : referencedIDPEntries) {
939 String s = entry.getProviderID();
947 // iterate over all the IDPEntries we've gathered,
948 // and check if we're in scope.
949 for (String requestProviderId : idpEntries) {
950 if (providerId.equals(requestProviderId)) {
951 log.debug("Found Scoping match for IdP: (" + providerId + ")");
956 log.error("SAML 2 AuthnRequest " + authnRequest.getID()
957 + " contains a Scoping element which "
958 + "does not contain a providerID registered with this IdP.");
960 failureStatus = buildStatus(StatusCode.RESPONDER_URI,
961 StatusCode.NO_SUPPORTED_IDP_URI, null);
962 throw new AuthenticationRequestException(
963 "Unrecognized providerID in Scoping element", failureStatus);
967 * Retrieve an incomplete IDPlist.
969 * This only handles URL-based <GetComplete/> references.
972 * The (possibly <code>null</code>) <GetComplete/>
975 * @return an {@link IDPList} or <code>null</code> if the uri can't be
978 protected IDPList getCompleteIDPList(final GetComplete getComplete) {
980 // XXX: enhance this method to cache the url and last-modified-header
982 if (getComplete == null) {
986 String uri = getComplete.getGetComplete();
991 IDPList idpList = null;
992 InputStream istream = null;
995 URL url = new URL(uri);
996 URLConnection conn = url.openConnection();
997 istream = conn.getInputStream();
999 // convert the raw data into an XML object
1000 Document doc = parserPool.parse(istream);
1001 Element docElement = doc.getDocumentElement();
1002 Unmarshaller unmarshaller = Configuration.getUnmarshallerFactory()
1003 .getUnmarshaller(docElement);
1004 idpList = (IDPList) unmarshaller.unmarshall(docElement);
1006 } catch (MalformedURLException ex) {
1008 "Unable to retrieve GetComplete IDPList. Unsupported URI: "
1010 } catch (IOException ex) {
1011 log.error("IO Error while retreieving GetComplete IDPList from "
1013 } catch (XMLParserException ex) {
1015 "Internal OpenSAML error while parsing GetComplete IDPList from "
1017 } catch (UnmarshallingException ex) {
1019 "Internal OpenSAML error while unmarshalling GetComplete IDPList from "
1023 if (istream != null) {
1026 } catch (IOException ex) {
1036 * Encode a SAML Response.
1038 * @param binding The SAML protocol binding to use.
1039 * @param requestContext The context for the request.
1041 * @throws ProfileException On error.
1043 protected void encodeResponse(String binding,
1044 final AuthenticationRequestContext requestContext) throws ProfileException {
1046 final ProfileResponse<ServletResponse> profileResponse = requestContext.getProfileResponse();
1047 final Response samlResponse = requestContext.getResponse();
1048 final RelyingPartyConfiguration relyingParty = requestContext.getRpConfig();
1049 final RoleDescriptor roleDescriptor = requestContext.getSpDescriptor();
1050 final Endpoint endpoint = requestContext.getAssertionConsumerService();
1053 MessageEncoder<ServletResponse> encoder = getMessageEncoderFactory().getMessageEncoder(binding);
1054 if (encoder == null) {
1055 log.error("No MessageEncoder registered for " + binding);
1056 throw new ProfileException("No MessageEncoder registered for " + binding);
1059 encoder.setResponse(profileResponse.getRawResponse());
1060 encoder.setIssuer(relyingParty.getProviderId());
1061 encoder.setMetadataProvider(getRelyingPartyConfigurationManager().getMetadataProvider());
1062 encoder.setRelyingPartyRole(roleDescriptor);
1063 encoder.setSigningCredential(relyingParty.getDefaultSigningCredential());
1064 encoder.setSamlMessage(samlResponse);
1065 encoder.setRelyingPartyEndpoint(endpoint);
1069 } catch (BindingException ex) {
1070 log.error("Unable to encode response the relying party: " + relyingParty.getRelyingPartyId(), ex);
1071 throw new ProfileException("Unable to encode response the relying party: "
1072 + relyingParty.getRelyingPartyId(), ex);