--- /dev/null
+/*
+ * 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>) <GetComplete/>
+ * 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;
+ }
+}
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;
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;
/**
* 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;
+ }
}
+++ /dev/null
-/*
- * 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>) <GetComplete/> 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;
- }
-}
--- /dev/null
+/*
+ * 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
+ }
+}
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;
}
/**
*
* @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;
}
/**
* 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;
}
/**
* 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;
}
}
/**
* 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 {