reformat with eclipse.
authordmorr <dmorr@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Fri, 30 Mar 2007 17:54:12 +0000 (17:54 +0000)
committerdmorr <dmorr@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Fri, 30 Mar 2007 17:54:12 +0000 (17:54 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@2167 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

src/edu/internet2/middleware/shibboleth/idp/profile/saml1/ShibbolethSSO.java

index 97a05b2..7afa93d 100644 (file)
@@ -16,7 +16,6 @@
 
 package edu.internet2.middleware.shibboleth.idp.profile.saml1;
 
-
 import java.io.IOException;
 import java.net.URLEncoder;
 import java.security.MessageDigest;
@@ -35,6 +34,7 @@ import javax.servlet.http.HttpSession;
 import edu.internet2.middleware.shibboleth.common.profile.ProfileHandler;
 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyManager;
+import edu.internet2.middleware.shibboleth.common.relyingparty.saml1.ShibbolethSSOConfiguration;
 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
 
 import org.apache.log4j.Logger;
@@ -46,6 +46,7 @@ import org.opensaml.common.binding.SAMLArtifact;
 import org.opensaml.common.binding.SAMLArtifactFactory;
 import org.opensaml.common.binding.ArtifactMap;
 import org.opensaml.saml1.core.Assertion;
+import org.opensaml.saml1.core.AttributeStatement;
 import org.opensaml.saml1.core.Audience;
 import org.opensaml.saml1.core.AudienceRestrictionCondition;
 import org.opensaml.saml1.core.AuthenticationStatement;
@@ -70,792 +71,939 @@ import org.opensaml.xml.signature.KeyInfo;
 import org.opensaml.xml.signature.SignableXMLObject;
 import org.opensaml.xml.signature.Signature;
 
-
 /**
  * Shibboleth, version 1.X, single sign-on profile handler.
- *
- * This profile implements the SSO profile from 
- * "Shibboleth Architecture Protocols and Profiles" - 10 September 2005.
+ * 
+ * This profile implements the SSO profile from "Shibboleth Architecture
+ * Protocols and Profiles" - 10 September 2005.
  */
 public class ShibbolethSSO extends AbstractProfileHandler {
-    
-    /** log4j. */
-    private static final Logger log = Logger.getLogger(ShibbolethSSO.class);
-    
-    /** SAML 1 bearer confirmation method URI. */
-    protected static final String BEARER_CONF_METHOD_URI =
-           "urn:oasis:names:tc:SAML:1.0:cm:bearer";
-    
-    /** SAML 1 artifact confirmation method URI */
-    protected static final String ARTIFACT_CONF_METHOD_URI =
-           "urn:oasis:names:tc:SAML:1.0:cm:artifact";
-    
-    /** SAML 1.1 SPSSO protocol URI */
-    protected static final String SAML11_PROTOCOL_URI =
-           "urn:oasis:names:tc:SAML:1.1:protocol";
-    
-    /** SAML 1 Browser/POST protocol URI. */
-    protected static final String PROFILE_BROWSER_POST_URI =
-           "urn:oasis:names:tc:SAML:1.0:profiles:browser-post";
-    
-    /** SAML 1 Artifact protocol URI. */
-    protected static final String PROFILE_ARTIFACT_URI =
-           "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01";
-    
-    /** The digest algorithm for generating SP cookies. */
-    protected static final String RP_COOKIE_DIGEST_ALG = "SHA-1";
-    
-    /** The RelyingPartyManager. */
-    protected RelyingPartyManager 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;
-    
-    /** The URI of the default authentication method */
-    protected String authenticationMethodURI;
-    
-    /** Builder for AuthenticationStatement objects. */
-    protected XMLObjectBuilder authnStmtBuilder;
-    
-    /** Builder for Subject elements. */
-    protected XMLObjectBuilder subjectbuilder;
-    
-    /** Builder for SubjectConfirmation objects. */
-    protected XMLObjectBuilder subjConfBuilder;
-    
-    /** Builder for SubjectConfirmationMethod objects. */
-    protected XMLObjectBuilder confMethodBuilder;
-    
-    /** Builder for Artifacts. */
-    protected XMLObjectBuilder artifactBuilder;
-    
-    /** Builder for NameIdentifiers. */
-    protected XMLObjectBuilder nameIdentifierBuilder;
-    
-    /** Builder for Audience elements. */
-    protected XMLObjectBuilder audienceBuilder;
-    
-    /** Builder for AudienceRestrictionCondition elements. */
-    protected XMLObjectBuilder audienceRestrictionBuilder;
-    
-    /** Builder for Assertions. */
-    protected XMLObjectBuilder assertionBuilder;
-    
-    /** Builder for Status objects. */
-    protected XMLObjectBuilder statusBuilder;
-    
-    /** Builder for StatusCode objects. */
-    protected XMLObjectBuilder statusCodeBuilder;
-    
-    /** Builder for StatusMessage objects. */
-    protected XMLObjectBuilder statusMessageBuilder;
-    
-    /** Builder for Response objects. */
-    protected XMLObjectbuilder responseBuilder;
-    
-    /** Block stale requests. */
-    protected boolean blockStaleRequests = false;
-    
-    /** Blame the SP if requests are malformed. */
-    protected boolean blameSP = false;
-    
-    /** Time after which an authn request is considered stale(in seconds). Defaults to 30 minutes. */
-    protected int requestTTL = 1800;
-    
-    /** Lifetime for an AuthenticationStatement (in seconds). Defaults to 5 minutes. */
-    protected int statementLifetime = 300;
-    
-    /** Protocol binding to use to the Authentication Assertion */
-    protected enum ENDPOINT_BINDING { BROWSER_POST, ARTIFACT };
-    
-    /** ArtifactFactory used to make artifacts. */
-    protected SAMLArtifactFactory artifactFactory;
-    
-    /** PRNG for Artifact assertionHandles. */
-    protected SecureRandom prng;
-    
-    /**
-     * Default constructor.
-     */
-    public ShibbolethSSO() {
-       
-       // setup SAML object builders
-       
-       assertionBuilder           = getBuilderFactory().getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
-       authnStmtBuilder           = getBuilderFactory().getBuilder(AuthenticationStatement.DEFAULT_ELEMENT_NAME);
-       subjectbuilder             = getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
-       subjConfBuilder            = getBuilderFactory().getBuilder(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
-       confMethodBuilder          = getBuilderFactory().getBuilder(ConfirmationMethod.DEFAULT_ELEMENT_NAME);
-       nameIdentifierBuilder      = getBuilderFactory().getBuilder(NameIdentifier.DEFAULT_ELEMENT_NAME);
-       audienceBuilder            = getBuilderFactory().getBbuilder(Audience.DEFAULT_ELEMENT_NAME);
-       audienceRestrictionBuilder = getBuilderFactory().getBuilder(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
-       statusBuilder              = getBuilderFactory().getBuidler(Status.DEFAULT_ELEMENT_NAME);
-       statusCodeBuilder          = getBuilderFactory().getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
-       statusMessageBuilder       = getBuilderFactory().getBuilder(StatusMessage.DEFAULT_ELEMENT_NAME);
-       responseBuilder            = getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
-       
-       artifactFactory = new SAMLArtifactFactory();
-    }
-    
-    
-    /**
-     * Set the authentication manager.
-     *
-     * @param authnManagerURL The URL of the IdP's AuthenticationManager servlet
-     */
-    public void setAuthenticationManager(String authnManagerURL) {
-       authnMgrURL = authnManagerURL;
-    }
-    
-    
-    /**
-     * Set the RelyingPartyManager.
-     *
-     * @param rpManager A RelyingPartyManager.
-     */
-    public void setRelyingPartyManager(RelyingPartyManager rpManager) {
-       this.rpManager = rpManager;
-    }
-    
-    
-    /**
-     * Set the authentication method URI
-     *
-     * The URI SHOULD come from oasis-sstc-saml-core-1.1, section 7.1
-     *
-     * @param authMethod The authentication method's URI
-     */
-    public void setAuthenticationMethodURI(String authMethod) {
-       authenticationMethodURI = authMethod;
-    }
-    
-    
-    /**
-     * Set if old requests should be blocked.
-     *
-     * @param blockStaleRequests boolean flag.
-     */
-    public void setBlockStaleRequests(boolean blockStaleRequests) {
-       this.blockStaleRequests = blockStaleRequests;
-    }
-    
-    
-    /**
-     * Return if stale requests are blocked.
-     *
-     * @return <code>true</code> if old requests are blocked.
-     */
-    public boolean getBlockStaleRequests() {
-       return blockStaleRequests;
-    }
-    
-    
-    /**
-     * Set request TTL.
-     *
-     * @param ttl Request timeout (in seconds).
-     */
-    public void setRequestTTL(int ttl) {
-       requestTTL = ttl;
-    }
-    
-    
-    /**
-     * Get Request TTL.
-     * This is the time after which a request is considered stale.
-     *
-     * @return request timeout (in seconds).
-     */
-    public int getRequestTTL() {
-       return requestTTL;
-    }
-    
-    
-    /**
-     * Set the artifact map backing store.
-     *
-     * @param artifactMap the Artifact mapping backing store.
-     */
-    public void setArtifactMap(ArtifactMap artifactMap) {
-       this.artifactMap = artifactMap;
-    }
-    
-    
-    /**
-     * Get the artifact map backing store.
-     *
-     * @return An ArtifactMap instance.
-     */
-    public ArtifactMap getArtifactMap() {
-       return artifactMap;
-    }
-    
-    
-    /** {@inheritDoc} */
-    public boolean processRequest(ServletRequest request,
-           ServletResponse response) throws ServletException {
-       
-       // Only http servlets are supported for now.
-       if (!(request instanceof HttpServletRequest)) {
-           log.error("Received a non-HTTP request from " + request.getRemoteHost());
-           return false;
-       }
-       
-       HttpServletRequest httpReq = (HttpServletRequest)request;
-       HttpServletResponse httpResp = (HttpServletResponse)response;
-       HttpSession httpSession = httpReq.getSession();
-       LoginContext loginCtx;
-       
-       String shire = null;
-       String target = null;
-       String providerId = null;
-       String remoteAddr = null;
-       
-       // extract the (mandatory) request parameters.
-       if (!getRequestParameters(httpReq, shire, target, providerId, remoteAddr)) {
-           
-           if (blameSP) {
-               httpReq.setAttribute("errorPage", "/IdPErrorBlameSP.jsp");
-               // XXX: flesh this out more.
-           }
-           
-           return false;
-       }
-       
-       // check for stale requests
-       if (blockStaleRequests) {
-           String cookieName = getRPCookieName(providerName);
-           if (!validateFreshness(httpReq, httpResp, cookieName)) {
-               return false;
-           }
-           
-           writeFreshnessCookie(httpReq, httpResp, cookieName);
-       }
-       
-       // check if the user has already been authenticated
-       Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
-       if (o == null) {
-           
-           // the user hasn't been authenticated, so forward the request
-           // to the AuthenticationManager. When the AuthenticationManager
-           // is done it will forward the request back to this servlet.
-           
-           // don't force reauth or passive auth
-           loginCtx = new LoginContext(false, false);
-           loginCtx.setProfileHandlerURL(httpReq.getPathInfo());
-           try {
-               RequestDispatcher dispatcher = request.getRequestDispatcher(authnMgrURL);
-               dispatcher.forward(request, response);
-           } catch (IOException ex) {
-               log.error("Error forwarding SAML 1 SSO request to AuthenticationManager", ex);
-               return false;
-           }
-       }
-       
-       
-       // The user has been authenticated.
-       // Process the SAML 1 authn request.
-       
-       if (! (o instanceof LoginContext)) {
-           log.error("Invalid login context object -- object is not an instance of LoginContext.");
-           return false;
-       }
-       
-       loginCtx = (LoginContext)o;
-       
-       if (!loginCtx.getAuthenticationOK()) {
-           // issue error message.
-           String failureMessage = loginCtx.getAuthenticationFailureMessage();
-           
-           // generate SAML failure message
-           
-           return true;
-       }
-       
-       // The user successfully authenticated,
-       // so build the appropriate AuthenticationStatement.
-
-       DateTime now = new DateTime();
-       RelyingPartyConfiguration relyingParty = rpManager.getRelyingPartyConfiguration(providerId);
-       SPSSODescriptor spDescriptor;
-
-       try {
-           spDescriptor = rpManager.getMetadataProvider().
-                   getEntityDescriptor(rpManager.getProviderID()).
-                   getSPSSODescriptor(SAML11_PROTOCOL_URI);
-       } catch (MetadataProviderException ex) {
-           log.error("Unable to locate metadata for SP " + providerId
-                   + " for protocol " + SAML11_PROTOCOL_URI, ex);
-           return false;
-       }
-       
-       if (spDescriptor == null) {
-           log.error("Unable to locate metadata for SP " + providerId
-                   + " for protocol " + SAML11_PROTOCOL_URI);
-           // handle error
-           return true;
-       }
-       
-       // validate the AssertionConsumer URL
-       List<AssertionConsumerService> consumerEndpoints = validateAssertionConsumerURL(spDescriptor, shire);
-       if (consumerEndpoints.length == 0) {
-           // handle error
-           return true;
-       }
-       
-       ENDPOINT_BINDING endpointBinding = getProtocolBinding(spDescriptor, consumerEndpoints, shire);
-       
-       String confMethod;
-       if (endpointBinding = ENDPOINT_BINDING.BROWSER_POST) {
-           confMethod = BEARER_CONF_METHOD_URI;
-       } else if (endpointBinding = ENDPOINT_BINDING.ARTIFACT) {
-           confMethod = ARTIFACT_CONF_METHOD_URI;
-       } 
-       
-       Assertion authenticationAssertion = generateAuthenticationAssertion(loginCtx, relyingParty,
-               providerId, spDescriptor, confMethod, now);
-       if (authenticationAssertion == null) {
-           // do error handling
-           return true;
+
+       /** log4j. */
+       private static final Logger log = Logger.getLogger(ShibbolethSSO.class);
+
+       /** SAML 1 bearer confirmation method URI. */
+       protected static final String BEARER_CONF_METHOD_URI = "urn:oasis:names:tc:SAML:1.0:cm:bearer";
+
+       /** SAML 1 artifact confirmation method URI */
+       protected static final String ARTIFACT_CONF_METHOD_URI = "urn:oasis:names:tc:SAML:1.0:cm:artifact";
+
+       /** SAML 1.1 SPSSO protocol URI */
+       protected static final String SAML11_PROTOCOL_URI = "urn:oasis:names:tc:SAML:1.1:protocol";
+
+       /** SAML 1 Browser/POST protocol URI. */
+       protected static final String PROFILE_BROWSER_POST_URI = "urn:oasis:names:tc:SAML:1.0:profiles:browser-post";
+
+       /** SAML 1 Artifact protocol URI. */
+       protected static final String PROFILE_ARTIFACT_URI = "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01";
+
+       /** The digest algorithm for generating SP cookies. */
+       protected static final String RP_COOKIE_DIGEST_ALG = "SHA-1";
+
+       /** The RelyingPartyManager. */
+       protected RelyingPartyManager 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;
+
+       /** The URI of the default authentication method */
+       protected String authenticationMethodURI;
+
+       /** Builder for AuthenticationStatement objects. */
+       protected XMLObjectBuilder authnStmtBuilder;
+
+       /** Builder for Subject elements. */
+       protected XMLObjectBuilder subjectbuilder;
+
+       /** Builder for SubjectConfirmation objects. */
+       protected XMLObjectBuilder subjConfBuilder;
+
+       /** Builder for SubjectConfirmationMethod objects. */
+       protected XMLObjectBuilder confMethodBuilder;
+
+       /** Builder for Artifacts. */
+       protected XMLObjectBuilder artifactBuilder;
+
+       /** Builder for NameIdentifiers. */
+       protected XMLObjectBuilder nameIdentifierBuilder;
+
+       /** Builder for Audience elements. */
+       protected XMLObjectBuilder audienceBuilder;
+
+       /** Builder for AudienceRestrictionCondition elements. */
+       protected XMLObjectBuilder audienceRestrictionBuilder;
+
+       /** Builder for Assertions. */
+       protected XMLObjectBuilder assertionBuilder;
+
+       /** Builder for Status objects. */
+       protected XMLObjectBuilder statusBuilder;
+
+       /** Builder for StatusCode objects. */
+       protected XMLObjectBuilder statusCodeBuilder;
+
+       /** Builder for StatusMessage objects. */
+       protected XMLObjectBuilder statusMessageBuilder;
+
+       /** Builder for Response objects. */
+       protected XMLObjectbuilder responseBuilder;
+
+       /** Block stale requests. */
+       protected boolean blockStaleRequests = false;
+
+       /** Blame the SP if requests are malformed. */
+       protected boolean blameSP = false;
+
+       /**
+        * Time after which an authn request is considered stale(in seconds).
+        * Defaults to 30 minutes.
+        */
+       protected int requestTTL = 1800;
+
+       /** Protocol binding to use to the Authentication Assertion */
+       protected enum ENDPOINT_BINDING {
+               BROWSER_POST, ARTIFACT
+       };
+
+       /** ArtifactFactory used to make artifacts. */
+       protected SAMLArtifactFactory artifactFactory;
+
+       /** PRNG for Artifact assertionHandles. */
+       protected SecureRandom prng;
+
+       /**
+        * Default constructor.
+        */
+       public ShibbolethSSO() {
+
+               // setup SAML object builders
+
+               assertionBuilder = getBuilderFactory().getBuilder(
+                               Assertion.DEFAULT_ELEMENT_NAME);
+               authnStmtBuilder = getBuilderFactory().getBuilder(
+                               AuthenticationStatement.DEFAULT_ELEMENT_NAME);
+               subjectbuilder = getBuilderFactory().getBuilder(
+                               Subject.DEFAULT_ELEMENT_NAME);
+               subjConfBuilder = getBuilderFactory().getBuilder(
+                               SubjectConfirmation.DEFAULT_ELEMENT_NAME);
+               confMethodBuilder = getBuilderFactory().getBuilder(
+                               ConfirmationMethod.DEFAULT_ELEMENT_NAME);
+               nameIdentifierBuilder = getBuilderFactory().getBuilder(
+                               NameIdentifier.DEFAULT_ELEMENT_NAME);
+               audienceBuilder = getBuilderFactory().getBbuilder(
+                               Audience.DEFAULT_ELEMENT_NAME);
+               audienceRestrictionBuilder = getBuilderFactory().getBuilder(
+                               AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
+               statusBuilder = getBuilderFactory().getBuidler(
+                               Status.DEFAULT_ELEMENT_NAME);
+               statusCodeBuilder = getBuilderFactory().getBuilder(
+                               StatusCode.DEFAULT_ELEMENT_NAME);
+               statusMessageBuilder = getBuilderFactory().getBuilder(
+                               StatusMessage.DEFAULT_ELEMENT_NAME);
+               responseBuilder = getBuilderFactory().getBuilder(
+                               Response.DEFAULT_ELEMENT_NAME);
+
+               artifactFactory = new SAMLArtifactFactory();
        }
-       
-       if (endpointBinging == ENDPOINT_BINDING.BROWSER_POST) {
-           // do post
-       } else if (endpointBinding == ENDPOINT_BINDING.ARTIFACT) {
-           respondWithArtifact(httpReq, httpResp, shire, target, new Assertion[] { authenticationAssertion });
+
+       /**
+        * Set the authentication manager.
+        * 
+        * @param authnManagerURL
+        *            The URL of the IdP's AuthenticationManager servlet
+        */
+       public void setAuthenticationManager(String authnManagerURL) {
+               authnMgrURL = authnManagerURL;
        }
-       
-       
-       return true;
-    }
-    
-    
-    /**
-     * Respond with a SAML Artifact.
-     *
-     * @param request The HttpServletRequest.
-     * @param response The HttpServletResponse.
-     * @param shire The AssertionConsumerService URL.
-     * @parma target The target parameter from the request.
-     * @param assertions One or more SAML assertions.
-     */
-    protected void respondWithArtifact(HttpServletRequest request, HttpServletResponse response, String shire,
-           String target, RelyingPartyConfiguration relyingParty, Assertion[] assertions) throws ServletException, NoSuchProviderException {
-       
-       if (assertions.length < 1) {
-           return;
+
+       /**
+        * Set the RelyingPartyManager.
+        * 
+        * @param rpManager
+        *            A RelyingPartyManager.
+        */
+       public void setRelyingPartyManager(RelyingPartyManager rpManager) {
+               this.rpManager = rpManager;
        }
-       
-       StringBuilder buf = new StringBuilder(shire);
-       buf.append("?TARGET=");
-       buf.append(URLEncoder.encode(target), "UTF-8");;
-       
-       // We construct the type 1 Artifact's sourceID by SHA-1 hashing the IdP's providerID.
-       // This is legacy holdover from Shib 1.x.
-       MessageDigest digester = MessageDigest.getInstance("SHA-1");
-       byte[] sourceID = digester.digest(relyingParty.getProviderID);
-       
-       for (Assertion assertion : assertions) {
-           
-           // XXX: todo: log the assertion to log4j @ debug level.
-           
-           byte artifactType = (byte)relyingParty.getDefaultArtifactType();
-           
-           SAMLArtifact artifact = artifactFactory.buildArtifact(SAML_VERSION,
-                   new byte[] { 0, artifactType }, relyingParty.getProviderID());
-           
-           String artifactID = artifact.hexEncode();
-           artifactMap.put(artifact, assertion);
-           
-           log.debug("encoding assertion " + assertion.getID() + " into artifact " + artifactID);  
-           log.debug("appending artifact " + artifactID + " for URL " + shire);
-           buf.append("&SAMLArt=");
-           buf.append(URLEncoder.encode(artifact.base64Encode(), "UTF-8"));
+
+       /**
+        * Set the authentication method URI.
+        * 
+        * The URI SHOULD come from oasis-sstc-saml-core-1.1, section 7.1
+        * 
+        * @param authMethod
+        *            The authentication method's URI
+        */
+       public void setAuthenticationMethodURI(String authMethod) {
+               authenticationMethodURI = authMethod;
        }
-       
-       String url = buf.toString();
-       response.sendRedirect(url);
-    }
-    
-    
-    /**
-     * Respond with the SAML 1 Browser/POST profile.
-     *
-     * @param request The HttpServletRequest.
-     * @param response The HttpServletResponse.
-     * @param shire The AssertionConsumerService URL.
-     * @parma target The target parameter from the request.
-     * @param assertions One or more SAML assertions.
-     */
-    protected void respondWithPOST(HttpServletRequest request, HttpServletResponse response, String shire,
-           String target, RelyingPartyConfiguration relyingParty, Assertion[] assertions) throws ServletException {
-       
-       Response samlResponse = (Response)responseBuilder.buildObject(Response.DEFAULT_ELEMENT_NAME);
-       Status status = buildStatus("Success", null);
-       samlResponse.setStatus(status);
-       samlResponse.setIssueInstant(new DateTime());
-       samlResponse.setVersion(SAML_VERSION);
-       samlResponse.setID(getIdGenerator().generateIdentifier());
-       samlResponse.setRecipient(relyingParty.getRelyingPartyID());
-       
-       List<Assertion> assertionList = samlResponse.getAssertions();
-       for (Assertion assertion : assertions) {
-           assertionList.add(assertion);
+
+       /**
+        * Set if old requests should be blocked.
+        * 
+        * @param blockStaleRequests
+        *            boolean flag.
+        */
+       public void setBlockStaleRequests(boolean blockStaleRequests) {
+               this.blockStaleRequests = blockStaleRequests;
        }
-       
-       request.setAttribute("acceptanceURL", shire);
-       request.setAttribute("target", target);
-       
-       
-       RequestDispatcher dispatcher = request.getRequestDispatcher("/IdP_SAML1_POST.jdp");
-       dispatcher.forward(request, response);
-    }
-    
-    
-    /**
-     * Get the Shibboleth profile-specific request parameters.
-     * The shire, target, providerId and remoteAddr parameters will be
-     * populated upon successful return.
-     *
-     * @param request The servlet request from the SP.
-     * @param shire The AttributeConsumerService URL
-     * @param target The location to which to POST the response.
-     * @param providerId The SP's provider ID in the metadata.
-     * @param remoteAddr The address of the requestor.
-     *
-     * @return <code>true</code> if the request contains valid parameters.
-     */
-    protected boolean getRequestParameters(HttpServletRequest request,
-           String shire, String target, String providerId, String remoteAddr) {
-       
-       target = request.getParameter("target");
-       providerId = request.getParameter("providerId");
-       shire = request.getParameter("shire");
-       remoteAddr = request.getRemoteAddr();
-       
-       if (target == null || target.equals("")) {
-           log.error("Shib 1 SSO request is missing or contains an invalid target parameter");
-           return false;
+
+       /**
+        * Return if stale requests are blocked.
+        * 
+        * @return <code>true</code> if old requests are blocked.
+        */
+       public boolean getBlockStaleRequests() {
+               return blockStaleRequests;
        }
-       
-       if (providerId == null || providerId.equals("")) {
-           log.error("Shib 1 SSO request is missing or contains an invalid provierId parameter");
-           return false;
+
+       /**
+        * Set request TTL.
+        * 
+        * @param ttl
+        *            Request timeout (in seconds).
+        */
+       public void setRequestTTL(int ttl) {
+               requestTTL = ttl;
        }
-       
-       if (shire == null || providerId.equals("")) {
-           log.error("Shib 1 SSO request is missing or contains an invalid shire parameter");
-           return false;
+
+       /**
+        * Get Request TTL. This is the time after which a request is considered
+        * stale.
+        * 
+        * @return request timeout (in seconds).
+        */
+       public int getRequestTTL() {
+               return requestTTL;
        }
-       
-       if (remoteAddr == null || remoteAddr.equals("")) {
-           log.error("Unable to obtain requestor address when processing Shib 1 SSO request");
-           return false;
+
+       /**
+        * Set the artifact map backing store.
+        * 
+        * @param artifactMap
+        *            the Artifact mapping backing store.
+        */
+       public void setArtifactMap(ArtifactMap artifactMap) {
+               this.artifactMap = artifactMap;
        }
-       
-       return true;
-    }
-    
-    
-    /**
-     * Generate a SAML 1 AuthenticationStatement.
-     *
-     * @param loginCtx The LoginContext.
-     * @param relyingParty The Replying Party configuration for the SP.
-     * @param spID The providerID of the SP that sent the request.
-     * @param spDescriptor The SPSSO Descriptor from the metadata.
-     * @param subjectConfirmationMethod The SubjectConfirmationMethod.
-     *          If <code>null</code> no SubjectConfirmationMethod element
-     *          will be generated.
-     * @param now The current timestamp
-     *
-     * @return A SAML 1 Authentication Assertion or <code>null</code> on error.
-     */
-    protected Assertion generateAuthenticationAssertion(final LoginContext loginCtx,
-           final RelyingPartyConfiguration relyingParty, String spID, final SPSSODescriptor spDescriptor,
-           String subjectConfirmationMethod, final DateTime now) {
-       
-       Assertion authenticationAssertion =
-               (Assertion)assertionBuilder.build(Assertion.DEFAULT_ELEMENT_NAME);
-       
-       authenticationAssertion.setIssueInstant(now);
-       authenticationAssertion.setVersion(SAMLVersion.VERSION_11);
-       authenticationAssertion.setIssuer(relyingParty.getProviderID());
-       authenticationAssertion.setID(getIdGenerator().generateIdentifier());
-       authenticationAssertion.setIssuer(relyingParty.getProviderID());
-       
-       Conditions conditions = authenticationAssertion.getConditions();
-       conditions.setNotBefore(now.minusSeconds(30)); // for now, clock skew is hard-coded to 30 seconds.
-       conditions.setNotOnOrAfter(now.plusSeconds(statementLifetime)); 
-       
-       List<AudienceRestrictionCondition> audiences = conditions.getAudienceRestrictionConditions();
-       AudienceRestrictionCondition restrictionCondition = 
-               (AudienceRestrictionCondition)audienceRestrictionBuilder.buildObject(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
-       Audience rpAudience = (Audience)audienceBuilder.buildObject(Audience.DEFAULT_ELEMENT_NAME);
-       rpAudience.setURI(relyingParty.getProviderID());
-       audiences.add(rpAudience);
-       if (!relyingParty.getProviderID().equals(spID)) {
-           Audience spAudience = (Audience)audienceBuilder.buildObject(Audience.DEFAULT_ELEMENT_NAME);
-           spAudience.setURI(spID);
-           audiences.add(spAudience);
+
+       /**
+        * Get the artifact map backing store.
+        * 
+        * @return An ArtifactMap instance.
+        */
+       public ArtifactMap getArtifactMap() {
+               return artifactMap;
        }
 
-       AuthenticationStatement authenticationStatement =
-               (AuthenticationStatement)authnStmtBuilder.buildObject(AuthenticationStatement.DEFAULT_ELEMENT_NAME);
-
-       authenticationStatement.setSubject(buildSubject(loginCtx, subjectConfirmationMethod, relyingParty));
-       authenticationStatement.setAuthenticationInstant(loginCtx.getAuthenticationInstant());
-       authenticationStatement.setAuthenticationMethod(authenticationMethodURI);
-       
-       authenticationAssertion.getAuthenticationStatements().add(authenticationStatement);
-       
-       if (spDescriptor.getWantAssertionsSigned()) {
-           // sign the assertion
+       /** {@inheritDoc} */
+       public boolean processRequest(ServletRequest request,
+                       ServletResponse response) throws ServletException {
+
+               // Only http servlets are supported for now.
+               if (!(request instanceof HttpServletRequest)) {
+                       log.error("Received a non-HTTP request from "
+                                       + request.getRemoteHost());
+                       return false;
+               }
+
+               HttpServletRequest httpReq = (HttpServletRequest) request;
+               HttpServletResponse httpResp = (HttpServletResponse) response;
+               HttpSession httpSession = httpReq.getSession();
+               LoginContext loginCtx;
+
+               String shire = null;
+               String target = null;
+               String providerId = null;
+               String remoteAddr = null;
+
+               // extract the (mandatory) request parameters.
+               if (!getRequestParameters(httpReq, shire, target, providerId,
+                               remoteAddr)) {
+
+                       if (blameSP) {
+                               httpReq.setAttribute("errorPage", "/IdPErrorBlameSP.jsp");
+                               // XXX: flesh this out more.
+                       }
+
+                       return false;
+               }
+
+               // check for stale requests
+               if (blockStaleRequests) {
+                       String cookieName = getRPCookieName(providerName);
+                       if (!validateFreshness(httpReq, httpResp, cookieName)) {
+                               return false;
+                       }
+
+                       writeFreshnessCookie(httpReq, httpResp, cookieName);
+               }
+
+               // check if the user has already been authenticated
+               Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
+               if (o == null) {
+
+                       // the user hasn't been authenticated, so forward the request
+                       // to the AuthenticationManager. When the AuthenticationManager
+                       // is done it will forward the request back to this servlet.
+
+                       // don't force reauth or passive auth
+                       loginCtx = new LoginContext(false, false);
+                       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 1 SSO request to AuthenticationManager",
+                                                               ex);
+                               return false;
+                       }
+               }
+
+               // The user has been authenticated.
+               // Process the SAML 1 authn request.
+
+               if (!(o instanceof LoginContext)) {
+                       log
+                                       .error("Invalid login context object -- object is not an instance of LoginContext.");
+                       return false;
+               }
+
+               loginCtx = (LoginContext) o;
+
+               if (!loginCtx.getAuthenticationOK()) {
+                       // issue error message.
+                       String failureMessage = loginCtx.getAuthenticationFailureMessage();
+
+                       // generate SAML failure message
+
+                       return true;
+               }
+
+               // The user successfully authenticated,
+               // so build the appropriate AuthenticationStatement.
+
+               DateTime now = new DateTime();
+               RelyingPartyConfiguration relyingParty = rpManager
+                               .getRelyingPartyConfiguration(providerId);
+               ShibbolethSSOConfiguration ssoConfig = relyingParty
+                               .getProfileConfigurations().get(
+                                               ShibbolethSSOConfiguration.PROFILE_ID);
+               SPSSODescriptor spDescriptor;
+
+               try {
+                       spDescriptor = rpManager.getMetadataProvider().getEntityDescriptor(
+                                       relyingParty.getRelyingPartyID()).getSPSSODescriptor(
+                                       SAML11_PROTOCOL_URI);
+               } catch (MetadataProviderException ex) {
+                       log.error("Unable to locate metadata for SP " + providerId
+                                       + " for protocol " + SAML11_PROTOCOL_URI, ex);
+                       return false;
+               }
+
+               if (spDescriptor == null) {
+                       log.error("Unable to locate metadata for SP " + providerId
+                                       + " for protocol " + SAML11_PROTOCOL_URI);
+                       // handle error
+                       return true;
+               }
+
+               // validate the AssertionConsumer URL
+               List<AssertionConsumerService> consumerEndpoints = validateAssertionConsumerURL(
+                               spDescriptor, shire);
+               if (consumerEndpoints.length == 0) {
+                       // handle error
+                       return true;
+               }
+
+               ENDPOINT_BINDING endpointBinding = getProtocolBinding(spDescriptor,
+                               consumerEndpoints, shire);
+
+               String confMethod;
+               if (endpointBinding = ENDPOINT_BINDING.BROWSER_POST) {
+                       confMethod = BEARER_CONF_METHOD_URI;
+               } else if (endpointBinding = ENDPOINT_BINDING.ARTIFACT) {
+                       confMethod = ARTIFACT_CONF_METHOD_URI;
+               }
+
+               Assertion authenticationAssertion = generateAuthenticationAssertion(
+                               loginCtx, relyingParty, ssoConfig, providerId, spDescriptor,
+                               confMethod, now);
+               if (authenticationAssertion == null) {
+                       // do error handling
+                       return true;
+               }
+
+               if (endpointBinging == ENDPOINT_BINDING.BROWSER_POST) {
+                       // do post
+               } else if (endpointBinding == ENDPOINT_BINDING.ARTIFACT) {
+                       respondWithArtifact(httpReq, httpResp, shire, target,
+                                       new Assertion[] { authenticationAssertion });
+               }
+
+               return true;
        }
-       
-       return authenticationStatement;
-    }
-    
-    
-    /**
-     * Get the protocol binding to use for sending the authentication assertion.
-     * Currently, only Browser/POST and Artifact are supported. This method will
-     * return the first recognized binding that it locates.
-     *
-     * @param spDescriptor The SP's SPSSODescriptor
-     * @param endpoints The list of AssertionConsumerEndpoints with the "shire" URL as their location.
-     * @param shireURL The "shire" url from the authn request.
-     *
-     * @return The protocol binding for a given SPSSODescriptor.
-     *
-     * @throws MetadataException if no Browswer/POST or Artifact binding can be found.
-     */
-    protected ENDPOINT_BINDING getProtocolBinding(final SPSSODescriptor spDecsriptor,
-           final List<AssertionConsumerService> endpoints, String shireURL) throws MetadataException {
-       
-       // check the default AssertionConsumerService first.
-       AssertionConsumerService defaultConsumer =
-               spDescriptor.getDefaultAssertionConsumerService();
-       
-       if (defaultConsumer != null && defaultConsumer.getLocation().equals(acceptanceURL)) {
-           
-           if (defaultConsumer.getBinding().equals(PROFILE_ARTIFACT_URI)) {
-               return ENDPOINT_BINDING.ARTIFACT;
-           } else if (defaultConsumer.getBinding().equals(PROFILE_BROWSER_POST_URI)) {
-               return ENDPOINT_BINDING.BROWSER_POST;
-           }
+
+       /**
+        * Respond with a SAML Artifact.
+        * 
+        * @param request
+        *            The HttpServletRequest.
+        * @param response
+        *            The HttpServletResponse.
+        * @param shire
+        *            The AssertionConsumerService URL.
+        * @parma target The target parameter from the request.
+        * @param assertions
+        *            One or more SAML assertions.
+        */
+       protected void respondWithArtifact(HttpServletRequest request,
+                       HttpServletResponse response, String shire, String target,
+                       RelyingPartyConfiguration relyingParty, Assertion[] assertions)
+                       throws ServletException, NoSuchProviderException {
+
+               if (assertions.length < 1) {
+                       return;
+               }
+
+               StringBuilder buf = new StringBuilder(shire);
+               buf.append("?TARGET=");
+               buf.append(URLEncoder.encode(target), "UTF-8");
+               ;
+
+               // We construct the type 1 Artifact's sourceID by SHA-1 hashing the
+               // IdP's providerID.
+               // This is legacy holdover from Shib 1.x.
+               MessageDigest digester = MessageDigest.getInstance("SHA-1");
+               byte[] sourceID = digester.digest(relyingParty.getProviderID);
+
+               for (Assertion assertion : assertions) {
+
+                       // XXX: todo: log the assertion to log4j @ debug level.
+
+                       byte artifactType = (byte) relyingParty.getDefaultArtifactType();
+
+                       SAMLArtifact artifact = artifactFactory.buildArtifact(SAML_VERSION,
+                                       new byte[] { 0, artifactType }, relyingParty
+                                                       .getProviderID());
+
+                       String artifactID = artifact.hexEncode();
+                       artifactMap.put(artifact, assertion);
+
+                       log.debug("encoding assertion " + assertion.getID()
+                                       + " into artifact " + artifactID);
+                       log.debug("appending artifact " + artifactID + " for URL " + shire);
+                       buf.append("&SAMLArt=");
+                       buf.append(URLEncoder.encode(artifact.base64Encode(), "UTF-8"));
+               }
+
+               String url = buf.toString();
+               response.sendRedirect(url);
        }
-       
-       // check the (already filtered) list of AssertionConsumer endpoints
-       for (AssertionConsumerService endpoint : endpoints) {
-           if (endpoint.getBinding().equals(PROFILE_ARTIFACT_URI)) {
-               return ENDPOINT_BINDING.ARTIFACT;
-           } else if (endpoint.getBinding().equals(PROFILE_BROWSER_POST_URI)) {
-               return ENDPOINT_BINDING.BROWSER_POST;
-           }
+
+       /**
+        * Respond with the SAML 1 Browser/POST profile.
+        * 
+        * @param request
+        *            The HttpServletRequest.
+        * @param response
+        *            The HttpServletResponse.
+        * @param shire
+        *            The AssertionConsumerService URL.
+        * @parma target The target parameter from the request.
+        * @param assertions
+        *            One or more SAML assertions.
+        */
+       protected void respondWithPOST(HttpServletRequest request,
+                       HttpServletResponse response, String shire, String target,
+                       RelyingPartyConfiguration relyingParty, Assertion[] assertions)
+                       throws ServletException {
+
+               Response samlResponse = (Response) responseBuilder
+                               .buildObject(Response.DEFAULT_ELEMENT_NAME);
+               Status status = buildStatus("Success", null);
+               samlResponse.setStatus(status);
+               samlResponse.setIssueInstant(new DateTime());
+               samlResponse.setVersion(SAML_VERSION);
+               samlResponse.setID(getIdGenerator().generateIdentifier());
+               samlResponse.setRecipient(relyingParty.getRelyingPartyID());
+
+               List<Assertion> assertionList = samlResponse.getAssertions();
+               for (Assertion assertion : assertions) {
+                       assertionList.add(assertion);
+               }
+
+               request.setAttribute("acceptanceURL", shire);
+               request.setAttribute("target", target);
+
+               RequestDispatcher dispatcher = request
+                               .getRequestDispatcher("/IdP_SAML1_POST.jdp");
+               dispatcher.forward(request, response);
        }
-       
-       // no AssertionConsumerServices were found, or none had a recognized binding
-       log.error("Unable to find a Browswer/POST or Artifact binding "
-               + " for an AssertionConsumerService in " + spDecsriptor.getID());
-       
-       throw new MetadataException("Unable to find a Browswer/POST or Artifact binding "
-               + " for an AssertionConsumerService in " + spDecsriptor.getID());
-    }
-    
-    
-    /**
-     * Sign an XMLObject.
-     *
-     * @param object The XMLObject to be signed
-     */
-    protected void SignXMLObject(final SignableXMLObject object) throws KeyException {
-       // sign the object
-    }
-    
-    
-    /**
-     * Validate the AssertionConsumer ("shire") URL against the metadata.
-     *
-     * @param spDescriptor The SPSSO element from the metadata
-     * @param URL The "shire" URL.
-     *
-     * @return a {@link List} of AssertionConsumerServices which have
-     *     <code>url</code> as their location.
-     */
-    protected List<AssertionConsumerService> validateAssertionConsumerURL(
-           final SPSSODescriptor spDescriptor, String url) {
-       
-       // spDescriptor returns a reference to an internal mutable copy, so make a copy of it.
-       List<AssertionConsumerService> consumerURLs = new FastList<AssertionConsumerService>();
-       
-       // filter out any list elements that don't have the correct location field
-       // copy any consumerURLs with the correct location
-       for (AssertionConsumerService service : spDescriptor.getAssertionConsumerServices()) {
-           if (service.getLocation().equals(url)) {
-               consumerURLs.add(service);
-           }
+
+       /**
+        * Get the Shibboleth profile-specific request parameters. The shire,
+        * target, providerId and remoteAddr parameters will be populated upon
+        * successful return.
+        * 
+        * @param request
+        *            The servlet request from the SP.
+        * @param shire
+        *            The AttributeConsumerService URL
+        * @param target
+        *            The location to which to POST the response.
+        * @param providerId
+        *            The SP's provider ID in the metadata.
+        * @param remoteAddr
+        *            The address of the requestor.
+        * 
+        * @return <code>true</code> if the request contains valid parameters.
+        */
+       protected boolean getRequestParameters(HttpServletRequest request,
+                       String shire, String target, String providerId, String remoteAddr) {
+
+               target = request.getParameter("target");
+               providerId = request.getParameter("providerId");
+               shire = request.getParameter("shire");
+               remoteAddr = request.getRemoteAddr();
+
+               if (target == null || target.equals("")) {
+                       log
+                                       .error("Shib 1 SSO request is missing or contains an invalid target parameter");
+                       return false;
+               }
+
+               if (providerId == null || providerId.equals("")) {
+                       log
+                                       .error("Shib 1 SSO request is missing or contains an invalid provierId parameter");
+                       return false;
+               }
+
+               if (shire == null || providerId.equals("")) {
+                       log
+                                       .error("Shib 1 SSO request is missing or contains an invalid shire parameter");
+                       return false;
+               }
+
+               if (remoteAddr == null || remoteAddr.equals("")) {
+                       log
+                                       .error("Unable to obtain requestor address when processing Shib 1 SSO request");
+                       return false;
+               }
+
+               return true;
        }
-       
-       return consumerURLs;
-    }
-    
-    
-    /**
-     * Validate the "freshness" of an authn request.
-     * If the reqeust is more than 30 minutes old, reject it.
-     *
-     * @param request The HttpServletRequest
-     * @param response The HttpServletResponse
-     * @param cookieName The name of the RP's cookie.
-     *
-     * @return <code>true</code> if the cookie is fresh; otherwise <code>false</code>
-     */
-    protected boolean validateFreshness(HttpServletRequest request,
-           HttpServletResponse response, String cookieName) throws IOException, ServletException {
-       
-       if (cookieName == null) {
-           return false;
+
+       /**
+        * Generate a SAML 1 AuthenticationStatement.
+        * 
+        * @param loginCtx
+        *            The LoginContext.
+        * @param relyingParty
+        *            The Replying Party configuration for the SP.
+        * @param ssoConfig
+        *            The ShibbolethSSOConfiguration data.
+        * @param spID
+        *            The providerID of the SP that sent the request.
+        * @param spDescriptor
+        *            The SPSSO Descriptor from the metadata.
+        * @param subjectConfirmationMethod
+        *            The SubjectConfirmationMethod. If <code>null</code> no
+        *            SubjectConfirmationMethod element will be generated.
+        * @param now
+        *            The current timestamp
+        * 
+        * @return A SAML 1 Authentication Assertion or <code>null</code> on
+        *         error.
+        */
+       protected Assertion generateAuthenticationAssertion(
+                       final LoginContext loginCtx,
+                       final RelyingPartyConfiguration relyingParty,
+                       final ShibbolethSSOConfiguration ssoConfig, String spID,
+                       final SPSSODescriptor spDescriptor,
+                       String subjectConfirmationMethod, final DateTime now) {
+
+               Assertion authenticationAssertion = (Assertion) assertionBuilder
+                               .build(Assertion.DEFAULT_ELEMENT_NAME);
+
+               authenticationAssertion.setIssueInstant(now);
+               authenticationAssertion.setVersion(SAMLVersion.VERSION_11);
+               authenticationAssertion.setIssuer(relyingParty.getProviderID());
+               authenticationAssertion.setID(getIdGenerator().generateIdentifier());
+               authenticationAssertion.setIssuer(relyingParty.getProviderID());
+
+               Conditions conditions = authenticationAssertion.getConditions();
+               conditions.setNotBefore(now.minusSeconds(30)); // for now, clock skew
+                                                                                                               // is hard-coded to 30
+                                                                                                               // seconds.
+               conditions.setNotOnOrAfter(now.plusMillis(ssoConfig
+                               .getAssertionLifetime()));
+
+               List<AudienceRestrictionCondition> audiences = conditions
+                               .getAudienceRestrictionConditions();
+               AudienceRestrictionCondition restrictionCondition = (AudienceRestrictionCondition) audienceRestrictionBuilder
+                               .buildObject(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
+               Audience rpAudience = (Audience) audienceBuilder
+                               .buildObject(Audience.DEFAULT_ELEMENT_NAME);
+               rpAudience.setURI(relyingParty.getProviderID());
+               audiences.add(rpAudience);
+               if (!relyingParty.getProviderID().equals(spID)) {
+                       Audience spAudience = (Audience) audienceBuilder
+                                       .buildObject(Audience.DEFAULT_ELEMENT_NAME);
+                       spAudience.setURI(spID);
+                       audiences.add(spAudience);
+               }
+
+               AuthenticationStatement authenticationStatement = (AuthenticationStatement) authnStmtBuilder
+                               .buildObject(AuthenticationStatement.DEFAULT_ELEMENT_NAME);
+
+               authenticationStatement.setSubject(buildSubject(loginCtx,
+                               subjectConfirmationMethod, relyingParty));
+               authenticationStatement.setAuthenticationInstant(loginCtx
+                               .getAuthenticationInstant());
+               authenticationStatement
+                               .setAuthenticationMethod(authenticationMethodURI);
+
+               authenticationAssertion.getAuthenticationStatements().add(
+                               authenticationStatement);
+
+               if (spDescriptor.getWantAssertionsSigned()) {
+                       // sign the assertion
+               }
+
+               return authenticationStatement;
        }
-       
-       String timestamp = request.getParameter("time");
-       if (timestamp == null || timestamp.equals("")) {
-           return true;
+
+       /**
+        * Get the protocol binding to use for sending the authentication assertion.
+        * Currently, only Browser/POST and Artifact are supported. This method will
+        * return the first recognized binding that it locates.
+        * 
+        * @param spDescriptor
+        *            The SP's SPSSODescriptor
+        * @param endpoints
+        *            The list of AssertionConsumerEndpoints with the "shire" URL as
+        *            their location.
+        * @param shireURL
+        *            The "shire" url from the authn request.
+        * 
+        * @return The protocol binding for a given SPSSODescriptor.
+        * 
+        * @throws MetadataException
+        *             if no Browswer/POST or Artifact binding can be found.
+        */
+       protected ENDPOINT_BINDING getProtocolBinding(
+                       final SPSSODescriptor spDecsriptor,
+                       final List<AssertionConsumerService> endpoints, String shireURL)
+                       throws MetadataException {
+
+               // check the default AssertionConsumerService first.
+               AssertionConsumerService defaultConsumer = spDescriptor
+                               .getDefaultAssertionConsumerService();
+
+               if (defaultConsumer != null
+                               && defaultConsumer.getLocation().equals(acceptanceURL)) {
+
+                       if (defaultConsumer.getBinding().equals(PROFILE_ARTIFACT_URI)) {
+                               return ENDPOINT_BINDING.ARTIFACT;
+                       } else if (defaultConsumer.getBinding().equals(
+                                       PROFILE_BROWSER_POST_URI)) {
+                               return ENDPOINT_BINDING.BROWSER_POST;
+                       }
+               }
+
+               // check the (already filtered) list of AssertionConsumer endpoints
+               for (AssertionConsumerService endpoint : endpoints) {
+                       if (endpoint.getBinding().equals(PROFILE_ARTIFACT_URI)) {
+                               return ENDPOINT_BINDING.ARTIFACT;
+                       } else if (endpoint.getBinding().equals(PROFILE_BROWSER_POST_URI)) {
+                               return ENDPOINT_BINDING.BROWSER_POST;
+                       }
+               }
+
+               // no AssertionConsumerServices were found, or none had a recognized
+               // binding
+               log
+                               .error("Unable to find a Browswer/POST or Artifact binding "
+                                               + " for an AssertionConsumerService in "
+                                               + spDecsriptor.getID());
+
+               throw new MetadataException(
+                               "Unable to find a Browswer/POST or Artifact binding "
+                                               + " for an AssertionConsumerService in "
+                                               + spDecsriptor.getID());
        }
-       
-       long reqtime;
-       try {
-           reqtime = Long.parseLong(timestamp);
-       } catch (NumberFormatException ex) {
-           log.error("Unable to parse Authentication Request's timestamp", ex);
-           return false;
+
+       /**
+        * Sign an XMLObject.
+        * 
+        * @param object
+        *            The XMLObject to be signed
+        */
+       protected void SignXMLObject(final SignableXMLObject object)
+                       throws KeyException {
+               // sign the object
        }
-       
-       if (reqtime * 1000 < System.currentTimeMillis() - requestTTL * 1000) {
-           RequestDispatcher rd = request.getRequestDispatcher("/IdPStale.jsp");
-           rd.forward(request, response);
-           return false;
+
+       /**
+        * Validate the AssertionConsumer ("shire") URL against the metadata.
+        * 
+        * @param spDescriptor
+        *            The SPSSO element from the metadata
+        * @param URL
+        *            The "shire" URL.
+        * 
+        * @return a {@link List} of AssertionConsumerServices which have
+        *         <code>url</code> as their location.
+        */
+       protected List<AssertionConsumerService> validateAssertionConsumerURL(
+                       final SPSSODescriptor spDescriptor, String url) {
+
+               // spDescriptor returns a reference to an internal mutable copy, so make
+               // a copy of it.
+               List<AssertionConsumerService> consumerURLs = new FastList<AssertionConsumerService>();
+
+               // filter out any list elements that don't have the correct location
+               // field
+               // copy any consumerURLs with the correct location
+               for (AssertionConsumerService service : spDescriptor
+                               .getAssertionConsumerServices()) {
+                       if (service.getLocation().equals(url)) {
+                               consumerURLs.add(service);
+                       }
+               }
+
+               return consumerURLs;
        }
-       
-       for (Cookie cookie : request.getCookies()) {
-           if (cookieName.equals(cookie.getName())) {
+
+       /**
+        * Validate the "freshness" of an authn request. If the reqeust is more than
+        * 30 minutes old, reject it.
+        * 
+        * @param request
+        *            The HttpServletRequest
+        * @param response
+        *            The HttpServletResponse
+        * @param cookieName
+        *            The name of the RP's cookie.
+        * 
+        * @return <code>true</code> if the cookie is fresh; otherwise
+        *         <code>false</code>
+        */
+       protected boolean validateFreshness(HttpServletRequest request,
+                       HttpServletResponse response, String cookieName)
+                       throws IOException, ServletException {
+
+               if (cookieName == null) {
+                       return false;
+               }
+
+               String timestamp = request.getParameter("time");
+               if (timestamp == null || timestamp.equals("")) {
+                       return true;
+               }
+
+               long reqtime;
                try {
-                   long cookieTime = Long.parseLong(cookie.getValue());
-                   if (reqtime <= cookieTime) {
-                       RequestDispatcher rd = request.getRequestDispatcher("/IdPStale.jsp");
+                       reqtime = Long.parseLong(timestamp);
+               } catch (NumberFormatException ex) {
+                       log.error("Unable to parse Authentication Request's timestamp", ex);
+                       return false;
+               }
+
+               if (reqtime * 1000 < System.currentTimeMillis() - requestTTL * 1000) {
+                       RequestDispatcher rd = request
+                                       .getRequestDispatcher("/IdPStale.jsp");
                        rd.forward(request, response);
                        return false;
-                   }
-               } catch (NumberFormatException ex) {
-                   log.error("Unable to parse freshness cookie's timestamp", ex);
-                   return false;
                }
-           }
+
+               for (Cookie cookie : request.getCookies()) {
+                       if (cookieName.equals(cookie.getName())) {
+                               try {
+                                       long cookieTime = Long.parseLong(cookie.getValue());
+                                       if (reqtime <= cookieTime) {
+                                               RequestDispatcher rd = request
+                                                               .getRequestDispatcher("/IdPStale.jsp");
+                                               rd.forward(request, response);
+                                               return false;
+                                       }
+                               } catch (NumberFormatException ex) {
+                                       log.error("Unable to parse freshness cookie's timestamp",
+                                                       ex);
+                                       return false;
+                               }
+                       }
+               }
+
+               return true;
        }
-       
-       return true;
-    }
-    
-    
-    /**
-     * Generate the RP's cookie name
-     *
-     * @param providerID The RP's providerID
-     *
-     * @throws NoSuchAlgorithmException If unable to find a JCE provider for SHA-1
-     *
-     * @return the RP's cookie name
-     */
-    protected String getRPCookieName(String providerID) throws NoSuchAlgorithmException {
-       
-       MessageDigest digester = MessageDigest.getInstance(RP_COOKIE_DIGEST_ALG);       
-       return "shib_sp_" + new String(Hex.encode(digester.digest(providerID.getBytes("UTF-8"))));
-    }
-    
-    
-    /**
-     * Write the current time into the freshness cookie.
-     */
-    protected void writeFreshnessCookie(HttpServletRequest request,
-           HttpServletResponse response, String cookieName) {
-       
-       String timestamp = request.getParameter("time");
-       if (timestamp == null || timestamp.equals("")) {
-           return;
+
+       /**
+        * Generate the RP's cookie name
+        * 
+        * @param providerID
+        *            The RP's providerID
+        * 
+        * @throws NoSuchAlgorithmException
+        *             If unable to find a JCE provider for SHA-1
+        * 
+        * @return the RP's cookie name
+        */
+       protected String getRPCookieName(String providerID)
+                       throws NoSuchAlgorithmException {
+
+               MessageDigest digester = MessageDigest
+                               .getInstance(RP_COOKIE_DIGEST_ALG);
+               return "shib_sp_"
+                               + new String(Hex.encode(digester.digest(providerID
+                                               .getBytes("UTF-8"))));
        }
-       
-       Cookie cookie = new Cookie(cookieName, timestamp);
-       cookie.setSecure(true);
-       response.addCookie(cookie);
-    }
-    
-    
-    /**
-     * Generate a SAML 1 Subject element.
-     *
-     * @param loginContext The LoginContext for an authenticated user.
-     * @param confirmationMethod The SubjectConfirmationMethod URI, or <code>null</code> is none is to be set.
-     * @param relyingParty The RelyingPartyConfiguration for the request.
-     *
-     * @return a Subject object.
-     */
-    protected Subject buildSubject(final LoginContext loginCtx,
-           String confirmationMethod, final RelyingPartyConfiguration relyingParty) {
-       
-       Subject subject = (Subject)subjectBuilder.buildObject(Subject.DEFAULT_ELEMENT_NAME);
-
-       NameIdentifier nameID = (NameIdentifier)nameIdentifierBuilder.buildObject(NameIdentifier.DEFAULT_ELEMENT_NAME);
-       nameID.setFormat(relyingParty.getDefaultNameIDFormat());
-       String username = loginCtx.getUserID();
-       // XXX: todo: map the username onto an appropriate format
-       nameID.setNameQualifier(username);
-       
-       if (subjectConfirmationMethod != null) {
-           
-           SubjectConfirmation subjConf =
-                   (SubjectConfirmation)subjConfBuilder.buildObject(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
-           
-           ConfirmationMethod m =
-                   (ConfirmationMethod)confMethodBuilder.buildObject(ConfirmationMethod.DEFAULT_ELEMENT_NAME);
-           
-           m.setConfirmationMethod(subjectConfirmationMethod);
-           subjConf.getConfirmationMethods().add(m);
-           subject.setSubjectConfirmation(subjConf);
+
+       /**
+        * Write the current time into the freshness cookie.
+        */
+       protected void writeFreshnessCookie(HttpServletRequest request,
+                       HttpServletResponse response, String cookieName) {
+
+               String timestamp = request.getParameter("time");
+               if (timestamp == null || timestamp.equals("")) {
+                       return;
+               }
+
+               Cookie cookie = new Cookie(cookieName, timestamp);
+               cookie.setSecure(true);
+               response.addCookie(cookie);
        }
-       
-       return subject;
-    }
-    
-    
-    /**
-     * Build a SAML 1 Status element.
-     *
-     * @param statusCode The status code - see oasis-sstc-saml-core-1.1, section 3.4.3.1.
-     * @param statusMessage The status message, or <code>null</code> if none is to be set.
-     *
-     * @return The Status object, or <code>null</code> on error.
-     */
-    protected Status buildStatus(String statusCode, String statusMessage) {
-       
-       if (statusCode == null || statusCode.equals("")) {
-           return null;
+
+       /**
+        * Generate a SAML 1 Subject element.
+        * 
+        * @param loginContext
+        *            The LoginContext for an authenticated user.
+        * @param confirmationMethod
+        *            The SubjectConfirmationMethod URI, or <code>null</code> is
+        *            none is to be set.
+        * @param relyingParty
+        *            The RelyingPartyConfiguration for the request.
+        * 
+        * @return a Subject object.
+        */
+       protected Subject buildSubject(final LoginContext loginCtx,
+                       String confirmationMethod,
+                       final RelyingPartyConfiguration relyingParty) {
+
+               Subject subject = (Subject) subjectBuilder
+                               .buildObject(Subject.DEFAULT_ELEMENT_NAME);
+
+               NameIdentifier nameID = (NameIdentifier) nameIdentifierBuilder
+                               .buildObject(NameIdentifier.DEFAULT_ELEMENT_NAME);
+               nameID.setFormat(relyingParty.getDefaultNameIDFormat());
+               String username = loginCtx.getUserID();
+               // XXX: todo: map the username onto an appropriate format
+               nameID.setNameQualifier(username);
+
+               if (subjectConfirmationMethod != null) {
+
+                       SubjectConfirmation subjConf = (SubjectConfirmation) subjConfBuilder
+                                       .buildObject(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
+
+                       ConfirmationMethod m = (ConfirmationMethod) confMethodBuilder
+                                       .buildObject(ConfirmationMethod.DEFAULT_ELEMENT_NAME);
+
+                       m.setConfirmationMethod(subjectConfirmationMethod);
+                       subjConf.getConfirmationMethods().add(m);
+                       subject.setSubjectConfirmation(subjConf);
+               }
+
+               return subject;
        }
-       
-       Status status = (Status)statusBuilder.buildObject(Status.DEFAULT_ELEMENT_NAME);
-       StatusCode sc = (StatusCode)statusCodeBuilder.buildObject(StatusCode.DEFAULT_ELEMENT_NAME);
-       sc.setValue(statusCode);
-       status.setStatusCode(sc);
-       
-       if (statusMessage != null || !(statusMessage.equals(""))) {
-           
-           StatusMessage sm = (StatusMessage)statusMessageBuilder.buildObject(StatusMessage.DEFAULT_ELEMENT_NAME);
-           sm.setMessage(statusMessage);
-           status.setStatusMessage(sm);
+
+       /**
+        * Build a SAML 1 Status element.
+        * 
+        * @param statusCode
+        *            The status code - see oasis-sstc-saml-core-1.1, section
+        *            3.4.3.1.
+        * @param statusMessage
+        *            The status message, or <code>null</code> if none is to be
+        *            set.
+        * 
+        * @return The Status object, or <code>null</code> on error.
+        */
+       protected Status buildStatus(String statusCode, String statusMessage) {
+
+               if (statusCode == null || statusCode.equals("")) {
+                       return null;
+               }
+
+               Status status = (Status) statusBuilder
+                               .buildObject(Status.DEFAULT_ELEMENT_NAME);
+               StatusCode sc = (StatusCode) statusCodeBuilder
+                               .buildObject(StatusCode.DEFAULT_ELEMENT_NAME);
+               sc.setValue(statusCode);
+               status.setStatusCode(sc);
+
+               if (statusMessage != null || !(statusMessage.equals(""))) {
+
+                       StatusMessage sm = (StatusMessage) statusMessageBuilder
+                                       .buildObject(StatusMessage.DEFAULT_ELEMENT_NAME);
+                       sm.setMessage(statusMessage);
+                       status.setStatusMessage(sm);
+               }
+
+               return status;
+       }
+
+       /**
+        * Get an Attribute Statement.
+        * 
+        * @param rpConfig
+        *            The RelyingPartyConfiguration for the request.
+        * @param subject
+        *            The Subject of the request.
+        * @param request
+        *            The ServletRequest.
+        * 
+        * @return An AttributeStatement.
+        * 
+        * @throws ServletException
+        *             On error.
+        */
+       protected AttributeStatement getAttributeStatement(
+                       RelyingPartyConfiguration rpConfig, Subject subject,
+                       ServletRequest request) throws ServletException {
+
+               // build a dummy AttributeQuery object for the AA.
+
+               AttributeAuthority aa = new AttributeAuthority();
+               aa.setAttributeResolver(getAttributeResolver());
+               aa.setFilteringEngine(getFilteringEngine());
+               // aa.setSecurityPolicy(getDecoder().getSecurityPolicy()); //
+               // super.getDecoder() will need to change.
+               aa.setRequest(request);
+               aa.setRelyingPartyConfiguration(rpConfig);
+               AttributeStatement statement = null;
+               try {
+                       statement = aa.performAttributeQuery(message);
+               } catch (AttributeResolutionException e) {
+                       log.error("Error resolving attributes", e);
+                       throw new ServletException("Error resolving attributes");
+               } catch (FilteringException e) {
+                       log.error("Error filtering attributes", e);
+                       throw new ServletException("Error filtering attributes");
+               }
+
+               return statement;
        }
-       
-       return status;
-    }
 }
\ No newline at end of file