fixes for saml 2 authnreq
authordmorr <dmorr@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Fri, 20 Apr 2007 22:44:06 +0000 (22:44 +0000)
committerdmorr <dmorr@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Fri, 20 Apr 2007 22:44:06 +0000 (22:44 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@2183 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AbstractAuthenticationRequest.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AbstractSAML2ProfileHandler.java
src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AuthenticationRequest.java [deleted file]
src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AuthenticationRequestBrowserPost.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AuthenticationRequestException.java
src/edu/internet2/middleware/shibboleth/idp/profile/saml2/LogoutRequest.java

diff --git a/src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AbstractAuthenticationRequest.java b/src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AbstractAuthenticationRequest.java
new file mode 100644 (file)
index 0000000..266eea5
--- /dev/null
@@ -0,0 +1,953 @@
+/*
+ * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package edu.internet2.middleware.shibboleth.idp.profile.saml2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+import org.opensaml.Configuration;
+import org.opensaml.common.SAMLObjectBuilder;
+import org.opensaml.common.binding.ArtifactMap;
+import org.opensaml.common.binding.SAMLArtifactFactory;
+import org.opensaml.saml2.core.Assertion;
+import org.opensaml.saml2.core.AuthnContext;
+import org.opensaml.saml2.core.AuthnContextClassRef;
+import org.opensaml.saml2.core.AuthnContextDeclRef;
+import org.opensaml.saml2.core.AuthnRequest;
+import org.opensaml.saml2.core.AuthnStatement;
+import org.opensaml.saml2.core.Condition;
+import org.opensaml.saml2.core.Conditions;
+import org.opensaml.saml2.core.GetComplete;
+import org.opensaml.saml2.core.IDPEntry;
+import org.opensaml.saml2.core.IDPList;
+import org.opensaml.saml2.core.Issuer;
+import org.opensaml.saml2.core.RequestedAuthnContext;
+import org.opensaml.saml2.core.Response;
+import org.opensaml.saml2.core.Scoping;
+import org.opensaml.saml2.core.Status;
+import org.opensaml.saml2.core.StatusCode;
+import org.opensaml.saml2.core.Subject;
+import org.opensaml.saml2.core.SubjectConfirmation;
+import org.opensaml.saml2.metadata.AssertionConsumerService;
+import org.opensaml.saml2.metadata.SPSSODescriptor;
+import org.opensaml.saml2.metadata.provider.MetadataProvider;
+import org.opensaml.saml2.metadata.provider.MetadataProviderException;
+import org.opensaml.xml.ConfigurationException;
+import org.opensaml.xml.io.Unmarshaller;
+import org.opensaml.xml.io.UnmarshallingException;
+import org.opensaml.xml.parse.BasicParserPool;
+import org.opensaml.xml.parse.ParserPool;
+import org.opensaml.xml.parse.XMLParserException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+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.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;
+       }
+}
index c368479..3a51587 100644 (file)
@@ -25,16 +25,17 @@ import org.apache.log4j.Logger;
 import org.joda.time.DateTime;
 import org.opensaml.Configuration;
 import org.opensaml.common.IdentifierGenerator;
-import org.opensaml.common.SAMLObject;
 import org.opensaml.common.SAMLObjectBuilder;
 import org.opensaml.common.SAMLVersion;
-import org.opensaml.common.binding.BindingException;
-import org.opensaml.common.binding.MessageDecoder;
-import org.opensaml.common.binding.MessageEncoder;
 import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
+import org.opensaml.saml2.core.AuthnContext;
+import org.opensaml.saml2.core.AuthnContextClassRef;
+import org.opensaml.saml2.core.AuthnContextDeclRef;
+import org.opensaml.saml2.core.AuthnStatement;
 import org.opensaml.saml2.core.Assertion;
 import org.opensaml.saml2.core.Audience;
 import org.opensaml.saml2.core.AudienceRestriction;
+import org.opensaml.saml2.core.AuthnStatement;
 import org.opensaml.saml2.core.Conditions;
 import org.opensaml.saml2.core.Issuer;
 import org.opensaml.saml2.core.Response;
@@ -42,7 +43,6 @@ import org.opensaml.saml2.core.Status;
 import org.opensaml.saml2.core.StatusCode;
 import org.opensaml.saml2.core.StatusMessage;
 import org.opensaml.saml2.core.Subject;
-import org.opensaml.xml.XMLObjectBuilder;
 import org.opensaml.xml.XMLObjectBuilderFactory;
 
 import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
@@ -50,171 +50,282 @@ 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;
-
-    /** Class logger. */
-    private static Logger log = Logger.getLogger(AbstractSAML2ProfileHandler.class);
-
-    /** For building XML. */
-    private XMLObjectBuilderFactory builderFactory;
-
-    /** For generating random ids. */
-    private IdentifierGenerator idGenerator;
-
-    /** Builder for Response elements. */
-    //protected SAMLObjectBuilder<Response> responseBuilder;
-
-    /** Builder for Status elements. */
-    //private SAMLObjectBuilder<Status> statusBuilder;
-
-    /** Builder for StatusCode elements. */
-    //private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
-
-    /** Builder for StatusMessage elements. */
-    //private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
-
-    /** Builder for Issuer elements. */
-    //protected SAMLObjectBuilder<Issuer> issuerBuilder;
-
-    /**
-     * Default constructor.
-     */
-    public AbstractSAML2ProfileHandler() {
-        builderFactory = Configuration.getBuilderFactory();
-        idGenerator = new SecureRandomIdentifierGenerator();
-
-        /*
-        responseBuilder = (SAMLObjectBuilder<Response>) builderFactory.getBuilder(Response.DEFAULT_ELEMENT_NAME);
-        statusBuilder = (SAMLObjectBuilder<Status>) builderFactory.getBuilder(Status.DEFAULT_ELEMENT_NAME);
-        statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) builderFactory.getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
-        statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) builderFactory
-                .getBuilder(StatusMessage.DEFAULT_ELEMENT_NAME);
-        issuerBuilder = (SAMLObjectBuilder<Issuer>) builderFactory.getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
-        */
-    }
-
-    /**
-     * Returns the XML builder factory.
-     * 
-     * @return Returns the builderFactory.
-     */
-    public XMLObjectBuilderFactory getBuilderFactory() {
-        return builderFactory;
-    }
-
-    /**
-     * Returns the id generator.
-     * 
-     * @return Returns the idGenerator.
-     */
-    public IdentifierGenerator getIdGenerator() {
-        return idGenerator;
-    }
-
-    /**
-     * 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 topLevelCode, String secondLevelCode, String secondLevelFailureMessage) {
-
-        Status status = statusBuilder.buildObject();
-        StatusCode statusCode = statusCodeBuilder.buildObject();
-
-        statusCode.setValue(topLevelCode);
-        if (secondLevelCode != null) {
-            StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
-            secondLevelStatusCode.setValue(secondLevelCode);
-            statusCode.setStatusCode(secondLevelStatusCode);
-        }
-
-        if (secondLevelFailureMessage != null) {
-            StatusMessage msg = statusMessageBuilder.buildObject();
-            msg.setMessage(secondLevelFailureMessage);
-            status.setStatusMessage(msg);
-        }
-
-        return status;
-    }
-    */
-
-    /**
-     * 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, DateTime issueInstant, String issuer, final Status status) {
-
-        Response response = responseBuilder.buildObject();
-
-        Issuer i = issuerBuilder.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;
-    }
-    */
-
-    /*
-    protected Assertion buildAssertion(final Subject subject, final Conditions conditions, String issuer,
-            final String[] audiences) {
-
-        Assertion assertion = (Assertion) assertionBuilder.buildObject();
-        assertion.setID(getIdGenerator().generateIdentifier());
-        assertion.setVersion(SAML_VERSION);
-        assertion.setIssueInstant(new DateTime());
-        assertion.setConditions(conditions);
-        assertion.setSubject(subject);
-
-        Issuer i = (Issuer) issuerBuilder.buildObject();
-        i.setValue(issuer);
-        assertion.setIssuer(i);
-
-        // if audiences were specified, set an AudienceRestriction condition
-        if (audiences != null && audiences.length > 0) {
-
-            Conditions conditions = assertion.getConditions();
-            List<AudienceRestriction> audienceRestrictionConditions = conditions.getAudienceRestrictions();
-
-            for (String audienceURI : audiences) {
-
-                Audience audience = audienceBuilder.buildObject();
-                audience.setAudienceURI(audienceURI);
-
-                AudienceRestriction audienceRestriction = audienceRestrictionBuilder
-                        .buildObject();
-                List<Audience> audienceList = audienceRestriction.getAudiences();
-                audienceList.add(audience);
-
-                audienceRestrictionConditions.add(audienceRestriction);
-            }
-        }
-
-        return assertion;
-    }
-    */
+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";
+
+       /** Class logger. */
+       private static Logger log = Logger
+                       .getLogger(AbstractSAML2ProfileHandler.class);
+
+       /** For building XML. */
+       private XMLObjectBuilderFactory builderFactory;
+
+       /** For generating random ids. */
+       private IdentifierGenerator idGenerator;
+
+       /** Builder for Response elements. */
+       protected SAMLObjectBuilder<Response> responseBuilder;
+
+       /** Builder for Status elements. */
+       protected SAMLObjectBuilder<Status> statusBuilder;
+
+       /** Builder for StatusCode elements. */
+       protected SAMLObjectBuilder<StatusCode> statusCodeBuilder;
+
+       /** Builder for StatusMessage elements. */
+       protected SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
+
+       /** Builder for Issuer elements. */
+       protected SAMLObjectBuilder<Issuer> issuerBuilder;
+
+       /** Builder for Assertion elements. */
+       protected SAMLObjectBuilder<Assertion> assertionBuilder;
+
+       /** Builder for Condition elements. */
+       protected SAMLObjectBuilder<Conditions> conditionsBuilder;
+
+       /** Builder for AuthnStatement elements. */
+       protected SAMLObjectBuilder<AuthnStatement> authnStatementBuilder;
+
+       /** Builder for AuthnContext elements. */
+       protected SAMLObjectBuilder<AuthnContext> authnContextBuilder;
+
+       /** Builder for AuthnContextClassRef elements. */
+       protected SAMLObjectBuilder<AuthnContextClassRef> authnContextClassRefBuilder;
+
+       /** Builder for AuthnContextDeclRef elements. */
+       protected SAMLObjectBuilder<AuthnContextDeclRef> authnContextDeclRefBuilder;
+
+       /** Builder for AudienceRestriction conditions. */
+       protected SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder;
+
+       /** Builder for Audience elemenets. */
+       protected SAMLObjectBuilder<Audience> audienceBuilder;
+
+       /**
+        * Default constructor.
+        */
+       public AbstractSAML2ProfileHandler() {
+               builderFactory = Configuration.getBuilderFactory();
+               idGenerator = new SecureRandomIdentifierGenerator();
+
+               assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
+                               .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
+               authnStatementBuilder = (SAMLObjectBuilder<AuthnStatement>) getBuilderFactory()
+                               .getBuilder(AuthnStatement.DEFAULT_ELEMENT_NAME);
+               authnContextBuilder = (SAMLObjectBuilder<AuthnContext>) getBuilderFactory()
+                               .getBuilder(AuthnContext.DEFAULT_ELEMENT_NAME);
+               authnContextClassRefBuilder = (SAMLObjectBuilder<AuthnContextClassRef>) getBuilderFactory()
+                               .getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
+               authnContextDeclRefBuilder = (SAMLObjectBuilder<AuthnContextDeclRef>) getBuilderFactory()
+                               .getBuilder(AuthnContextDeclRef.DEFAULT_ELEMENT_NAME);
+               audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory()
+                               .getBuilder(AudienceRestriction.DEFAULT_ELEMENT_NAME);
+               audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory()
+                               .getBuilder(Audience.DEFAULT_ELEMENT_NAME);
+               conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory()
+                               .getBuilder(Conditions.DEFAULT_ELEMENT_NAME);
+               responseBuilder = (SAMLObjectBuilder<Response>) builderFactory
+                               .getBuilder(Response.DEFAULT_ELEMENT_NAME);
+               statusBuilder = (SAMLObjectBuilder<Status>) builderFactory
+                               .getBuilder(Status.DEFAULT_ELEMENT_NAME);
+               statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) builderFactory
+                               .getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
+               statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) builderFactory
+                               .getBuilder(StatusMessage.DEFAULT_ELEMENT_NAME);
+               issuerBuilder = (SAMLObjectBuilder<Issuer>) builderFactory
+                               .getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
+       }
+
+       /**
+        * Returns the XML builder factory.
+        * 
+        * @return Returns the builderFactory.
+        */
+       public XMLObjectBuilderFactory getBuilderFactory() {
+               return builderFactory;
+       }
+
+       /**
+        * Returns the id generator.
+        * 
+        * @return Returns the idGenerator.
+        */
+       public IdentifierGenerator getIdGenerator() {
+               return idGenerator;
+       }
+
+       /**
+        * 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 topLevelCode, String secondLevelCode,
+                       String secondLevelFailureMessage) {
+
+               Status status = statusBuilder.buildObject();
+               StatusCode statusCode = statusCodeBuilder.buildObject();
+
+               statusCode.setValue(topLevelCode);
+               if (secondLevelCode != null) {
+                       StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
+                       secondLevelStatusCode.setValue(secondLevelCode);
+                       statusCode.setStatusCode(secondLevelStatusCode);
+               }
+
+               if (secondLevelFailureMessage != null) {
+                       StatusMessage msg = statusMessageBuilder.buildObject();
+                       msg.setMessage(secondLevelFailureMessage);
+                       status.setStatusMessage(msg);
+               }
+
+               return status;
+       }
+
+       /**
+        * 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.
+        * 
+        * @return a Status object.
+        */
+       protected Status buildStatus(String topLevelCode,
+                       final StatusCode secondLevelCode) {
+
+               Status status = statusBuilder.buildObject();
+               StatusCode statusCode = statusCodeBuilder.buildObject();
+
+               statusCode.setValue(topLevelCode);
+               if (secondLevelCode != null) {
+                       statusCode.setStatusCode(secondLevelCode);
+               }
+
+               return status;
+       }
+
+       /**
+        * Build a StatusCode.
+        * 
+        * @param statusCode
+        *            The URI status code.
+        * @param message
+        *            The message; may be <code>null</code.
+        *
+        * @return a StatusCode object.
+        */
+       protected StatusCode buildStatusCode(String statusCode) {
+               return null;
+       }
+
+       /**
+        * 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 = responseBuilder.buildObject();
+
+               Issuer i = issuerBuilder.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;
+       }
+
+       /**
+        * Build a skeletal SAML 2 assertion.
+        * 
+        * Note, the caller may either set the audiences in the conditions argument,
+        * or pass a list of URIs to this method. If the latter option is chosen,
+        * this method will create the appropriate AudienceRestriction element.
+        * 
+        * @param subject
+        *            The Subject of the assertion.
+        * @param conditions
+        *            The conditions object.
+        * @param issuer
+        *            The URI of the RP issuing the assertion.
+        * @param audiences
+        *            A possibly null array of audience URIs for the assertion.
+        * 
+        * @return The assertion object.
+        */
+       protected Assertion buildAssertion(final Subject subject,
+                       final Conditions conditions, final Issuer issuer,
+                       final String[] audiences) {
+
+               Assertion assertion = assertionBuilder.buildObject();
+               assertion.setID(getIdGenerator().generateIdentifier());
+               assertion.setVersion(SAML_VERSION);
+               assertion.setIssueInstant(new DateTime());
+               assertion.setConditions(conditions);
+               assertion.setSubject(subject);
+
+               Issuer i = issuerBuilder.buildObject();
+               i.setValue(issuer.getValue());
+               assertion.setIssuer(i);
+
+               // if audiences were specified, set an AudienceRestriction condition
+               if (audiences != null && audiences.length > 0) {
+
+                       List<AudienceRestriction> audienceRestrictionConditions = assertion
+                                       .getConditions().getAudienceRestrictions();
+
+                       AudienceRestriction audienceRestriction = audienceRestrictionBuilder
+                                       .buildObject();
+                       audienceRestrictionConditions.add(audienceRestriction);
+
+                       List<Audience> audienceList = audienceRestriction.getAudiences();
+
+                       for (String audienceURI : audiences) {
+                               Audience audience = audienceBuilder.buildObject();
+                               audience.setAudienceURI(audienceURI);
+                               audienceList.add(audience);
+                       }
+               }
+
+               return assertion;
+       }
 }
