compile fixes for saml2 authnreq
authordmorr <dmorr@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Mon, 7 May 2007 15:05:46 +0000 (15:05 +0000)
committerdmorr <dmorr@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Mon, 7 May 2007 15:05:46 +0000 (15:05 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@2190 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AbstractAuthenticationRequest.java
src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AbstractSAML2ProfileHandler.java
src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AuthenticationRequestBrowserPost.java

index 266eea5..01f6d3c 100644 (file)
@@ -72,882 +72,911 @@ import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfigurationManager;
+import edu.internet2.middleware.shibboleth.common.relyingparty.saml2.AbstractSAML2ProfileConfiguration;
 import edu.internet2.middleware.shibboleth.common.relyingparty.saml2.SSOConfiguration;
 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationManager;
 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
 import edu.internet2.middleware.shibboleth.idp.authn.Saml2LoginContext;
 
+
 /**
  * Abstract SAML 2.0 Authentication Request profile handler.
  */
-public abstract class AbstractAuthenticationRequest extends
-               AbstractSAML2ProfileHandler {
-
-       /** Class logger. */
-       private static final Logger log = Logger
-                       .getLogger(AbstractAuthenticationRequest.class.getName());
-
-       /** Key in an HttpSession for the AssertionConsumerService object. */
-       protected static final String ACS_SESSION_KEY = "AssertionConsumerService";
-
-       /** Key in an HttpSession for the RelyingParty. */
-       protected static final String RPCONFIG_SESSION_KEY = "RelyingPartyConfiguration";
-
-       /** Key in an HttpSession for the SSOConfiguration. */
-       protected static final String SSOCONFIG_SESSION_KEY = "SSOConfiguration";
-
-       /** Key in an HttpSession for the SPSSODescriptor. */
-       protected static final String SPSSODESC_SESSION_KEY = "SPSSODescriptor";
-
-       /** Key in an HttpSession for the AuthnRequest. */
-       protected static final String AUTHNREQUEST_SESSION_KEY = "AuthnRequest";
-
-       /** Key in an HttpSession for the Issuer. */
-       protected static final String ISSUER_SESSION_KEY = "Issuer";
-
-       /**
-        * Backing store for artifacts. This must be shared between ShibbolethSSO
-        * and AttributeQuery.
-        */
-       protected ArtifactMap artifactMap;
-
-       /** The path to the IdP's AuthenticationManager servlet */
-       protected String authnMgrURL;
-
-       /** AuthenticationManager to be used */
-       protected AuthenticationManager authnMgr;
-
-       /** ArtifactFactory used to make artifacts. */
-       protected SAMLArtifactFactory artifactFactory;
-
-       /** A pool of XML parsers. */
-       protected ParserPool parserPool;
-
-       /**
-        * Constructor.
-        */
-       public AbstractAuthenticationRequest() {
-
-               parserPool = new BasicParserPool();
-               artifactFactory = new SAMLArtifactFactory();
-       }
-
-       /**
-        * Set the Authentication Mananger.
-        * 
-        * @param authnManager
-        *            The IdP's AuthenticationManager.
-        */
-       public void setAuthenticationManager(AuthenticationManager authnManager) {
-               this.authnMgr = authnMgr;
-       }
-
-       /**
-        * Set the ArtifactMap.
-        * 
-        * @param artifactMap
-        *            The IdP's ArtifactMap.
-        */
-       public void setArtifactMap(ArtifactMap artifactMap) {
-               this.artifactMap = artifactMap;
-       }
-
-       /**
-        * Evaluate a SAML 2 AuthenticationRequest message.
-        * 
-        * @param authnRequest
-        *            A SAML 2 AuthenticationRequest
-        * @param issuer
-        *            The issuer of the authnRequest.
-        * @param session
-        *            The HttpSession of the request.
-        * @param relyingParty
-        *            The RelyingPartyConfiguration for the request.
-        * @param ssoConfig
-        *            The SSOConfiguration for the request.
-        * @param spDescriptor
-        *            The SPSSODescriptor for the request.
-        * 
-        * @return A Response containing a failure message or a AuthenticationStmt.
-        * 
-        * @throws ServletException
-        *             On Error.
-        */
-       protected Response evaluateRequest(final AuthnRequest authnRequest,
-                       final Issuer issuer, final HttpSession session,
-                       final RelyingPartyConfiguration relyingParty,
-                       final SSOConfiguration ssoConfig, final SPSSODescriptor spDescriptor)
-                       throws ServletException {
-
-               Response samlResponse;
-
-               try {
-                       // check if the authentication was successful.
-                       Saml2LoginContext loginCtx = getLoginContext(session);
-                       if (!loginCtx.getAuthenticationOK()) {
-                               // if authentication failed, send the appropriate SAML error
-                               // message.
-                               String failureMessage = loginCtx
-                                               .getAuthenticationFailureMessage();
-                               Status failureStatus = buildStatus(StatusCode.RESPONDER_URI,
-                                               StatusCode.AUTHN_FAILED_URI, failureMessage);
-                               samlResponse = buildResponse(authnRequest.getID(),
-                                               new DateTime(), relyingParty.getProviderId(),
-                                               failureStatus);
-
-                               return samlResponse;
-                       }
-
-                       // the user successfully authenticated.
-                       // build an authentication assertion.
-                       samlResponse = buildResponse(authnRequest.getID(), new DateTime(),
-                                       relyingParty.getProviderId(), buildStatus(
-                                                       StatusCode.SUCCESS_URI, null, null));
-
-                       DateTime now = new DateTime();
-                       Conditions conditions = conditionsBuilder.buildObject();
-                       conditions.setNotBefore(now.minusSeconds(30)); // for now, clock
-                                                                                                                       // skew is
-                                                                                                                       // hard-coded to 30
-                                                                                                                       // seconds.
-                       conditions.setNotOnOrAfter(now.plus(ssoConfig
-                                       .getAssertionLifetime()));
-
-                       // XXX: don't blindly copy conditions from the AuthnRequest.
-                       List<Condition> requestConditions = authnRequest.getConditions()
-                                       .getConditions();
-                       if (requestConditions != null && requestConditions.size() > 0) {
-                               conditions.getConditions().addAll(requestConditions);
-                       }
-
-                       Assertion assertion = buildAssertion(authnRequest.getSubject(),
-                                       conditions, issuer, new String[] { relyingParty
-                                                       .getRelyingPartyId() });
-                       setAuthenticationStatement(assertion, loginCtx, authnRequest);
-
-                       samlResponse.getAssertions().add(assertion);
-
-                       // retrieve the AssertionConsumerService endpoint (we parsed it in
-                       // verifyAuthnRequest()
-                       AssertionConsumerService acsEndpoint = getACSEndpointFromSession(session);
-
-               } catch (AuthenticationRequestException ex) {
-
-                       Status errorStatus = ex.getStatus();
-                       if (errorStatus == null) {
-                               // if no explicit status code was set,
-                               // assume the error was in the message.
-                               samlResponse = buildResponse(authnRequest.getID(),
-                                               new DateTime(), relyingParty.getProviderId(),
-                                               errorStatus);
-                       }
-               }
-
-               return samlResponse;
-       }
-
-       /**
-        * Check that a request's issuer can be found in the metadata. If so, store
-        * the relevant metadata objects in the user's session.
-        * 
-        * @param issuer
-        *            The issuer of the AuthnRequest.
-        * @param relyingParty
-        *            The RelyingPartyConfiguration for the issuer.
-        * @param ssoConfig
-        *            The SSOConfiguration for the relyingParty
-        * @param spDescriptor
-        *            The SPSSODescriptor for the ssoConfig.
-        * 
-        * @return <code>true</code> if Metadata was found for the issuer;
-        *         otherwise, <code>false</code>.
-        */
-       protected boolean findMetadataForSSORequest(Issuer issuer,
-                       RelyingPartyConfiguration relyingParty, SSOConfiguration ssoConfig,
-                       SPSSODescriptor spDescriptor) {
-
-               MetadataProvider metadataProvider = getRelyingPartyConfigurationManager()
-                               .getMetadataProvider();
-               String providerId = issuer.getSPProvidedID();
-               relyingParty = getRelyingPartyConfigurationManager()
-                               .getRelyingPartyConfiguration(providerId);
-               ssoConfig = (SSOConfiguration) relyingParty.getProfileConfigurations()
-                               .get(SSOConfiguration.PROFILE_ID);
-
-               try {
-                       spDescriptor = metadataProvider.getEntityDescriptor(
-                                       relyingParty.getRelyingPartyId()).getSPSSODescriptor(
-                                       SAML20_PROTOCOL_URI);
-               } catch (MetadataProviderException ex) {
-                       log.error(
-                                       "SAML 2 Authentication Request: Unable to locate metadata for SP "
-                                                       + providerId + " for protocol "
-                                                       + SAML20_PROTOCOL_URI, ex);
-                       return false;
-               }
-
-               if (spDescriptor == null) {
-                       log
-                                       .error("SAML 2 Authentication Request: Unable to locate metadata for SP "
-                                                       + providerId
-                                                       + " for protocol "
-                                                       + SAML20_PROTOCOL_URI);
-                       return false;
-               }
-
-               return true;
-       }
-
-       /**
-        * Check if the user has already been authenticated.
-        * 
-        * @param httpSession
-        *            the user's HttpSession.
-        * 
-        * @return <code>true</code> if the user has been authenticated. otherwise
-        *         <code>false</code>
-        */
-       protected boolean hasUserAuthenticated(final HttpSession httpSession) {
-
-               // if the user has authenticated, their session will have a LoginContext
-
-               Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
-               return (o != null && o instanceof LoginContext);
-       }
-
-       /**
-        * Store a user's AuthnRequest and Issuer in the session.
-        * 
-        * @param authnRequest
-        *            A SAML 2 AuthnRequest.
-        * @param issuer
-        *            The issuer of the AuthnRequest.
-        * @param session
-        *            The HttpSession in which the data should be stored.
-        * @param relyingParty
-        *            The RelyingPartyConfiguration for the issuer.
-        * @param ssoConfig
-        *            The SSOConfiguration for the relyingParty
-        * @param spDescriptor
-        *            The SPSSODescriptor for the ssoConfig.
-        */
-       protected void storeRequestData(final HttpSession session,
-                       final AuthnRequest authnRequest, final Issuer issuer,
-                       final RelyingPartyConfiguration relyingParty,
-                       final SSOConfiguration ssoConfig, final SPSSODescriptor spDescriptor) {
-
-               if (session == null) {
-                       return;
-               }
-
-               session.setAttribute(AUTHNREQUEST_SESSION_KEY, authnRequest);
-               session.setAttribute(ISSUER_SESSION_KEY, issuer);
-               session.setAttribute(RPCONFIG_SESSION_KEY, relyingParty);
-               session.setAttribute(SSOCONFIG_SESSION_KEY, ssoConfig);
-               session.setAttribute(SPSSODESC_SESSION_KEY, spDescriptor);
-       }
-
-       /**
-        * Retrieve the AuthnRequest and Issuer from a session.
-        * 
-        * @param session
-        *            The HttpSession in which the data was stored.
-        * @param authnRequest
-        *            Will be populated with the AuthnRequest.
-        * @param issuer
-        *            Will be populated with the ssuer of the AuthnRequest.
-        * @param relyingParty
-        *            Will be populated with the RelyingPartyConfiguration for the
-        *            issuer.
-        * @param ssoConfig
-        *            Will be populated with the SSOConfiguration for the
-        *            relyingParty
-        * @param spDescriptor
-        *            Will be populated with the SPSSODescriptor for the ssoConfig.
-        */
-       protected void retrieveRequestData(final HttpSession session,
-                       AuthnRequest authnRequest, Issuer issuer,
-                       RelyingPartyConfiguration relyingParty, SSOConfiguration ssoConfig,
-                       SPSSODescriptor spDescriptor) {
-
-               if (session == null) {
-                       authnRequest = null;
-                       issuer = null;
-               }
-
-               authnRequest = (AuthnRequest) session
-                               .getAttribute(AUTHNREQUEST_SESSION_KEY);
-               issuer = (Issuer) session.getAttribute(ISSUER_SESSION_KEY);
-               relyingParty = (RelyingPartyConfiguration) session
-                               .getAttribute(RPCONFIG_SESSION_KEY);
-               ssoConfig = (SSOConfiguration) session
-                               .getAttribute(SSOCONFIG_SESSION_KEY);
-               spDescriptor = (SPSSODescriptor) session
-                               .getAttribute(SPSSODESC_SESSION_KEY);
-
-               session.removeAttribute(AUTHNREQUEST_SESSION_KEY);
-               session.removeAttribute(ISSUER_SESSION_KEY);
-               session.removeAttribute(RPCONFIG_SESSION_KEY);
-               session.removeAttribute(SSOCONFIG_SESSION_KEY);
-               session.removeAttribute(SPSSODESC_SESSION_KEY);
-       }
-
-       /**
-        * Check if the user has already been authenticated. If so, return the
-        * LoginContext. If not, redirect the user to the AuthenticationManager.
-        * 
-        * @param authnRequest
-        *            The SAML 2 AuthnRequest.
-        * @param httpSession
-        *            The user's HttpSession.
-        * @param request
-        *            The user's HttpServletRequest.
-        * @param response
-        *            The user's HttpServletResponse.
-        * 
-        * @return A LoginContext for the authenticated user.
-        * 
-        * @throws SerlvetException
-        *             on error.
-        */
-       protected void authenticateUser(final AuthnRequest authnRequest,
-                       final HttpSession httpSession, final HttpServletRequest request,
-                       final HttpServletResponse response) throws ServletException {
-
-               // Forward the request to the AuthenticationManager.
-               // When the AuthenticationManager is done it will
-               // forward the request back to this servlet.
-
-               Saml2LoginContext loginCtx = new Saml2LoginContext(authnRequest);
-               loginCtx.setProfileHandlerURL(request.getPathInfo());
-               httpSession.setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginCtx);
-               try {
-                       RequestDispatcher dispatcher = request
-                                       .getRequestDispatcher(authnMgrURL);
-                       dispatcher.forward(request, response);
-               } catch (IOException ex) {
-                       log.error("Error forwarding SAML 2 AuthnRequest "
-                                       + authnRequest.getID() + " to AuthenticationManager", ex);
-                       throw new ServletException("Error forwarding SAML 2 AuthnRequest "
-                                       + authnRequest.getID() + " to AuthenticationManager", ex);
-               }
-       }
-
-       /**
-        * Build an AuthnStatement and add it to a Response.
-        * 
-        * @param response
-        *            The Response to which the AuthnStatement will be added.
-        * @param loginCtx
-        *            The LoginContext of the sucessfully authenticated user.
-        * @param authnRequest
-        *            The AuthnRequest that prompted this message.
-        * @param ssoConfig
-        *            The SSOConfiguration for the RP to which we are addressing
-        *            this message.
-        * @param issuer
-        *            The IdP's identifier.
-        * @param audiences
-        *            An array of URIs restricting the audience of this assertion.
-        */
-       protected void setAuthenticationStatement(Assertion assertion,
-                       final Saml2LoginContext loginContext,
-                       final AuthnRequest authnRequest) throws ServletException {
-
-               // Build the AuthnCtx. We need to determine if the user was
-               // authenticated
-               // with an AuthnContextClassRef or a AuthnContextDeclRef
-               AuthnContext authnCtx = buildAuthnCtx(authnRequest
-                               .getRequestedAuthnContext(), loginContext);
-               if (authnCtx == null) {
-                       log.error("Error respond to SAML 2 AuthnRequest "
-                                       + authnRequest.getID()
-                                       + " : Unable to determine authentication method");
-               }
-
-               AuthnStatement stmt = authnStatementBuilder.buildObject();
-               stmt.setAuthnInstant(loginContext.getAuthenticationInstant());
-               stmt.setAuthnContext(authnCtx);
-
-               // add the AuthnStatement to the Assertion
-               List<AuthnStatement> authnStatements = assertion.getAuthnStatements();
-               authnStatements.add(stmt);
-       }
-
-       /**
-        * Create the AuthnContex object.
-        * 
-        * To do this, we have to walk the AuthnRequest's RequestedAuthnContext
-        * object and compare any values we find to what's set in the loginContext.
-        * 
-        * @param requestedAuthnCtx
-        *            The RequestedAuthnContext from the Authentication Request.
-        * @param authnMethod
-        *            The authentication method that was used.
-        * 
-        * @return An AuthnCtx object on success or <code>null</code> on failure.
-        */
-       protected AuthnContext buildAuthnCtx(
-                       final RequestedAuthnContext requestedAuthnCtx,
-                       final Saml2LoginContext loginContext) {
-
-               // this method assumes that only one URI will match.
-
-               AuthnContext authnCtx = authnContextBuilder.buildObject();
-               String authnMethod = loginContext.getAuthenticationMethod();
-
-               List<AuthnContextClassRef> authnClasses = requestedAuthnCtx
-                               .getAuthnContextClassRefs();
-               List<AuthnContextDeclRef> authnDeclRefs = requestedAuthnCtx
-                               .getAuthnContextDeclRefs();
-
-               if (authnClasses != null) {
-                       for (AuthnContextClassRef classRef : authnClasses) {
-                               if (classRef != null) {
-                                       String s = classRef.getAuthnContextClassRef();
-                                       if (s != null && authnMethod.equals(s)) {
-                                               AuthnContextClassRef ref = authnContextClassRefBuilder
-                                                               .buildObject();
-                                               authnCtx.setAuthnContextClassRef(ref);
-                                               return authnCtx;
-                                       }
-                               }
-                       }
-               }
-
-               // if no AuthnContextClassRef's matched, try the DeclRefs
-               if (authnDeclRefs != null) {
-                       for (AuthnContextDeclRef declRef : authnDeclRefs) {
-                               if (declRef != null) {
-                                       String s = declRef.getAuthnContextDeclRef();
-                                       if (s != null && authnMethod.equals((s))) {
-                                               AuthnContextDeclRef ref = authnContextDeclRefBuilder
-                                                               .buildObject();
-                                               authnCtx.setAuthnContextDeclRef(ref);
-                                               return authnCtx;
-                                       }
-                               }
-                       }
-               }
-
-               // no matches were found.
-               return null;
-       }
-
-       /**
-        * Get the user's LoginContext.
-        * 
-        * @param httpSession
-        *            The user's HttpSession.
-        * 
-        * @return The user's LoginContext.
-        * 
-        * @throws ServletException
-        *             On error.
-        */
-       protected Saml2LoginContext getLoginContext(final HttpSession httpSession)
-                       throws ServletException {
-
-               Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
-               if (o == null) {
-                       log.error("User's session does not contain a LoginContext object.");
-                       throw new ServletException(
-                                       "User's session does not contain a LoginContext object.");
-               }
-
-               if (!(o instanceof Saml2LoginContext)) {
-                       log
-                                       .error("Invalid login context object -- object is not an instance of Saml2LoginContext.");
-                       throw new ServletException("Invalid login context object.");
-               }
-
-               Saml2LoginContext ctx = (Saml2LoginContext) o;
-
-               httpSession.removeAttribute(LoginContext.LOGIN_CONTEXT_KEY);
-
-               return ctx;
-       }
-
-       /**
-        * Verify the AuthnRequest is well-formed.
-        * 
-        * @param authnRequest
-        *            The user's SAML 2 AuthnRequest.
-        * @param issuer
-        *            The Issuer of the AuthnRequest.
-        * @param relyingParty
-        *            The relying party configuration for the request's originator.
-        * @param session
-        *            The user's HttpSession.
-        * 
-        * @throws AuthenticationRequestException
-        *             on error.
-        */
-       protected void verifyAuthnRequest(final AuthnRequest authnRequest,
-                       Issuer issuer, final RelyingPartyConfiguration relyingParty,
-                       final HttpSession session) throws AuthenticationRequestException {
-
-               Status failureStatus;
-
-               // Check if we are in scope to handle this AuthnRequest
-               checkScope(authnRequest, issuer.getSPProvidedID());
-
-               // XXX: run signature checks on authnRequest
-
-               // verify that the AssertionConsumerService url is valid.
-               AssertionConsumerService acsEndpoint = getAndVerifyACSEndpoint(
-                               authnRequest, relyingParty.getRelyingPartyId(),
-                               getRelyingPartyConfigurationManager().getMetadataProvider());
-               session.setAttribute(ACS_SESSION_KEY, acsEndpoint);
-
-               // check for nameID constraints.
-               Subject subject = getAndVerifySubject(authnRequest);
-       }
-
-       /**
-        * Get and verify the Subject element.
-        * 
-        * @param authnRequest
-        *            The SAML 2 AuthnRequest.
-        * 
-        * @return A Subject element.
-        * 
-        * @throws AuthenticationRequestException
-        *             on error.
-        */
-       protected Subject getAndVerifySubject(final AuthnRequest authnRequest)
-                       throws AuthenticationRequestException {
-
-               Status failureStatus;
-
-               Subject subject = authnRequest.getSubject();
-
-               if (subject == null) {
-                       failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
-                                       "SAML 2 AuthnRequest " + authnRequest.getID()
-                                                       + " is malformed: It does not contain a Subject.");
-                       throw new AuthenticationRequestException(
-                                       "AuthnRequest lacks a Subject", failureStatus);
-               }
-
-               // The Web Browser SSO profile disallows SubjectConfirmation
-               // methods in the requested subject.
-               List<SubjectConfirmation> confMethods = subject
-                               .getSubjectConfirmations();
-               if (confMethods != null || confMethods.size() > 0) {
-                       log
-                                       .error("SAML 2 AuthnRequest "
-                                                       + authnRequest.getID()
-                                                       + " is malformed: It contains SubjectConfirmation elements.");
-                       failureStatus = buildStatus(
-                                       StatusCode.REQUESTER_URI,
-                                       null,
-                                       "SAML 2 AuthnRequest "
-                                                       + authnRequest.getID()
-                                                       + " is malformed: It contains SubjectConfirmation elements.");
-                       throw new AuthenticationRequestException(
-                                       "AuthnRequest contains SubjectConfirmation elements",
-                                       failureStatus);
-               }
-
-               return subject;
-       }
-
-       /**
-        * Return the endpoint URL and protocol binding to use for the AuthnRequest.
-        * 
-        * @param authnRequest
-        *            The SAML 2 AuthnRequest.
-        * @param providerId
-        *            The SP's providerId.
-        * @param metadata
-        *            The appropriate Metadata.
-        * 
-        * @return The AssertionConsumerService for the endpoint, or
-        *         <code>null</code> on error.
-        * 
-        * @throws AuthenticationRequestException
-        *             On error.
-        */
-       protected AssertionConsumerService getAndVerifyACSEndpoint(
-                       final AuthnRequest authnRequest, String providerId,
-                       final MetadataProvider metadata)
-                       throws AuthenticationRequestException {
-
-               Status failureStatus;
-
-               // Either the AssertionConsumerServiceIndex must be present
-               // or AssertionConsumerServiceURL must be present.
-
-               Integer idx = authnRequest.getAssertionConsumerServiceIndex();
-               String acsURL = authnRequest.getAssertionConsumerServiceURL();
-
-               if (idx != null && acsURL != null) {
-                       log
-                                       .error("SAML 2 AuthnRequest "
-                                                       + authnRequest.getID()
-                                                       + " is malformed: It contains both an AssertionConsumerServiceIndex and an AssertionConsumerServiceURL");
-                       failureStatus = buildStatus(
-                                       StatusCode.REQUESTER_URI,
-                                       null,
-                                       "SAML 2 AuthnRequest "
-                                                       + authnRequest.getID()
-                                                       + " is malformed: It contains both an AssertionConsumerServiceIndex and an AssertionConsumerServiceURL");
-                       throw new AuthenticationRequestException("Malformed AuthnRequest",
-                                       failureStatus);
-               }
-
-               SPSSODescriptor spDescriptor;
-               try {
-                       spDescriptor = metadata.getEntityDescriptor(providerId)
-                                       .getSPSSODescriptor(SAML20_PROTOCOL_URI);
-               } catch (MetadataProviderException ex) {
-                       log.error(
-                                       "Unable retrieve SPSSODescriptor metadata for providerId "
-                                                       + providerId
-                                                       + " while processing SAML 2 AuthnRequest "
-                                                       + authnRequest.getID(), ex);
-                       failureStatus = buildStatus(StatusCode.RESPONDER_URI, null,
-                                       "Unable to locate metadata for " + providerId);
-                       throw new AuthenticationRequestException(
-                                       "Unable to locate metadata", ex, failureStatus);
-               }
-
-               List<AssertionConsumerService> acsList = spDescriptor
-                               .getAssertionConsumerServices();
-
-               // if the ACS index is specified, retrieve it from the metadata
-               if (idx != null) {
-
-                       int i = idx.intValue();
-
-                       // if the index is out of range, return an appropriate error.
-                       if (i > acsList.size()) {
-                               log.error("Illegal AssertionConsumerIndex specicifed (" + i
-                                               + ") in SAML 2 AuthnRequest " + authnRequest.getID());
-
-                               failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
-                                               "Illegal AssertionConsumerIndex specicifed (" + i
-                                                               + ") in SAML 2 AuthnRequest "
-                                                               + authnRequest.getID());
-
-                               throw new AuthenticationRequestException(
-                                               "Illegal AssertionConsumerIndex in AuthnRequest",
-                                               failureStatus);
-                       }
-
-                       return acsList.get(i);
-               }
-
-               // if the ACS endpoint is specified, validate it against the metadata
-               String protocolBinding = authnRequest.getProtocolBinding();
-               for (AssertionConsumerService acs : acsList) {
-                       if (acsURL.equals(acs.getLocation())) {
-                               if (protocolBinding != null) {
-                                       if (protocolBinding.equals(acs.getBinding())) {
-                                               return acs;
-                                       }
-                               }
-                       }
-               }
-
-               log
-                               .error("Error processing SAML 2 AuthnRequest message "
-                                               + authnRequest.getID()
-                                               + ": Unable to validate AssertionConsumerServiceURL against metadata: "
-                                               + acsURL + " for binding " + protocolBinding);
-
-               failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
-                               "Unable to validate AssertionConsumerService against metadata.");
-
-               throw new AuthenticationRequestException(
-                               "SAML 2 AuthenticationRequest: Unable to validate AssertionConsumerService against Metadata",
-                               failureStatus);
-       }
-
-       /**
-        * Retrieve a parsed AssertionConsumerService endpoint from the user's
-        * session.
-        * 
-        * @param session
-        *            The user's HttpSession.
-        * 
-        * @return An AssertionConsumerServiceEndpoint object.
-        * 
-        * @throws ServletException
-        *             On error.
-        */
-       protected AssertionConsumerService getACSEndpointFromSession(
-                       final HttpSession session) throws ServletException {
-
-               Object o = session.getAttribute(ACS_SESSION_KEY);
-               if (o == null) {
-                       log
-                                       .error("User's session does not contain an AssertionConsumerService object.");
-                       throw new ServletException(
-                                       "User's session does not contain an AssertionConsumerService object.");
-               }
-
-               if (!(o instanceof AssertionConsumerService)) {
-                       log
-                                       .error("Invalid session data -- object is not an instance of AssertionConsumerService.");
-                       throw new ServletException(
-                                       "Invalid session data -- object is not an instance of AssertionConsumerService.");
-               }
-
-               AssertionConsumerService endpoint = (AssertionConsumerService) o;
-
-               session.removeAttribute(ACS_SESSION_KEY);
-
-               return endpoint;
-       }
-
-       /**
-        * Check if an {@link AuthnRequest} contains a {@link Scoping} element. If
-        * so, check if the specified IdP is in the {@link IDPList} element. If no
-        * Scoping element is present, this method returns <code>true</code>.
-        * 
-        * @param authnRequest
-        *            The {@link AuthnRequest} element to check.
-        * @param providerId
-        *            The IdP's ProviderID.
-        * 
-        * @throws AuthenticationRequestException
-        *             on error.
-        */
-       protected void checkScope(final AuthnRequest authnRequest, String providerId)
-                       throws AuthenticationRequestException {
-
-               Status failureStatus;
-
-               List<String> idpEntries = new LinkedList<String>();
-
-               Scoping scoping = authnRequest.getScoping();
-               if (scoping == null) {
-                       return;
-               }
-
-               // process all of the explicitly listed idp provider ids
-               IDPList idpList = scoping.getIDPList();
-               if (idpList == null) {
-                       return;
-               }
-
-               List<IDPEntry> explicitIDPEntries = idpList.getIDPEntrys();
-               if (explicitIDPEntries != null) {
-                       for (IDPEntry entry : explicitIDPEntries) {
-                               String s = entry.getProviderID();
-                               if (s != null) {
-                                       idpEntries.add(s);
-                               }
-                       }
-               }
-
-               // If the IDPList is incomplete, retrieve the complete list
-               // and add the entries to idpEntries.
-               GetComplete getComplete = idpList.getGetComplete();
-               IDPList referencedIdPs = getCompleteIDPList(getComplete);
-               if (referencedIdPs != null) {
-                       List<IDPEntry> referencedIDPEntries = referencedIdPs.getIDPEntrys();
-                       if (referencedIDPEntries != null) {
-                               for (IDPEntry entry : referencedIDPEntries) {
-                                       String s = entry.getProviderID();
-                                       if (s != null) {
-                                               idpEntries.add(s);
-                                       }
-                               }
-                       }
-               }
-
-               // iterate over all the IDPEntries we've gathered,
-               // and check if we're in scope.
-               for (String requestProviderId : idpEntries) {
-                       if (providerId.equals(requestProviderId)) {
-                               log.debug("Found Scoping match for IdP: (" + providerId + ")");
-                               return;
-                       }
-               }
-
-               log.error("SAML 2 AuthnRequest " + authnRequest.getID()
-                               + " contains a Scoping element which "
-                               + "does not contain a providerID registered with this IdP.");
-
-               failureStatus = buildStatus(StatusCode.RESPONDER_URI,
-                               StatusCode.NO_SUPPORTED_IDP_URI, null);
-               throw new AuthenticationRequestException(
-                               "Unrecognized providerID in Scoping element", failureStatus);
-       }
-
-       /**
-        * Retrieve an incomplete IDPlist.
-        * 
-        * This only handles URL-based <GetComplete/> references.
-        * 
-        * @param getComplete
-        *            The (possibly <code>null</code>) &lt;GetComplete/&gt;
-        *            element
-        * 
-        * @return an {@link IDPList} or <code>null</code> if the uri can't be
-        *         dereferenced.
-        */
-       protected IDPList getCompleteIDPList(final GetComplete getComplete) {
-
-               // XXX: enhance this method to cache the url and last-modified-header
-
-               if (getComplete == null) {
-                       return null;
-               }
-
-               String uri = getComplete.getGetComplete();
-               if (uri != null) {
-                       return null;
-               }
-
-               IDPList idpList = null;
-               InputStream istream = null;
-
-               try {
-                       URL url = new URL(uri);
-                       URLConnection conn = url.openConnection();
-                       istream = conn.getInputStream();
-
-                       // convert the raw data into an XML object
-                       Document doc = parserPool.parse(istream);
-                       Element docElement = doc.getDocumentElement();
-                       Unmarshaller unmarshaller = Configuration.getUnmarshallerFactory()
-                                       .getUnmarshaller(docElement);
-                       idpList = (IDPList) unmarshaller.unmarshall(docElement);
-
-               } catch (MalformedURLException ex) {
-                       log.error(
-                                       "Unable to retrieve GetComplete IDPList. Unsupported URI: "
-                                                       + uri, ex);
-               } catch (IOException ex) {
-                       log.error("IO Error while retreieving GetComplete IDPList from "
-                                       + uri, ex);
-               } catch (ConfigurationException ex) {
-                       log.error(
-                                       "Internal OpenSAML error while parsing GetComplete IDPList from "
-                                                       + uri, ex);
-               } catch (XMLParserException ex) {
-                       log.error(
-                                       "Internal OpenSAML error while parsing GetComplete IDPList from "
-                                                       + uri, ex);
-               } catch (UnmarshallingException ex) {
-                       log.error(
-                                       "Internal OpenSAML error while unmarshalling GetComplete IDPList from "
-                                                       + uri, ex);
-               } finally {
-
-                       if (istream != null) {
-                               try {
-                                       istream.close();
-                               } catch (IOException ex) {
-                                       // pass
-                               }
-                       }
-               }
-
-               return idpList;
-       }
+public abstract class AbstractAuthenticationRequest extends AbstractSAML2ProfileHandler {
+    
+    /** Class logger. */
+    private static final Logger log = Logger.getLogger(AbstractAuthenticationRequest.class);
+    
+    /** Key in an HttpSession for the AssertionConsumerService object. */
+    protected static final String ACS_SESSION_KEY = "AssertionConsumerService";
+    
+    /** Key in an HttpSession for the RelyingParty. */
+    protected static final String RPCONFIG_SESSION_KEY = "RelyingPartyConfiguration";
+    
+    /** Key in an HttpSession for the SSOConfiguration. */
+    protected static final String SSOCONFIG_SESSION_KEY = "SSOConfiguration";
+    
+    /** Key in an HttpSession for the SPSSODescriptor. */
+    protected static final String SPSSODESC_SESSION_KEY = "SPSSODescriptor";
+    
+    /** Key in an HttpSession for the AuthnRequest. */
+    protected static final String AUTHNREQUEST_SESSION_KEY = "AuthnRequest";
+    
+    /** Key in an HttpSession for the Issuer. */
+    protected static final String ISSUER_SESSION_KEY = "Issuer";
+    
+    /** Backing store for artifacts. */
+    protected ArtifactMap artifactMap;
+    
+    /** The path to the IdP's AuthenticationManager servlet */
+    protected String authnMgrURL;
+    
+    /** AuthenticationManager to be used */
+    protected AuthenticationManager authnMgr;
+    
+    /** ArtifactFactory used to make artifacts. */
+    protected SAMLArtifactFactory artifactFactory;
+    
+    /** A pool of XML parsers. */
+    protected ParserPool parserPool;
+    
+    /** Builder for AuthnStatements. */
+    protected SAMLObjectBuilder<AuthnStatement> authnStatementBuilder;
+    
+    /** Builder for AuthnContexts. */
+    protected SAMLObjectBuilder<AuthnContext> authnContextBuilder;
+    
+    /** Builder for AuthnContextDeclRef's */
+    protected SAMLObjectBuilder<AuthnContextDeclRef> authnContextDeclRefBuilder;
+    
+    /** Builder for AuthnContextClassRef's. */
+    protected SAMLObjectBuilder<AuthnContextClassRef> authnContextClassRefBuilder;
+    
+    /**
+     * Constructor.
+     */
+    public AbstractAuthenticationRequest() {
+        
+        parserPool = new BasicParserPool();
+        artifactFactory = new SAMLArtifactFactory();
+        authnStatementBuilder = (SAMLObjectBuilder<AuthnStatement>) getBuilderFactory().getBuilder(AuthnStatement.DEFAULT_ELEMENT_NAME);
+        authnContextBuilder = (SAMLObjectBuilder<AuthnContext>) getBuilderFactory().getBuilder(AuthnContext.DEFAULT_ELEMENT_NAME);
+        authnContextDeclRefBuilder = (SAMLObjectBuilder<AuthnContextDeclRef>) getBuilderFactory().getBuilder(AuthnContextDeclRef.DEFAULT_ELEMENT_NAME);
+        authnContextClassRefBuilder = (SAMLObjectBuilder<AuthnContextClassRef>) getBuilderFactory().getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
+    }
+    
+    /**
+     * Set the Authentication Mananger.
+     *
+     * @param authnManager
+     *            The IdP's AuthenticationManager.
+     */
+    public void setAuthenticationManager(AuthenticationManager authnManager) {
+        this.authnMgr = authnMgr;
+    }
+    
+    /**
+     * Set the ArtifactMap.
+     *
+     * @param artifactMap
+     *            The IdP's ArtifactMap.
+     */
+    public void setArtifactMap(ArtifactMap artifactMap) {
+        this.artifactMap = artifactMap;
+    }
+    
+    /**
+     * Evaluate a SAML 2 AuthenticationRequest message.
+     *
+     * @param authnRequest
+     *            A SAML 2 AuthenticationRequest
+     * @param issuer
+     *            The issuer of the authnRequest.
+     * @param session
+     *            The HttpSession of the request.
+     * @param relyingParty
+     *            The RelyingPartyConfiguration for the request.
+     * @param ssoConfig
+     *            The SSOConfiguration for the request.
+     * @param spDescriptor
+     *            The SPSSODescriptor for the request.
+     *
+     * @return A Response containing a failure message or a AuthenticationStmt.
+     *
+     * @throws ServletException
+     *             On Error.
+     */
+    protected Response evaluateRequest(final AuthnRequest authnRequest,
+            final Issuer issuer, final HttpSession session,
+            final RelyingPartyConfiguration relyingParty,
+            final SSOConfiguration ssoConfig, final SPSSODescriptor spDescriptor)
+            throws ServletException {
+        
+        Response samlResponse;
+        
+        try {
+            // check if the authentication was successful.
+            Saml2LoginContext loginCtx = getLoginContext(session);
+            if (!loginCtx.getAuthenticationOK()) {
+                // if authentication failed, send the appropriate SAML error message.
+                String failureMessage = loginCtx.getAuthenticationFailureMessage();
+                Status failureStatus = buildStatus(StatusCode.RESPONDER_URI, StatusCode.AUTHN_FAILED_URI, failureMessage);
+                samlResponse = buildResponse(authnRequest.getID(), new DateTime(), relyingParty.getProviderId(),
+                        failureStatus);
+                
+                return samlResponse;
+            }
+            
+            // the user successfully authenticated.
+            // build an authentication assertion.
+            samlResponse = buildResponse(authnRequest.getID(), new DateTime(),
+                    relyingParty.getProviderId(), buildStatus(StatusCode.SUCCESS_URI, null, null));
+            
+            DateTime now = new DateTime();
+            Assertion assertion = buildAssertion(now, relyingParty,
+                    (AbstractSAML2ProfileConfiguration) relyingParty.getProfileConfigurations().get(SSOConfiguration.PROFILE_ID));
+            assertion.setSubject(authnRequest.getSubject());
+            setAuthenticationStatement(assertion, loginCtx, authnRequest);
+            
+            samlResponse.getAssertions().add(assertion);
+            
+            // retrieve the AssertionConsumerService endpoint (we parsed it in
+            // verifyAuthnRequest()
+            AssertionConsumerService acsEndpoint = getACSEndpointFromSession(session);
+            
+        } catch (AuthenticationRequestException ex) {
+            
+            Status errorStatus = ex.getStatus();
+            if (errorStatus == null) {
+                // if no explicit status code was set,
+                // assume the error was in the message.
+                samlResponse = buildResponse(authnRequest.getID(),
+                        new DateTime(), relyingParty.getProviderId(),
+                        errorStatus);
+            }
+        }
+        
+        return samlResponse;
+    }
+    
+    /**
+     * Build a SAML 2 Response element with basic fields populated.
+     *
+     * Failure handlers can send the returned response element to the RP.
+     * Success handlers should add the assertions before sending it.
+     *
+     * @param inResponseTo
+     *            The ID of the request this is in response to.
+     * @param issueInstant
+     *            The timestamp of this response.
+     * @param issuer
+     *            The URI of the RP issuing the response.
+     * @param status
+     *            The response's status code.
+     *
+     * @return The populated Response object.
+     */
+    protected Response buildResponse(String inResponseTo,
+            final DateTime issueInstant, String issuer, final Status status) {
+        
+        Response response = getResponseBuilder().buildObject();
+        
+        Issuer i = getIssuerBuilder().buildObject();
+        i.setValue(issuer);
+        
+        response.setVersion(SAML_VERSION);
+        response.setID(getIdGenerator().generateIdentifier());
+        response.setInResponseTo(inResponseTo);
+        response.setIssueInstant(issueInstant);
+        response.setIssuer(i);
+        response.setStatus(status);
+        
+        return response;
+    }
+    
+    
+    /**
+     * Check that a request's issuer can be found in the metadata. If so, store
+     * the relevant metadata objects in the user's session.
+     *
+     * @param issuer
+     *            The issuer of the AuthnRequest.
+     * @param relyingParty
+     *            The RelyingPartyConfiguration for the issuer.
+     * @param ssoConfig
+     *            The SSOConfiguration for the relyingParty
+     * @param spDescriptor
+     *            The SPSSODescriptor for the ssoConfig.
+     *
+     * @return <code>true</code> if Metadata was found for the issuer;
+     *         otherwise, <code>false</code>.
+     */
+    protected boolean findMetadataForSSORequest(Issuer issuer,
+            RelyingPartyConfiguration relyingParty, SSOConfiguration ssoConfig,
+            SPSSODescriptor spDescriptor) {
+        
+        MetadataProvider metadataProvider = getRelyingPartyConfigurationManager()
+        .getMetadataProvider();
+        String providerId = issuer.getSPProvidedID();
+        relyingParty = getRelyingPartyConfigurationManager()
+        .getRelyingPartyConfiguration(providerId);
+        ssoConfig = (SSOConfiguration) relyingParty.getProfileConfigurations()
+        .get(SSOConfiguration.PROFILE_ID);
+        
+        try {
+            spDescriptor = metadataProvider.getEntityDescriptor(
+                    relyingParty.getRelyingPartyId()).getSPSSODescriptor(
+                    SAML20_PROTOCOL_URI);
+        } catch (MetadataProviderException ex) {
+            log.error(
+                    "SAML 2 Authentication Request: Unable to locate metadata for SP "
+                    + providerId + " for protocol "
+                    + SAML20_PROTOCOL_URI, ex);
+            return false;
+        }
+        
+        if (spDescriptor == null) {
+            log
+                    .error("SAML 2 Authentication Request: Unable to locate metadata for SP "
+                    + providerId
+                    + " for protocol "
+                    + SAML20_PROTOCOL_URI);
+            return false;
+        }
+        
+        return true;
+    }
+    
+    /**
+     * Check if the user has already been authenticated.
+     *
+     * @param httpSession
+     *            the user's HttpSession.
+     *
+     * @return <code>true</code> if the user has been authenticated. otherwise
+     *         <code>false</code>
+     */
+    protected boolean hasUserAuthenticated(final HttpSession httpSession) {
+        
+        // if the user has authenticated, their session will have a LoginContext
+        
+        Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
+        return (o != null && o instanceof LoginContext);
+    }
+    
+    /**
+     * Store a user's AuthnRequest and Issuer in the session.
+     *
+     * @param authnRequest
+     *            A SAML 2 AuthnRequest.
+     * @param issuer
+     *            The issuer of the AuthnRequest.
+     * @param session
+     *            The HttpSession in which the data should be stored.
+     * @param relyingParty
+     *            The RelyingPartyConfiguration for the issuer.
+     * @param ssoConfig
+     *            The SSOConfiguration for the relyingParty
+     * @param spDescriptor
+     *            The SPSSODescriptor for the ssoConfig.
+     */
+    protected void storeRequestData(final HttpSession session,
+            final AuthnRequest authnRequest, final Issuer issuer,
+            final RelyingPartyConfiguration relyingParty,
+            final SSOConfiguration ssoConfig, final SPSSODescriptor spDescriptor) {
+        
+        if (session == null) {
+            return;
+        }
+        
+        session.setAttribute(AUTHNREQUEST_SESSION_KEY, authnRequest);
+        session.setAttribute(ISSUER_SESSION_KEY, issuer);
+        session.setAttribute(RPCONFIG_SESSION_KEY, relyingParty);
+        session.setAttribute(SSOCONFIG_SESSION_KEY, ssoConfig);
+        session.setAttribute(SPSSODESC_SESSION_KEY, spDescriptor);
+    }
+    
+    /**
+     * Retrieve the AuthnRequest and Issuer from a session.
+     *
+     * @param session
+     *            The HttpSession in which the data was stored.
+     * @param authnRequest
+     *            Will be populated with the AuthnRequest.
+     * @param issuer
+     *            Will be populated with the ssuer of the AuthnRequest.
+     * @param relyingParty
+     *            Will be populated with the RelyingPartyConfiguration for the
+     *            issuer.
+     * @param ssoConfig
+     *            Will be populated with the SSOConfiguration for the
+     *            relyingParty
+     * @param spDescriptor
+     *            Will be populated with the SPSSODescriptor for the ssoConfig.
+     */
+    protected void retrieveRequestData(final HttpSession session,
+            AuthnRequest authnRequest, Issuer issuer,
+            RelyingPartyConfiguration relyingParty, SSOConfiguration ssoConfig,
+            SPSSODescriptor spDescriptor) {
+        
+        if (session == null) {
+            authnRequest = null;
+            issuer = null;
+        }
+        
+        authnRequest = (AuthnRequest) session
+                .getAttribute(AUTHNREQUEST_SESSION_KEY);
+        issuer = (Issuer) session.getAttribute(ISSUER_SESSION_KEY);
+        relyingParty = (RelyingPartyConfiguration) session
+                .getAttribute(RPCONFIG_SESSION_KEY);
+        ssoConfig = (SSOConfiguration) session
+                .getAttribute(SSOCONFIG_SESSION_KEY);
+        spDescriptor = (SPSSODescriptor) session
+                .getAttribute(SPSSODESC_SESSION_KEY);
+        
+        session.removeAttribute(AUTHNREQUEST_SESSION_KEY);
+        session.removeAttribute(ISSUER_SESSION_KEY);
+        session.removeAttribute(RPCONFIG_SESSION_KEY);
+        session.removeAttribute(SSOCONFIG_SESSION_KEY);
+        session.removeAttribute(SPSSODESC_SESSION_KEY);
+    }
+    
+    /**
+     * Check if the user has already been authenticated. If so, return the
+     * LoginContext. If not, redirect the user to the AuthenticationManager.
+     *
+     * @param authnRequest
+     *            The SAML 2 AuthnRequest.
+     * @param httpSession
+     *            The user's HttpSession.
+     * @param request
+     *            The user's HttpServletRequest.
+     * @param response
+     *            The user's HttpServletResponse.
+     *
+     * @return A LoginContext for the authenticated user.
+     *
+     * @throws SerlvetException
+     *             on error.
+     */
+    protected void authenticateUser(final AuthnRequest authnRequest,
+            final HttpSession httpSession, final HttpServletRequest request,
+            final HttpServletResponse response) throws ServletException {
+        
+        // Forward the request to the AuthenticationManager.
+        // When the AuthenticationManager is done it will
+        // forward the request back to this servlet.
+        
+        Saml2LoginContext loginCtx = new Saml2LoginContext(authnRequest);
+        loginCtx.setProfileHandlerURL(request.getPathInfo());
+        httpSession.setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginCtx);
+        try {
+            RequestDispatcher dispatcher = request
+                    .getRequestDispatcher(authnMgrURL);
+            dispatcher.forward(request, response);
+        } catch (IOException ex) {
+            log.error("Error forwarding SAML 2 AuthnRequest "
+                    + authnRequest.getID() + " to AuthenticationManager", ex);
+            throw new ServletException("Error forwarding SAML 2 AuthnRequest "
+                    + authnRequest.getID() + " to AuthenticationManager", ex);
+        }
+    }
+    
+    /**
+     * Build an AuthnStatement and add it to a Response.
+     *
+     * @param response
+     *            The Response to which the AuthnStatement will be added.
+     * @param loginCtx
+     *            The LoginContext of the sucessfully authenticated user.
+     * @param authnRequest
+     *            The AuthnRequest that prompted this message.
+     * @param ssoConfig
+     *            The SSOConfiguration for the RP to which we are addressing
+     *            this message.
+     * @param issuer
+     *            The IdP's identifier.
+     * @param audiences
+     *            An array of URIs restricting the audience of this assertion.
+     */
+    protected void setAuthenticationStatement(Assertion assertion,
+            final Saml2LoginContext loginContext,
+            final AuthnRequest authnRequest) throws ServletException {
+        
+        // Build the AuthnCtx. We need to determine if the user was
+        // authenticated
+        // with an AuthnContextClassRef or a AuthnContextDeclRef
+        AuthnContext authnCtx = buildAuthnCtx(authnRequest
+                .getRequestedAuthnContext(), loginContext);
+        if (authnCtx == null) {
+            log.error("Error respond to SAML 2 AuthnRequest "
+                    + authnRequest.getID()
+                    + " : Unable to determine authentication method");
+        }
+        
+        AuthnStatement stmt = authnStatementBuilder.buildObject();
+        stmt.setAuthnInstant(loginContext.getAuthenticationInstant());
+        stmt.setAuthnContext(authnCtx);
+        
+        // add the AuthnStatement to the Assertion
+        List<AuthnStatement> authnStatements = assertion.getAuthnStatements();
+        authnStatements.add(stmt);
+    }
+    
+    /**
+     * Create the AuthnContex object.
+     *
+     * To do this, we have to walk the AuthnRequest's RequestedAuthnContext
+     * object and compare any values we find to what's set in the loginContext.
+     *
+     * @param requestedAuthnCtx
+     *            The RequestedAuthnContext from the Authentication Request.
+     * @param authnMethod
+     *            The authentication method that was used.
+     *
+     * @return An AuthnCtx object on success or <code>null</code> on failure.
+     */
+    protected AuthnContext buildAuthnCtx(
+            final RequestedAuthnContext requestedAuthnCtx,
+            final Saml2LoginContext loginContext) {
+        
+        // this method assumes that only one URI will match.
+        
+        AuthnContext authnCtx = authnContextBuilder.buildObject();
+        String authnMethod = loginContext.getAuthenticationMethod();
+        
+        List<AuthnContextClassRef> authnClasses = requestedAuthnCtx
+                .getAuthnContextClassRefs();
+        List<AuthnContextDeclRef> authnDeclRefs = requestedAuthnCtx
+                .getAuthnContextDeclRefs();
+        
+        if (authnClasses != null) {
+            for (AuthnContextClassRef classRef : authnClasses) {
+                if (classRef != null) {
+                    String s = classRef.getAuthnContextClassRef();
+                    if (s != null && authnMethod.equals(s)) {
+                        AuthnContextClassRef ref = authnContextClassRefBuilder
+                                .buildObject();
+                        authnCtx.setAuthnContextClassRef(ref);
+                        return authnCtx;
+                    }
+                }
+            }
+        }
+        
+        // if no AuthnContextClassRef's matched, try the DeclRefs
+        if (authnDeclRefs != null) {
+            for (AuthnContextDeclRef declRef : authnDeclRefs) {
+                if (declRef != null) {
+                    String s = declRef.getAuthnContextDeclRef();
+                    if (s != null && authnMethod.equals((s))) {
+                        AuthnContextDeclRef ref = authnContextDeclRefBuilder
+                                .buildObject();
+                        authnCtx.setAuthnContextDeclRef(ref);
+                        return authnCtx;
+                    }
+                }
+            }
+        }
+        
+        // no matches were found.
+        return null;
+    }
+    
+    /**
+     * Get the user's LoginContext.
+     *
+     * @param httpSession
+     *            The user's HttpSession.
+     *
+     * @return The user's LoginContext.
+     *
+     * @throws ServletException
+     *             On error.
+     */
+    protected Saml2LoginContext getLoginContext(final HttpSession httpSession)
+    throws ServletException {
+        
+        Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
+        if (o == null) {
+            log.error("User's session does not contain a LoginContext object.");
+            throw new ServletException(
+                    "User's session does not contain a LoginContext object.");
+        }
+        
+        if (!(o instanceof Saml2LoginContext)) {
+            log
+                    .error("Invalid login context object -- object is not an instance of Saml2LoginContext.");
+            throw new ServletException("Invalid login context object.");
+        }
+        
+        Saml2LoginContext ctx = (Saml2LoginContext) o;
+        
+        httpSession.removeAttribute(LoginContext.LOGIN_CONTEXT_KEY);
+        
+        return ctx;
+    }
+    
+    /**
+     * Verify the AuthnRequest is well-formed.
+     *
+     * @param authnRequest
+     *            The user's SAML 2 AuthnRequest.
+     * @param issuer
+     *            The Issuer of the AuthnRequest.
+     * @param relyingParty
+     *            The relying party configuration for the request's originator.
+     * @param session
+     *            The user's HttpSession.
+     *
+     * @throws AuthenticationRequestException
+     *             on error.
+     */
+    protected void verifyAuthnRequest(final AuthnRequest authnRequest,
+            Issuer issuer, final RelyingPartyConfiguration relyingParty,
+            final HttpSession session) throws AuthenticationRequestException {
+        
+        Status failureStatus;
+        
+        // Check if we are in scope to handle this AuthnRequest
+        checkScope(authnRequest, issuer.getSPProvidedID());
+        
+        // XXX: run signature checks on authnRequest
+        
+        // verify that the AssertionConsumerService url is valid.
+        AssertionConsumerService acsEndpoint = getAndVerifyACSEndpoint(
+                authnRequest, relyingParty.getRelyingPartyId(),
+                getRelyingPartyConfigurationManager().getMetadataProvider());
+        session.setAttribute(ACS_SESSION_KEY, acsEndpoint);
+        
+        // check for nameID constraints.
+        Subject subject = getAndVerifySubject(authnRequest);
+    }
+    
+    /**
+     * Get and verify the Subject element.
+     *
+     * @param authnRequest
+     *            The SAML 2 AuthnRequest.
+     *
+     * @return A Subject element.
+     *
+     * @throws AuthenticationRequestException
+     *             on error.
+     */
+    protected Subject getAndVerifySubject(final AuthnRequest authnRequest)
+    throws AuthenticationRequestException {
+        
+        Status failureStatus;
+        
+        Subject subject = authnRequest.getSubject();
+        
+        if (subject == null) {
+            failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
+                    "SAML 2 AuthnRequest " + authnRequest.getID()
+                    + " is malformed: It does not contain a Subject.");
+            throw new AuthenticationRequestException(
+                    "AuthnRequest lacks a Subject", failureStatus);
+        }
+        
+        // The Web Browser SSO profile disallows SubjectConfirmation
+        // methods in the requested subject.
+        List<SubjectConfirmation> confMethods = subject
+                .getSubjectConfirmations();
+        if (confMethods != null || confMethods.size() > 0) {
+            log
+                    .error("SAML 2 AuthnRequest "
+                    + authnRequest.getID()
+                    + " is malformed: It contains SubjectConfirmation elements.");
+            failureStatus = buildStatus(
+                    StatusCode.REQUESTER_URI,
+                    null,
+                    "SAML 2 AuthnRequest "
+                    + authnRequest.getID()
+                    + " is malformed: It contains SubjectConfirmation elements.");
+            throw new AuthenticationRequestException(
+                    "AuthnRequest contains SubjectConfirmation elements",
+                    failureStatus);
+        }
+        
+        return subject;
+    }
+    
+    /**
+     * Return the endpoint URL and protocol binding to use for the AuthnRequest.
+     *
+     * @param authnRequest
+     *            The SAML 2 AuthnRequest.
+     * @param providerId
+     *            The SP's providerId.
+     * @param metadata
+     *            The appropriate Metadata.
+     *
+     * @return The AssertionConsumerService for the endpoint, or
+     *         <code>null</code> on error.
+     *
+     * @throws AuthenticationRequestException
+     *             On error.
+     */
+    protected AssertionConsumerService getAndVerifyACSEndpoint(
+            final AuthnRequest authnRequest, String providerId,
+            final MetadataProvider metadata)
+            throws AuthenticationRequestException {
+        
+        Status failureStatus;
+        
+        // Either the AssertionConsumerServiceIndex must be present
+        // or AssertionConsumerServiceURL must be present.
+        
+        Integer idx = authnRequest.getAssertionConsumerServiceIndex();
+        String acsURL = authnRequest.getAssertionConsumerServiceURL();
+        
+        if (idx != null && acsURL != null) {
+            log
+                    .error("SAML 2 AuthnRequest "
+                    + authnRequest.getID()
+                    + " is malformed: It contains both an AssertionConsumerServiceIndex and an AssertionConsumerServiceURL");
+            failureStatus = buildStatus(
+                    StatusCode.REQUESTER_URI,
+                    null,
+                    "SAML 2 AuthnRequest "
+                    + authnRequest.getID()
+                    + " is malformed: It contains both an AssertionConsumerServiceIndex and an AssertionConsumerServiceURL");
+            throw new AuthenticationRequestException("Malformed AuthnRequest",
+                    failureStatus);
+        }
+        
+        SPSSODescriptor spDescriptor;
+        try {
+            spDescriptor = metadata.getEntityDescriptor(providerId)
+            .getSPSSODescriptor(SAML20_PROTOCOL_URI);
+        } catch (MetadataProviderException ex) {
+            log.error(
+                    "Unable retrieve SPSSODescriptor metadata for providerId "
+                    + providerId
+                    + " while processing SAML 2 AuthnRequest "
+                    + authnRequest.getID(), ex);
+            failureStatus = buildStatus(StatusCode.RESPONDER_URI, null,
+                    "Unable to locate metadata for " + providerId);
+            throw new AuthenticationRequestException(
+                    "Unable to locate metadata", ex, failureStatus);
+        }
+        
+        List<AssertionConsumerService> acsList = spDescriptor
+                .getAssertionConsumerServices();
+        
+        // if the ACS index is specified, retrieve it from the metadata
+        if (idx != null) {
+            
+            int i = idx.intValue();
+            
+            // if the index is out of range, return an appropriate error.
+            if (i > acsList.size()) {
+                log.error("Illegal AssertionConsumerIndex specicifed (" + i
+                        + ") in SAML 2 AuthnRequest " + authnRequest.getID());
+                
+                failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
+                        "Illegal AssertionConsumerIndex specicifed (" + i
+                        + ") in SAML 2 AuthnRequest "
+                        + authnRequest.getID());
+                
+                throw new AuthenticationRequestException(
+                        "Illegal AssertionConsumerIndex in AuthnRequest",
+                        failureStatus);
+            }
+            
+            return acsList.get(i);
+        }
+        
+        // if the ACS endpoint is specified, validate it against the metadata
+        String protocolBinding = authnRequest.getProtocolBinding();
+        for (AssertionConsumerService acs : acsList) {
+            if (acsURL.equals(acs.getLocation())) {
+                if (protocolBinding != null) {
+                    if (protocolBinding.equals(acs.getBinding())) {
+                        return acs;
+                    }
+                }
+            }
+        }
+        
+        log
+                .error("Error processing SAML 2 AuthnRequest message "
+                + authnRequest.getID()
+                + ": Unable to validate AssertionConsumerServiceURL against metadata: "
+                + acsURL + " for binding " + protocolBinding);
+        
+        failureStatus = buildStatus(StatusCode.REQUESTER_URI, null,
+                "Unable to validate AssertionConsumerService against metadata.");
+        
+        throw new AuthenticationRequestException(
+                "SAML 2 AuthenticationRequest: Unable to validate AssertionConsumerService against Metadata",
+                failureStatus);
+    }
+    
+    /**
+     * Retrieve a parsed AssertionConsumerService endpoint from the user's
+     * session.
+     *
+     * @param session
+     *            The user's HttpSession.
+     *
+     * @return An AssertionConsumerServiceEndpoint object.
+     *
+     * @throws ServletException
+     *             On error.
+     */
+    protected AssertionConsumerService getACSEndpointFromSession(
+            final HttpSession session) throws ServletException {
+        
+        Object o = session.getAttribute(ACS_SESSION_KEY);
+        if (o == null) {
+            log
+                    .error("User's session does not contain an AssertionConsumerService object.");
+            throw new ServletException(
+                    "User's session does not contain an AssertionConsumerService object.");
+        }
+        
+        if (!(o instanceof AssertionConsumerService)) {
+            log
+                    .error("Invalid session data -- object is not an instance of AssertionConsumerService.");
+            throw new ServletException(
+                    "Invalid session data -- object is not an instance of AssertionConsumerService.");
+        }
+        
+        AssertionConsumerService endpoint = (AssertionConsumerService) o;
+        
+        session.removeAttribute(ACS_SESSION_KEY);
+        
+        return endpoint;
+    }
+    
+    /**
+     * Check if an {@link AuthnRequest} contains a {@link Scoping} element. If
+     * so, check if the specified IdP is in the {@link IDPList} element. If no
+     * Scoping element is present, this method returns <code>true</code>.
+     *
+     * @param authnRequest
+     *            The {@link AuthnRequest} element to check.
+     * @param providerId
+     *            The IdP's ProviderID.
+     *
+     * @throws AuthenticationRequestException
+     *             on error.
+     */
+    protected void checkScope(final AuthnRequest authnRequest, String providerId)
+    throws AuthenticationRequestException {
+        
+        Status failureStatus;
+        
+        List<String> idpEntries = new LinkedList<String>();
+        
+        Scoping scoping = authnRequest.getScoping();
+        if (scoping == null) {
+            return;
+        }
+        
+        // process all of the explicitly listed idp provider ids
+        IDPList idpList = scoping.getIDPList();
+        if (idpList == null) {
+            return;
+        }
+        
+        List<IDPEntry> explicitIDPEntries = idpList.getIDPEntrys();
+        if (explicitIDPEntries != null) {
+            for (IDPEntry entry : explicitIDPEntries) {
+                String s = entry.getProviderID();
+                if (s != null) {
+                    idpEntries.add(s);
+                }
+            }
+        }
+        
+        // If the IDPList is incomplete, retrieve the complete list
+        // and add the entries to idpEntries.
+        GetComplete getComplete = idpList.getGetComplete();
+        IDPList referencedIdPs = getCompleteIDPList(getComplete);
+        if (referencedIdPs != null) {
+            List<IDPEntry> referencedIDPEntries = referencedIdPs.getIDPEntrys();
+            if (referencedIDPEntries != null) {
+                for (IDPEntry entry : referencedIDPEntries) {
+                    String s = entry.getProviderID();
+                    if (s != null) {
+                        idpEntries.add(s);
+                    }
+                }
+            }
+        }
+        
+        // iterate over all the IDPEntries we've gathered,
+        // and check if we're in scope.
+        for (String requestProviderId : idpEntries) {
+            if (providerId.equals(requestProviderId)) {
+                log.debug("Found Scoping match for IdP: (" + providerId + ")");
+                return;
+            }
+        }
+        
+        log.error("SAML 2 AuthnRequest " + authnRequest.getID()
+        + " contains a Scoping element which "
+                + "does not contain a providerID registered with this IdP.");
+        
+        failureStatus = buildStatus(StatusCode.RESPONDER_URI,
+                StatusCode.NO_SUPPORTED_IDP_URI, null);
+        throw new AuthenticationRequestException(
+                "Unrecognized providerID in Scoping element", failureStatus);
+    }
+    
+    /**
+     * Retrieve an incomplete IDPlist.
+     *
+     * This only handles URL-based <GetComplete/> references.
+     *
+     * @param getComplete
+     *            The (possibly <code>null</code>) &lt;GetComplete/&gt;
+     *            element
+     *
+     * @return an {@link IDPList} or <code>null</code> if the uri can't be
+     *         dereferenced.
+     */
+    protected IDPList getCompleteIDPList(final GetComplete getComplete) {
+        
+        // XXX: enhance this method to cache the url and last-modified-header
+        
+        if (getComplete == null) {
+            return null;
+        }
+        
+        String uri = getComplete.getGetComplete();
+        if (uri != null) {
+            return null;
+        }
+        
+        IDPList idpList = null;
+        InputStream istream = null;
+        
+        try {
+            URL url = new URL(uri);
+            URLConnection conn = url.openConnection();
+            istream = conn.getInputStream();
+            
+            // convert the raw data into an XML object
+            Document doc = parserPool.parse(istream);
+            Element docElement = doc.getDocumentElement();
+            Unmarshaller unmarshaller = Configuration.getUnmarshallerFactory()
+            .getUnmarshaller(docElement);
+            idpList = (IDPList) unmarshaller.unmarshall(docElement);
+            
+        } catch (MalformedURLException ex) {
+            log.error(
+                    "Unable to retrieve GetComplete IDPList. Unsupported URI: "
+                    + uri, ex);
+        } catch (IOException ex) {
+            log.error("IO Error while retreieving GetComplete IDPList from "
+                    + uri, ex);
+        } catch (ConfigurationException ex) {
+            log.error(
+                    "Internal OpenSAML error while parsing GetComplete IDPList from "
+                    + uri, ex);
+        } catch (XMLParserException ex) {
+            log.error(
+                    "Internal OpenSAML error while parsing GetComplete IDPList from "
+                    + uri, ex);
+        } catch (UnmarshallingException ex) {
+            log.error(
+                    "Internal OpenSAML error while unmarshalling GetComplete IDPList from "
+                    + uri, ex);
+        } finally {
+            
+            if (istream != null) {
+                try {
+                    istream.close();
+                } catch (IOException ex) {
+                    // pass
+                }
+            }
+        }
+        
+        return idpList;
+    }
 }
index cf942a0..2a5a3d9 100644 (file)
@@ -19,9 +19,11 @@ package edu.internet2.middleware.shibboleth.idp.profile.saml2;
 import java.util.Collection;
 
 import org.joda.time.DateTime;
+
 import org.opensaml.common.SAMLObjectBuilder;
 import org.opensaml.common.SAMLVersion;
 import org.opensaml.common.impl.SAMLObjectContentReference;
+
 import org.opensaml.saml2.core.Advice;
 import org.opensaml.saml2.core.Assertion;
 import org.opensaml.saml2.core.Audience;
@@ -36,6 +38,7 @@ import org.opensaml.saml2.core.StatusCode;
 import org.opensaml.saml2.core.StatusMessage;
 import org.opensaml.saml2.core.StatusResponseType;
 import org.opensaml.saml2.core.Subject;
+
 import org.opensaml.xml.XMLObjectBuilder;
 import org.opensaml.xml.encryption.EncryptionException;
 import org.opensaml.xml.security.credential.Credential;
@@ -51,192 +54,193 @@ import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandle
  * Common implementation details for profile handlers.
  */
 public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHandler {
-
+    
+    /** SAML Version for this profile handler. */
+    public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
+    
+    /** URI for the SAML 2 protocol. */
+    public static final String SAML20_PROTOCOL_URI = "urn:oasis:names:tc:SAML:2.0:protocol";
+    
     /** For building response. */
     private SAMLObjectBuilder<Response> responseBuilder;
-
+    
     /** For building status. */
     private SAMLObjectBuilder<Status> statusBuilder;
-
+    
     /** For building statuscode. */
     private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
-
+    
     /** For building StatusMessages. */
     private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
-
+    
     /** For building assertion. */
     private SAMLObjectBuilder<Assertion> assertionBuilder;
-
+    
     /** For building issuer. */
     private SAMLObjectBuilder<Issuer> issuerBuilder;
-
+    
     /** For building subject. */
     private SAMLObjectBuilder<Subject> subjectBuilder;
-
+    
     /** For building conditions. */
     private SAMLObjectBuilder<Conditions> conditionsBuilder;
-
+    
     /** For building audience restriction. */
     private SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder;
-
+    
     /** For building proxy retrictions. */
     private SAMLObjectBuilder<ProxyRestriction> proxyRestrictionBuilder;
-
+    
     /** For building audience. */
     private SAMLObjectBuilder<Audience> audienceBuilder;
-
+    
     /** For building advice. */
     private SAMLObjectBuilder<Advice> adviceBuilder;
-
+    
     /** For building signature. */
     private XMLObjectBuilder<Signature> signatureBuilder;
-
+    
     /** Constructor. */
     @SuppressWarnings("unchecked")
     protected AbstractSAML2ProfileHandler() {
+        
         super();
-
-        responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
-        statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
-        statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
-                StatusCode.DEFAULT_ELEMENT_NAME);
-        statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
-                StatusMessage.DEFAULT_ELEMENT_NAME);
-        issuerBuilder = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
-        assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
-                .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
-        subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
-        conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
-                Conditions.DEFAULT_ELEMENT_NAME);
-        audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(
-                AudienceRestriction.DEFAULT_ELEMENT_NAME);
-        proxyRestrictionBuilder = (SAMLObjectBuilder<ProxyRestriction>) getBuilderFactory().getBuilder(
-                ProxyRestriction.DEFAULT_ELEMENT_NAME);
-        audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
-        adviceBuilder = (SAMLObjectBuilder<Advice>) getBuilderFactory().getBuilder(Advice.DEFAULT_ELEMENT_NAME);
-        signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
+        
+        responseBuilder            = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
+        statusBuilder              = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
+        statusCodeBuilder          = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
+        statusMessageBuilder       = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(StatusMessage.DEFAULT_ELEMENT_NAME);
+        issuerBuilder              = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
+        assertionBuilder           = (SAMLObjectBuilder<Assertion>) getBuilderFactory().getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
+        subjectBuilder             = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
+        conditionsBuilder          = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(Conditions.DEFAULT_ELEMENT_NAME);
+        audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(AudienceRestriction.DEFAULT_ELEMENT_NAME);
+        proxyRestrictionBuilder    = (SAMLObjectBuilder<ProxyRestriction>) getBuilderFactory().getBuilder(ProxyRestriction.DEFAULT_ELEMENT_NAME);
+        audienceBuilder            = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
+        adviceBuilder              = (SAMLObjectBuilder<Advice>) getBuilderFactory().getBuilder(Advice.DEFAULT_ELEMENT_NAME);
+        signatureBuilder           = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
     }