diff --git a/src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AuthenticationRequest.java b/src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AuthenticationRequest.java
deleted file mode 100644 (file)
index 978993b..0000000
+++ /dev/null
@@ -1,719 +0,0 @@
-/*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package edu.internet2.middleware.shibboleth.idp.profile.saml2;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.LinkedList;
-import java.util.List;
-
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.apache.log4j.Logger;
-import org.opensaml.Configuration;
-import org.opensaml.common.binding.ArtifactMap;
-import org.opensaml.common.binding.BindingException;
-import org.opensaml.common.binding.SAMLArtifactFactory;
-import org.opensaml.saml2.core.Assertion;
-import org.opensaml.saml2.core.Audience;
-import org.opensaml.saml2.core.AudienceRestriction;
-import org.opensaml.saml2.core.AuthnContext;
-import org.opensaml.saml2.core.AuthnContextClassRef;
-import org.opensaml.saml2.core.AuthnContextDeclRef;
-import org.opensaml.saml2.core.AuthnRequest;
-import org.opensaml.saml2.core.AuthnStatement;
-import org.opensaml.saml2.core.GetComplete;
-import org.opensaml.saml2.core.IDPEntry;
-import org.opensaml.saml2.core.IDPList;
-import org.opensaml.saml2.core.Issuer;
-import org.opensaml.saml2.core.RequestedAuthnContext;
-import org.opensaml.saml2.core.Response;
-import org.opensaml.saml2.core.Scoping;
-import org.opensaml.saml2.core.Status;
-import org.opensaml.saml2.core.StatusCode;
-import org.opensaml.saml2.core.Subject;
-import org.opensaml.saml2.core.SubjectConfirmation;
-import org.opensaml.saml2.metadata.AssertionConsumerService;
-import org.opensaml.saml2.metadata.SPSSODescriptor;
-import org.opensaml.saml2.metadata.provider.MetadataProvider;
-import org.opensaml.saml2.metadata.provider.MetadataProviderException;
-import org.opensaml.xml.ConfigurationException;
-import org.opensaml.xml.XMLObjectBuilder;
-import org.opensaml.xml.io.Unmarshaller;
-import org.opensaml.xml.io.UnmarshallingException;
-import org.opensaml.xml.parse.BasicParserPool;
-import org.opensaml.xml.parse.ParserPool;
-import org.opensaml.xml.parse.XMLParserException;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-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.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;
-
-/**
- * SAML 2.0 Authentication Request profile handler
- */
-public class AuthenticationRequest extends AbstractSAML2ProfileHandler {
-
-    private static final Logger log = Logger.getLogger(AuthenticationRequest.class.getName());
-
-    /** SAML 2.0 protocol URI. */
-    public static final String SAML20_PROTOCOL_URI = "urn:oasis:names:tc:SAML:2.0:protocol";
-
-    /** The RelyingPartyManager. */
-    protected RelyingPartyConfigurationManager rpManager;
-
-    /**
-     * 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;
-
-    /** Builder for Assertion elements. */
-    protected XMLObjectBuilder assertionBuilder;
-
-    /** Builder for AuthnStatement elements. */
-    protected XMLObjectBuilder authnStatementBuilder;
-
-    /** Builder for AuthnContext elements. */
-    protected XMLObjectBuilder authnContextBuilder;
-
-    /** Builder for AuthnContextClassRef elements. */
-    protected XMLObjectBuilder authnContextClassRefBuilder;
-
-    /** Builder for AuthnContextDeclRef elements. */
-    protected XMLObjectBuilder authnContextDeclRefBuilder;
-
-    /** Builder for AudienceRestriction conditions. */
-    protected XMLObjectBuilder audienceRestrictionBuilder;
-
-    /** Builder for Audience elemenets. */
-    protected XMLObjectBuilder audienceBuilder;
-
-    /**
-     * Constructor.
-     */
-    public AuthenticationRequest() {
-
-        parserPool = new BasicParserPool();
-        artifactFactory = new SAMLArtifactFactory();
-
-        assertionBuilder = getBuilderFactory().getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
-        authnStatementBuilder = getBuilderFactory().getBuilder(AuthnStatment.DEFULT_ELEMENT_NAME);
-        authnContextBuilder = getBuilderFactory().getBuilder(AuthnContext.DEFAULT_ELEMENT_NAME);
-        authnContextClassRefBuilder = getBuilderFactory().getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
-        authnContextDeclRefBuilder = getBuilderFactory().getBuilder(AuthnContextDeclRef.DEFAULT_ELEMENT_NAME);
-        audienceRestrictionBuilder = getBuilderFactory().getBuilder(AudienceRestriction.DEFAULT_ELEMENT_NAME);
-        audienceBuilder = getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
-    }
-
-    /**
-     * Set the Authentication Mananger.
-     * 
-     * @param authnManager The IdP's AuthenticationManager.
-     */
-    public void setAuthenticationManager(AuthenticationManager authnManager) {
-        this.authnMgr = authnMgr;
-    }
-
-    /**
-     * Set the RelyingPartyManager.
-     * 
-     * @param rpManager The IdP's RelyingParyManager.
-     */
-    public void setRelyingPartyManager(RelyingPartyConfigurationManager rpManager) {
-        this.rpManager = rpManager;
-    }
-
-    /**
-     * Set the ArtifactMap.
-     * 
-     * @param artifactMap The IdP's ArtifactMap.
-     */
-    public void setArtifactMap(ArtifactMap artifactMap) {
-        this.artifactMap = artifactMap;
-    }
-
-    /** {@inheritDoc} */
-    public boolean processRequest(ProfileRequest request, ProfileResponse response) throws ServletException {
-
-        // Only http servlets are supported for now.
-        if (!(request.getRequest() instanceof HttpServletRequest)) {
-            log.error("Received a non-HTTP request from " + request.getRequest().getRemoteHost());
-            throw new ServletException("Received a non-HTTP request");
-        }
-
-        HttpServletRequest httpReq = (HttpServletRequest) request.getRequest();
-        HttpServletResponse httpResp = (HttpServletResponse) response.getResponse();
-        HttpSession httpSession = httpReq.getSession();
-
-        AuthnRequest authnRequest;
-        try {
-            // this will need to change
-            authnRequest = (org.opensaml.saml2.core.AuthnRequest) decodeMessage(request.getMessageDecoder(), request
-                    .getRequest());
-            // to accomodate the factory
-        } catch (BindingException ex) {
-            log.error("Unable to decode SAML 2 authentication request", ex);
-            throw new ServletException("Error decoding SAML 2 authentication request", ex);
-        }
-
-        Issuer issuer = authnRequest.getIssuer();
-        String providerId = authnRequest.getIssuer().getSPProvidedID();
-        RelyingPartyConfiguration relyingParty = rpManager.getRelyingPartyConfiguration(providerId);
-        SSOConfiguration ssoConfig = relyingParty.getProfileConfigurations().get(SSOConfiguration.PROFILE_ID);
-        SPSSODescriptor spDescriptor;
-
-        try {
-
-            // If the user hasn't been authenticated, validate the AuthnRequest
-            // and
-            // redirect to AuthenticationManager to authenticate them.
-            // Otherwise, the user has been authenticated, so generate an
-            // AuthenticationStatement.
-            if (!hasUserAuthenticated()) {
-                verifyAuthnRequest(authnRequest);
-                authenticateUser(authnRequest, httpSession, httpReq, httpResp);
-            }
-
-            // the user has been authenticated.
-            // check if the authentication was successful.
-
-            Saml2LoginContext loginCtx = getLoginContext(httpSession);
-            if (!loginCtx.getAuthenticationOK()) {
-                // if authentication failed, send the appropriate SAML error
-                // message.
-                String failureMessage = loginCtx.getAuthenticationFailureMessage();
-                Status failureStatus = getStatus(StatusCode.RESPONDER_URI, StatusCode.AUTHN_FAILED_URI, failureMessage);
-                Response response = buildResponse(authnRequest.getID(), relyingParty.getProviderID(), failureStatus);
-
-                // XXX: TODO: send the response.
-
-                return true;
-            }
-
-            // the user successfully authenticated. build an authentication
-            // assertion.
-            Response response = buildResponse(authnRequest.getID(), relyingParty.getProviderID(), buildStatus(
-                    StatusCode.SUCCESS_URI, null, null));
-
-            // XXX: don't blindly copy conditions.
-            Assertion assertion = buildAssertion(authnRequest.getSubject(), authnRequest.getConditions(),
-                    new String[] { relyingParty.getRelyingPartyID() });
-            setAuthenticationStatement(assertion, loginCtx, authnRequest);
-
-            response.getAssertions().add(assertion);
-
-            // XXX: send the assertion
-
-        } catch (AuthenticationRequestException ex) {
-
-            StatusCode errorStatus = ex.getStatusCode();
-            if (errorStatus == null) {
-                // if no explicit status code was set, assume the error was in
-                // the message.
-                errorStatus = buildStatus(StatusCode.REQUESTER_URI, null, null);
-                Response response = buildResponse(authnRequest.getID(), relyingParty.getProviderID(), failureStatus);
-                // XXX: TODO: send the response.
-            }
-
-        }
-
-        // build assertion
-        // add assertion to response
-        // send response
-
-        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);
-    }
-
-    /**
-     * 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.
-
-        loginCtx = new Saml2LoginContext(authnRequest);
-        loginCtx.setProfileHandlerURL(httpReq.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 LoginContext loginCtx,
-            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, loginCtx.getAuthenticationMethod());
-        if (authnCtx == null) {
-            log.error("Error respond to SAML 2 AuthnRequest " + authnRequest.getID()
-                    + " : Unable to determine authentication method");
-        }
-
-        AuthnStatement stmt = (AuthnStatement) authnStatementBuilder.buildObject(AuthnStatment.DEFAULT_ELEMENT_NAME);
-        stmt.setAuthnInstant(loginCtx.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, String authnMethod) {
-
-        // this method assumes that only one URI will match.
-
-        AuthnContext authnCtx = (AuthnCtx) authnContextBuilder.buildObject(AuthnContext.DEFAULT_ELEMENT_NAME);
-        String authnMethod = loginCtx.getAuthenticationMethod();
-
-        List<AuthnContextClassRef> authnClasses = ctx.getAuthnContextClassRefs();
-        List<AuthnContextDeclRef> authnDeclRefs = ctx.getAuthnContextDeclRefs();
-
-        if (authnClasses != null) {
-            for (AuthnContextClassRef classRef : authnClasses) {
-                if (classRef != null) {
-                    String s = classRef.getAuthnContextClassRef();
-                    if (s != null && authnMethod.equals(s)) {
-                        AuthnContextClassRef classRef = (AuthnContextClassRef) authnContextClassRefBuilder
-                                .buildObject(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
-                        authnCtx.setAuthnContextClassRef(classRef);
-                        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 declRef = (AuthnContextDeclRef) authnContextDeclRefBuilder
-                                .buildObject(AuthnContextDeclRef.DEFAULT_ELEMENT_NAME);
-                        authnCtx.setAuthnContextDeclRef(declRef);
-                        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 LoginContext 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 LoginContext)) {
-            log.error("Invalid login context object -- object is not an instance of LoginContext.");
-            throw new ServletException("Invalid login context object.");
-        }
-
-        return (LoginContext) o;;
-    }
-
-    /**
-     * Verify the AuthnRequest is well-formed.
-     * 
-     * @param authnRequest The user's SAML 2 AuthnRequest.
-     * 
-     * @throws AuthenticationRequestException on error.
-     */
-    protected void verifyAuthnRequest(final AuthnRequest authnRequest) throws AuthenticationRequestException {
-
-        Status failureStatus;
-
-        // The Web Browser SSO profile requires that the Issuer element is
-        // present.
-        Issuer issuer = authnRequest.getIssuer();
-        if (issuer == null) {
-            log.error("Malformed SAML 2 AuthnReq - missing Issuer element.");
-            failureStatus = buildStatus(StatusCode.REQUESTER_URI, null, "SAML 2 AuthnRequest " + authnRequest.getID()
-                    + " is malformed: It lacks an Issuer.");
-            throw new AuthenticationRequestException("AuthnRequest lacks an Issuer", failureStatus);
-        }
-
-        // Check if we are in scope to handle this AuthnRequest
-        // XXX: confirm that SPProviderID is the field we want in the issuer
-        if (!checkScope(authnRequest, issuer.getSPProvidedID())) {
-            return false;
-        }
-
-        // XXX: run signature checks on authnRequest
-
-        // verify that the AssertionConsumerService url is valid.
-        AssertionConsumerService acsEndpoint = getAndVerifyACSEndpoint(authnRequest, relyingParty.getRelyingPartyID(),
-                rpManager.getMetadataProvider());
-
-        Subject subject = getAndVerifySubject(authnRequest, failureStatus);
-
-        // check for nameID constraints.
-    }
-
-    /**
-     * 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.length > 0) {
-            log.error("SAML 2 AuthnRequest " + authnRequest.getID()
-                    + " is malformed: It contains SubjectConfirmation elements.");
-            failureStauts = 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.length) {
-                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 (AssertionConumerService 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(statusCodeBuilder.REQUESTER_URI, null,
-                "Unable to validate AssertionConsumerService against metadata.");
-
-        throw new AuthenticationRequestException("Unabel to validate AssertionConsumerService against Metadata",
-                failureStatus);
-    }
-
-    /**
-     * 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 true;
-        }
-
-        // 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)) {
-                found = true;
-                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;
-    }
-}
diff --git a/src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AuthenticationRequestBrowserPost.java b/src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AuthenticationRequestBrowserPost.java
new file mode 100644 (file)
index 0000000..8f8adf8
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package edu.internet2.middleware.shibboleth.idp.profile.saml2;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.ServletException;
+
+import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
+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.saml2.SSOConfiguration;
+
+import org.apache.log4j.Logger;
+import org.opensaml.common.SAMLObject;
+import org.opensaml.common.binding.BindingException;
+import org.opensaml.common.binding.MessageDecoder;
+import org.opensaml.saml2.core.AuthnRequest;
+import org.opensaml.saml2.core.Issuer;
+import org.opensaml.saml2.core.Response;
+import org.opensaml.saml2.binding.HTTPPostDecoder;
+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
+       }
+}
index d817eae..e7e717f 100644 (file)
 
 package edu.internet2.middleware.shibboleth.idp.profile.saml2;
 
-import org.opensaml.saml2.core.StatusCode;
+import org.opensaml.saml2.core.Status;
 
 /**
  * Indicates an error while processing an {@link AuthenticationRequest}.
  */
 public class AuthenticationRequestException extends java.lang.Exception {
 
-       protected StatusCode statusCode = null;
+       protected Status status = null;
 
        /**
-        * Get the SAML 2 StatusCode, if any, associated with this error.
+        * Get the SAML 2 Status, if any, associated with this error.
         * 
-        * @return A SAML 2 StatusCode object, or <code>null</code> if none was
-        *         set.
+        * @return A SAML 2 Status object, or <code>null</code> if none was set.
         */
-       public StatusCode getStatusCode() {
-               return statusCode;
+       public Status getStatus() {
+               return status;
        }
 
        /**
@@ -59,14 +58,14 @@ public class AuthenticationRequestException extends java.lang.Exception {
         * 
         * @param message
         *            The detail message.
-        * @param code
-        *            A SAML 2 StatusCode indicated which error should be returned
-        *            to the requestor.
+        * @param status
+        *            A SAML 2 Status indicated which error should be returned to
+        *            the requestor.
         */
        public AuthenticationRequestException(final String message,
-                       final StatusCode code) {
+                       final Status status) {
                super(message);
-               statusCode = code;
+               this.status = status;
        }
 
        /**
@@ -101,13 +100,13 @@ public class AuthenticationRequestException extends java.lang.Exception {
         *            is permitted, and indicates that the cause is nonexistent or
         *            unknown.)
         * @param code
-        *            A SAML 2 StatusCode indicated which error should be returned
-        *            to the requestor.
+        *            A SAML 2 Status indicated which error should be returned to
+        *            the requestor.
         */
        public AuthenticationRequestException(final Throwable cause,
-                       final StatusCode code) {
+                       final Status status) {
                super(cause);
-               statusCode = code;
+               this.status = status;
        }
 
        /**
@@ -145,13 +144,13 @@ public class AuthenticationRequestException extends java.lang.Exception {
         *            is permitted, and indicates that the cause is nonexistent or
         *            unknown.)
         * @param code
-        *            A SAML 2 StatusCode indicated which error should be returned
-        *            to the requestor.
+        *            A SAML 2 Status indicated which error should be returned to
+        *            the requestor.
         */
        public AuthenticationRequestException(final String message,
-                       final Throwable cause, final StatusCode code) {
+                       final Throwable cause, final Status code) {
                super(message, cause);
-               statusCode = code;
+               this.status = status;
        }
 
 }
index 1c879e5..6d2ff50 100644 (file)
@@ -24,7 +24,7 @@ import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
 /**
  * SAML 2.0 Logout Request profile handler.
  */
-public class LogoutRequest extends AbstractSAML2ProfileHandler {
+public abstract class LogoutRequest extends AbstractSAML2ProfileHandler {
 
     /** {@inheritDoc} */
     public boolean processRequest(ProfileRequest request, ProfileResponse response) throws ServletException {