-
+    
     /**
      * Convenience method for getting the SAML 2 advice builder.
-     * 
+     *
      * @return SAML 2 advice builder
      */
     public SAMLObjectBuilder<Advice> getAdviceBuilder() {
         return adviceBuilder;
     }
-
+    
     /**
      * Convenience method for getting the SAML 2 assertion builder.
-     * 
+     *
      * @return SAML 2 assertion builder
      */
     public SAMLObjectBuilder<Assertion> getAssertionBuilder() {
         return assertionBuilder;
     }
-
+    
     /**
      * Convenience method for getting the SAML 2 audience builder.
-     * 
+     *
      * @return SAML 2 audience builder
      */
     public SAMLObjectBuilder<Audience> getAudienceBuilder() {
         return audienceBuilder;
     }
-
+    
     /**
      * Convenience method for getting the SAML 2 audience restriction builder.
-     * 
+     *
      * @return SAML 2 audience restriction builder
      */
     public SAMLObjectBuilder<AudienceRestriction> getAudienceRestrictionBuilder() {
         return audienceRestrictionBuilder;
     }
-
+    
     /**
      * Convenience method for getting the SAML 2 conditions builder.
-     * 
+     *
      * @return SAML 2 conditions builder
      */
     public SAMLObjectBuilder<Conditions> getConditionsBuilder() {
         return conditionsBuilder;
     }
-
+    
     /**
      * Convenience method for getting the SAML 2 Issuer builder.
-     * 
+     *
      * @return SAML 2 Issuer builder
      */
     public SAMLObjectBuilder<Issuer> getIssuerBuilder() {
         return issuerBuilder;
     }
-
+    
     /**
      * Convenience method for getting the SAML 2 proxy restriction builder.
-     * 
+     *
      * @return SAML 2 proxy restriction builder
      */
     public SAMLObjectBuilder<ProxyRestriction> getProxyRestrictionBuilder() {
         return proxyRestrictionBuilder;
     }
-
+    
     /**
      * Convenience method for getting the SAML 2 response builder.
-     * 
+     *
      * @return SAML 2 response builder
      */
     public SAMLObjectBuilder<Response> getResponseBuilder() {
         return responseBuilder;
     }
-
+    
     /**
      * Convenience method for getting the Signature builder.
-     * 
+     *
      * @return signature builder
      */
     public XMLObjectBuilder<Signature> getSignatureBuilder() {
         return signatureBuilder;
     }
-
+    
     /**
      * Convenience method for getting the SAML 2 status builder.
-     * 
+     *
      * @return SAML 2 status builder
      */
     public SAMLObjectBuilder<Status> getStatusBuilder() {
         return statusBuilder;
     }
-
+    
     /**
      * Convenience method for getting the SAML 2 status code builder.
-     * 
+     *
      * @return SAML 2 status code builder
      */
     public SAMLObjectBuilder<StatusCode> getStatusCodeBuilder() {
         return statusCodeBuilder;
     }
-
+    
     /**
      * Convenience method for getting the SAML 2 status message builder.
-     * 
+     *
      * @return SAML 2 status message builder
      */
     public SAMLObjectBuilder<StatusMessage> getStatusMessageBuilder() {
         return statusMessageBuilder;
     }
-
+    
     /**
      * Convenience method for getting the SAML 2 subject builder.
-     * 
+     *
      * @return SAML 2 subject builder
      */
     public SAMLObjectBuilder<Subject> getSubjectBuilder() {
         return subjectBuilder;
     }
-
+    
     /**
      * Populates the response's id, in response to, issue instant, version, and issuer properties.
-     * 
+     *
      * @param response the response to populate
      * @param issueInstant timestamp to use as the issue instant for the response
      * @param request the request that the response is for
@@ -244,121 +248,133 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      */
     protected void populateStatusResponse(StatusResponseType response, DateTime issueInstant,
             RequestAbstractType request, RelyingPartyConfiguration rpConfig) {
+        
         response.setID(getIdGenerator().generateIdentifier());
         response.setInResponseTo(request.getID());
         response.setIssueInstant(issueInstant);
         response.setVersion(SAMLVersion.VERSION_20);
         response.setIssuer(buildEntityIssuer(rpConfig));
     }
-
+    
     /**
-     * Builds a {@link Status} object populated with the given code and message.
-     * 
-     * @param statusCode status code or null
-     * @param statusMessage status message or null
-     * 
-     * @return built status object
+     * Build a status message, with an optional second-level failure message.
+     *
+     * @param topLevelCode
+     *            The top-level status code. Should be from saml-core-2.0-os,
+     *            sec. 3.2.2.2
+     * @param secondLevelCode
+     *            An optional second-level failure code. Should be from
+     *            saml-core-2.0-is, sec 3.2.2.2. If null, no second-level Status
+     *            element will be set.
+     * @param secondLevelFailureMessage
+     *            An optional second-level failure message.
+     *
+     * @return a Status object.
      */
-    protected Status buildStatus(String statusCode, String statusMessage) {
-        Status status = getStatusBuilder().buildObject();
-
-        String trimmedCode = DatatypeHelper.safeTrimOrNullString(statusCode);
-        if (trimmedCode != null) {
-            StatusCode code = getStatusCodeBuilder().buildObject();
-            code.setValue(trimmedCode);
-            status.setStatusCode(code);
+    protected Status buildStatus(String topLevelCode, String secondLevelCode,
+            String secondLevelFailureMessage) {
+        
+        Status status = statusBuilder.buildObject();
+        StatusCode statusCode = statusCodeBuilder.buildObject();
+        
+        statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
+        if (secondLevelCode != null) {
+            StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
+            secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
+            statusCode.setStatusCode(secondLevelStatusCode);
         }
-
-        String trimmedMessage = DatatypeHelper.safeTrimOrNullString(statusMessage);
-        if (trimmedMessage != null) {
-            StatusMessage message = getStatusMessageBuilder().buildObject();
-            message.setMessage(trimmedMessage);
-            status.setStatusMessage(message);
+        
+        if (secondLevelFailureMessage != null) {
+            StatusMessage msg = statusMessageBuilder.buildObject();
+            msg.setMessage(secondLevelFailureMessage);
+            status.setStatusMessage(msg);
         }
-
+        
         return status;
     }
-
+    
     /**
      * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
-     * 
+     *
      * @param issueInstant time to use as assertion issue instant
      * @param rpConfig the relying party configuration
      * @param profileConfig current profile configuration
-     * 
+     *
      * @return the built assertion
      */
-    protected Assertion buildAssertion(DateTime issueInstant, RelyingPartyConfiguration rpConfig,
-            AbstractSAML2ProfileConfiguration profileConfig) {
+    protected Assertion buildAssertion(final DateTime issueInstant, final RelyingPartyConfiguration rpConfig,
+            final AbstractSAML2ProfileConfiguration profileConfig) {
+        
         Assertion assertion = assertionBuilder.buildObject();
         assertion.setID(getIdGenerator().generateIdentifier());
         assertion.setIssueInstant(issueInstant);
         assertion.setVersion(SAMLVersion.VERSION_20);
         assertion.setIssuer(buildEntityIssuer(rpConfig));
         //TODO assertion.setSubject(buildSubject());
-
+        
         Conditions conditions = buildConditions(issueInstant, profileConfig);
         assertion.setConditions(conditions);
-
+        
         return assertion;
     }
-
+    
     /**
      * Builds an entity type Issuer populated with the correct provider Id for this relying party configuration.
-     * 
+     *
      * @param rpConfig the relying party configuration
-     * 
+     *
      * @return the built Issuer
      */
-    protected Issuer buildEntityIssuer(RelyingPartyConfiguration rpConfig) {
+    protected Issuer buildEntityIssuer(final RelyingPartyConfiguration rpConfig) {
+        
         Issuer issuer = getIssuerBuilder().buildObject();
         issuer.setFormat(Issuer.ENTITY);
         issuer.setValue(rpConfig.getProviderId());
-
+        
         return issuer;
     }
-
+    
     /**
      * Builds the SAML subject for the user for the service provider.
-     * 
+     *
      * @return SAML subject for the user for the service provider
-     * 
+     *
      * @throws EncryptionException thrown if there is a problem encryption the subject's NameID
      */
     protected Subject buildSubject() throws EncryptionException {
         // TODO
         return null;
     }
-
+    
     /**
      * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
      * restrictions, and proxy restrictions.
-     * 
+     *
      * @param issueInstant timestamp the assertion was created
      * @param profileConfig current profile configuration
-     * 
+     *
      * @return constructed conditions
      */
-    private Conditions buildConditions(DateTime issueInstant, AbstractSAML2ProfileConfiguration profileConfig) {
+    private Conditions buildConditions(final DateTime issueInstant, final AbstractSAML2ProfileConfiguration profileConfig) {
+        
         Conditions conditions = conditionsBuilder.buildObject();
         conditions.setNotBefore(issueInstant);
         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
-
+        
         Collection<String> audiences;
-
+        
         // add audience restrictions
         audiences = profileConfig.getAssertionAudiences();
         if (audiences != null && audiences.size() > 0) {
             AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject();
-            Audience audience;
             for (String audienceUri : audiences) {
-                audience = audienceBuilder.buildObject();
+                Audience audience = audienceBuilder.buildObject();
                 audience.setAudienceURI(audienceUri);
                 audienceRestriction.getAudiences().add(audience);
             }
             conditions.getAudienceRestrictions().add(audienceRestriction);
         }
-
+        
         // add proxy restrictions
         audiences = profileConfig.getProxyAudiences();
         if (audiences != null && audiences.size() > 0) {
@@ -369,18 +385,18 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
                 audience.setAudienceURI(audienceUri);
                 proxyRestriction.getAudiences().add(audience);
             }
-
+            
             proxyRestriction.setProxyCount(profileConfig.getProxyCount());
             conditions.getConditions().add(proxyRestriction);
         }
-
+        
         return conditions;
     }
-
+    
     /**
      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
      * signing credentials.
-     * 
+     *
      * @param assertion assertion to sign
      * @param rpConfig relying party configuration
      * @param profileConfig current profile configuration
@@ -390,21 +406,21 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         if (!profileConfig.getSignAssertions()) {
             return;
         }
-
+        
         Credential signatureCredential = profileConfig.getSigningCredential();
         if (signatureCredential == null) {
             signatureCredential = rpConfig.getDefaultSigningCredential();
         }
-
+        
         if (signatureCredential == null) {
             return;
         }
-
+        
         SAMLObjectContentReference contentRef = new SAMLObjectContentReference(assertion);
         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
         signature.getContentReferences().add(contentRef);
         assertion.setSignature(signature);
-
+        
         Signer.signObject(signature);
     }
     
@@ -412,23 +428,23 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         if (!profileConfig.getSignResponses()) {
             return;
         }
-
+        
         Credential signatureCredential = profileConfig.getSigningCredential();
         if (signatureCredential == null) {
             signatureCredential = rpConfig.getDefaultSigningCredential();
         }
-
+        
         if (signatureCredential == null) {
             return;
         }
-
+        
         SAMLObjectContentReference contentRef = new SAMLObjectContentReference(response);
         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
         signature.getContentReferences().add(contentRef);
         response.setSignature(signature);
-
+        
         Signer.signObject(signature);
     }
-
+    
     // TODO encryption support
 }
\ No newline at end of file
index 8f8adf8..ebe2d94 100644 (file)
@@ -42,106 +42,88 @@ import org.opensaml.saml2.metadata.SPSSODescriptor;
 /**
  * Browser POST binding for SAML 2 AuthenticationRequest.
  */
-public class AuthenticationRequestBrowserPost extends
-               AbstractAuthenticationRequest {
-
-       /** Class logger. */
-       private static final Logger log = Logger
-                       .getLogger(AuthenticationRequestBrowserPost.class);
-
-       /** Constructor. */
-       public AuthenticationRequestBrowserPost() {
-               super();
-       }
-
-       /** {@inheritDoc} */
-       public void processRequest(final ProfileRequest<ServletRequest> request,
-                       final ProfileResponse<ServletResponse> response)
-                       throws ProfileException {
-
-               // Only http servlets are supported for now.
-               if (!(request.getRawRequest() instanceof HttpServletRequest)) {
-                       log.error("Received a non-HTTP request");
-                       throw new ServletException("Received a non-HTTP request");
-               }
-
-               HttpServletRequest httpReq = (HttpServletRequest) request
-                               .getRawRequest();
-               HttpServletResponse httpResp = (HttpServletResponse) response
-                               .getRawResponse();
-               HttpSession httpSession = httpReq.getSession();
-
-               AuthnRequest authnRequest;
-               Issuer issuer;
-               RelyingPartyConfiguration relyingParty;
-               SSOConfiguration ssoConfig;
-               SPSSODescriptor spDescriptor;
-
-               // If the user hasn't been authenticated, validate the AuthnRequest
-               // and redirect to AuthenticationManager to authenticate the user.
-               if (!hasUserAuthenticated(httpSession)) {
-
-                       try {
-                               MessageDecoder decoder = new HTTPPostDecoder();
-                               decoder
-                                               .setMetadataProvider(getRelyingPartyConfigurationManager()
-                                                               .getMetadataProvider());
-                               // decoder.setSecurityPolicy(??);
-                               // decoder.setTrustEngine(??);
-                               decoder.setRequest(httpReq);
-                               decoder.decode();
-                               SAMLObject samlObject = decoder.getSAMLMessage();
-                               if (!(samlObject instanceof AuthnRequest)) {
-                                       log
-                                                       .error("SAML 2 AuthnRequest: Received message is not a SAML 2 Authentication Request");
-                                       throw new ProfileException(
-                                                       "SAML 2 AuthnRequest: Received message is not a SAML 2 Authentication Request");
-                               }
-
-                               authnRequest = (AuthnRequest) samlObject;
-                               issuer = decoder.getSecurityPolicy().getIssuer();
-
-                               if (!findMetadataForSSORequest(issuer, relyingParty, ssoConfig,
-                                               spDescriptor)) {
-                                       throw new ProfileException(
-                                                       "SAML 2 AuthnRequest: Unable to locate metadata for issuer: "
-                                                                       + issuer.getSPProvidedID());
-                               }
-
-                               verifyAuthnRequest(authnRequest, issuer, relyingParty,
-                                               httpSession);
-                               storeRequestData(httpSession, authnRequest, issuer,
-                                               relyingParty, ssoConfig, spDescriptor);
-                               authenticateUser(authnRequest, httpSession, httpReq, httpResp);
-
-                       } catch (BindingException ex) {
-                               log
-                                               .error(
-                                                               "SAML 2 Authentication Request: Unable to decode SAML 2 Authenticaiton Request",
-                                                               ex);
-                               throw new ProfileException(
-                                               "SAML 2 Authentication Request: Unable to decode SAML 2 Authenticaiton Request",
-                                               ex);
-                       } catch (AuthenticationRequestException ex) {
-                               // XXX: todo: generate and send the error, with a REQUEST_URI
-                               // failure.
-                       }
-               }
-
-               // The user has already been authenticated,
-               // so generate an AuthenticationStatement.
-               retrieveRequestData(httpSession, authnRequest, issuer, relyingParty,
-                               ssoConfig, spDescriptor);
-               Response samlResponse = evaluateRequest(authnRequest, issuer,
-                               httpSession, relyingParty, ssoConfig, spDescriptor);
-               encodeResponse(response, samlResponse, relyingParty, ssoConfig,
-                               spDescriptor);
-       }
-
-       protected void encodeResponse(final ProfileResponse response,
-                       final Response samlResponse,
-                       final RelyingPartyConfiguration relyingParty,
-                       final SSOConfiguration ssoConfig, final SPSSODescriptor spDescriptor) {
-               // xxx: todo
-       }
+public class AuthenticationRequestBrowserPost extends AbstractAuthenticationRequest {
+    
+    /** Class logger. */
+    private static final Logger log = Logger.getLogger(AuthenticationRequestBrowserPost.class);
+    
+    /** Constructor. */
+    public AuthenticationRequestBrowserPost() {
+        super();
+    }
+    
+    /** {@inheritDoc} */
+    public void processRequest(final ProfileRequest<ServletRequest> request,
+            final ProfileResponse<ServletResponse> response)
+            throws ProfileException {
+        
+        // Only http servlets are supported for now.
+        if (!(request.getRawRequest() instanceof HttpServletRequest)) {
+            log.error("Received a non-HTTP request");
+            throw new ServletException("Received a non-HTTP request");
+        }
+        
+        HttpServletRequest httpReq = (HttpServletRequest) request.getRawRequest();
+        HttpServletResponse httpResp = (HttpServletResponse) response.getRawResponse();
+        HttpSession httpSession = httpReq.getSession();
+        
+        AuthnRequest authnRequest;
+        Issuer issuer;
+        RelyingPartyConfiguration relyingParty;
+        SSOConfiguration ssoConfig;
+        SPSSODescriptor spDescriptor;
+        
+        // If the user hasn't been authenticated, validate the AuthnRequest
+        // and redirect to AuthenticationManager to authenticate the user.
+        if (!hasUserAuthenticated(httpSession)) {
+            
+            try {
+                MessageDecoder<HttpServletRequest> decoder = new HTTPPostDecoder();
+                decoder.setMetadataProvider(getRelyingPartyConfigurationManager().getMetadataProvider());
+                // decoder.setSecurityPolicy(??);
+                // decoder.setTrustEngine(??);
+                decoder.setRequest(httpReq);
+                decoder.decode();
+                SAMLObject samlObject = decoder.getSAMLMessage();
+                if (!(samlObject instanceof AuthnRequest)) {
+                    log.error("SAML 2 AuthnRequest: Received message is not a SAML 2 Authentication Request");
+                    throw new ProfileException("SAML 2 AuthnRequest: Received message is not a SAML 2 Authentication Request");
+                }
+                
+                authnRequest = (AuthnRequest) samlObject;
+                issuer = (Issuer) decoder.getSecurityPolicy().getIssuer();
+                
+                if (!findMetadataForSSORequest(issuer, relyingParty, ssoConfig, spDescriptor)) {
+                    throw new ProfileException(
+                            "SAML 2 AuthnRequest: Unable to locate metadata for issuer: "
+                            + issuer.getSPProvidedID());
+                }
+                
+                verifyAuthnRequest(authnRequest, issuer, relyingParty, httpSession);
+                storeRequestData(httpSession, authnRequest, issuer, relyingParty, ssoConfig, spDescriptor);
+                authenticateUser(authnRequest, httpSession, httpReq, httpResp);
+                
+            } catch (BindingException ex) {
+                log.error("SAML 2 Authentication Request: Unable to decode SAML 2 Authentication Request", ex);
+                throw new ProfileException(
+                        "SAML 2 Authentication Request: Unable to decode SAML 2 Authentication Request", ex);
+            } catch (AuthenticationRequestException ex) {
+                // XXX: todo: generate and send the error, with a REQUEST_URI
+                // failure.
+            }
+        }
+        
+        // The user has already been authenticated,
+        // so generate an AuthenticationStatement.
+        retrieveRequestData(httpSession, authnRequest, issuer, relyingParty, ssoConfig, spDescriptor);
+        Response samlResponse = evaluateRequest(authnRequest, issuer, httpSession, relyingParty, ssoConfig, spDescriptor);
+        encodeResponse(response, samlResponse, relyingParty, ssoConfig, spDescriptor);
+    }
+    
+    protected void encodeResponse(final ProfileResponse response,
+            final Response samlResponse,
+            final RelyingPartyConfiguration relyingParty,
+            final SSOConfiguration ssoConfig, final SPSSODescriptor spDescriptor) {
+        // xxx: todo
+    }
 }