--- /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;
+
+import javax.servlet.ServletRequest;
+
+import org.opensaml.common.binding.MessageDecoder;
+
+import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
+
+/**
+ * Shibboleth {@link ProfileRequest}.
+ */
+public class ShibbolethProfileRequest implements ProfileRequest {
+
+ /** Request to process. */
+ private ServletRequest request;
+
+ /** For decoding requests. */
+ private MessageDecoder<ServletRequest> messageDecoder;
+
+ /**
+ * Constructor.
+ *
+ * @param r to process
+ * @param d for decoding the servlet request
+ */
+ public ShibbolethProfileRequest(ServletRequest r, MessageDecoder<ServletRequest> d) {
+ request = r;
+ messageDecoder = d;
+ }
+
+ /** {@inheritDoc} */
+ public ServletRequest getRequest() {
+ return request;
+ }
+
+ /** {@inheritDoc} */
+ public MessageDecoder<ServletRequest> getMessageDecoder() {
+ return messageDecoder;
+ }
+}
--- /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;
+
+import javax.servlet.ServletResponse;
+
+import org.opensaml.common.binding.MessageEncoder;
+
+import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
+
+/**
+ * Shibboleth {@link ProfileResponse}.
+ */
+public class ShibbolethProfileResponse implements ProfileResponse {
+
+ /** Response to send back to client. */
+ private ServletResponse response;
+
+ /** For encoding responses. */
+ private MessageEncoder<ServletResponse> messageEncoder;
+
+ /**
+ * Constructor.
+ *
+ * @param r to send back
+ * @param e for encoding the servlet response
+ */
+ public ShibbolethProfileResponse(ServletResponse r, MessageEncoder<ServletResponse> e) {
+ response = r;
+ messageEncoder = e;
+ }
+
+ /** {@inheritDoc} */
+ public ServletResponse getResponse() {
+ return response;
+ }
+
+ /** {@inheritDoc} */
+ public MessageEncoder<ServletResponse> getMessageEncoder() {
+ return messageEncoder;
+ }
+}
package edu.internet2.middleware.shibboleth.idp.profile.saml1;
import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
import edu.internet2.middleware.shibboleth.common.profile.ProfileHandler;
+import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
+import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
/**
* SAML 1 Artifact Query profile handler.
public class ArtifactQuery implements ProfileHandler {
/** {@inheritDoc} */
- public boolean processRequest(ServletRequest request, ServletResponse response) throws ServletException {
+ public boolean processRequest(ProfileRequest request, ProfileResponse response) throws ServletException {
// TODO Auto-generated method stub
return false;
}
package edu.internet2.middleware.shibboleth.idp.profile.saml1;
+import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
+import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
+
import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.opensaml.saml1.core.SubjectStatement;
import org.opensaml.xml.encryption.EncryptionException;
-import edu.internet2.middleware.shibboleth.common.attribute.filtering.FilteringException;
+import edu.internet2.middleware.shibboleth.common.attribute.filtering.AttributeFilteringException;
import edu.internet2.middleware.shibboleth.common.attribute.resolver.AttributeResolutionException;
/**
private static Logger log = Logger.getLogger(AttributeQuery.class);
/** {@inheritDoc} */
- public boolean processRequest(ServletRequest request, ServletResponse response) throws ServletException {
+ public boolean processRequest(ProfileRequest request, ProfileResponse response) throws ServletException {
if (log.isDebugEnabled()) {
log.debug("begin processRequest");
}
package edu.internet2.middleware.shibboleth.idp.profile.saml1;
+import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
+import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
+
import java.io.IOException;
import java.net.URLEncoder;
import java.security.MessageDigest;
/**
* 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);
+ /** 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 bearer confirmation method URI. */
- protected static final String BEARER_CONF_METHOD_URI = "urn:oasis:names:tc:SAML:1.0:cm:bearer";
+ /** 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 confirmation method URI */
- protected static final String ARTIFACT_CONF_METHOD_URI = "urn:oasis:names:tc:SAML:1.0:cm:artifact";
+ /** SAML 1 Artifact protocol URI. */
+ protected static final String PROFILE_ARTIFACT_URI = "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01";
- /** SAML 1.1 SPSSO protocol URI */
- protected static final String SAML11_PROTOCOL_URI = "urn:oasis:names:tc:SAML:1.1:protocol";
+ /** The digest algorithm for generating SP cookies. */
+ protected static final String RP_COOKIE_DIGEST_ALG = "SHA-1";
- /** SAML 1 Browser/POST protocol URI. */
- protected static final String PROFILE_BROWSER_POST_URI = "urn:oasis:names:tc:SAML:1.0:profiles:browser-post";
+ /** The RelyingPartyManager. */
+ protected RelyingPartyManager rpManager;
- /** SAML 1 Artifact protocol URI. */
- protected static final String PROFILE_ARTIFACT_URI = "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01";
+ /**
+ * Backing store for artifacts. This must be shared between ShibbolethSSO and AttributeQuery.
+ */
+ protected ArtifactMap artifactMap;
- /** The digest algorithm for generating SP cookies. */
- protected static final String RP_COOKIE_DIGEST_ALG = "SHA-1";
+ /** The path to the IdP's AuthenticationManager servlet */
+ protected String authnMgrURL;
- /** The RelyingPartyManager. */
- protected RelyingPartyManager rpManager;
+ /** The URI of the default authentication method */
+ protected String authenticationMethodURI;
- /**
- * Backing store for artifacts. This must be shared between ShibbolethSSO
- * and AttributeQuery.
- */
- protected ArtifactMap artifactMap;
+ /** Builder for AuthenticationStatement objects. */
+ protected XMLObjectBuilder authnStmtBuilder;
- /** The path to the IdP's AuthenticationManager servlet */
- protected String authnMgrURL;
+ /** Builder for Subject elements. */
+ protected XMLObjectBuilder subjectbuilder;
- /** The URI of the default authentication method */
- protected String authenticationMethodURI;
+ /** Builder for SubjectConfirmation objects. */
+ protected XMLObjectBuilder subjConfBuilder;
- /** Builder for AuthenticationStatement objects. */
- protected XMLObjectBuilder authnStmtBuilder;
+ /** Builder for SubjectConfirmationMethod objects. */
+ protected XMLObjectBuilder confMethodBuilder;
- /** Builder for Subject elements. */
- protected XMLObjectBuilder subjectbuilder;
+ /** Builder for Artifacts. */
+ protected XMLObjectBuilder artifactBuilder;
- /** Builder for SubjectConfirmation objects. */
- protected XMLObjectBuilder subjConfBuilder;
+ /** Builder for NameIdentifiers. */
+ protected XMLObjectBuilder nameIdentifierBuilder;
- /** 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();
- }
-
- /**
- * 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());
- 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;
- }
-
- /**
- * 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);
- }
-
- /**
- * 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);
- }
-
- /**
- * 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;
- }
-
- /**
- * 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;
- }
-
- /**
- * 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());
- }
-
- /**
- * 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);
- }
- }
-
- 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;
- }
-
- String timestamp = request.getParameter("time");
- if (timestamp == null || timestamp.equals("")) {
- return true;
- }
-
- long reqtime;
- try {
- 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;
- }
-
- 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;
- }
-
- /**
- * 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;
- }
-
- 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);
- }
-
- 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;
- }
-
- 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;
- }
+ /** 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();
+ }
+
+ /**
+ * 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(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());
+ return false;
+ }
+
+ HttpServletRequest httpReq = (HttpServletRequest) request.getRequest();
+ HttpServletResponse httpResp = (HttpServletResponse) response.getResponse();
+ 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.getRequest().getRequestDispatcher(authnMgrURL);
+ dispatcher.forward(request.getRequest(), response.getResponse());
+ } 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;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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());
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ 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;
+ }
+
+ String timestamp = request.getParameter("time");
+ if (timestamp == null || timestamp.equals("")) {
+ return true;
+ }
+
+ long reqtime;
+ try {
+ 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;
+ }
+
+ 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ 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);
+ }
+
+ 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;
+ }
+
+ 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;
+ }
}
\ No newline at end of file
package edu.internet2.middleware.shibboleth.idp.profile.saml2;
+import java.util.List;
+
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
import org.opensaml.Configuration;
import org.opensaml.common.IdentifierGenerator;
import org.opensaml.common.SAMLObject;
import org.opensaml.common.binding.MessageDecoder;
import org.opensaml.common.binding.MessageEncoder;
import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
+import org.opensaml.saml2.core.Assertion;
+import org.opensaml.saml2.core.Audience;
+import org.opensaml.saml2.core.AudienceRestriction;
+import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.Issuer;
+import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.Status;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.core.StatusMessage;
-import org.opensaml.saml2.encryption.Encrypter;
-import org.opensaml.saml2.metadata.provider.MetadataProvider;
+import org.opensaml.saml2.core.Subject;
+import org.opensaml.xml.XMLObjectBuilder;
import org.opensaml.xml.XMLObjectBuilderFactory;
-import edu.internet2.middleware.shibboleth.common.attribute.filtering.FilteringEngine;
-import edu.internet2.middleware.shibboleth.common.attribute.resolver.AttributeResolver;
import edu.internet2.middleware.shibboleth.common.profile.ProfileHandler;
-import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
+import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyManager;
/**
* Common implementation details for profile handlers.
*/
public abstract class AbstractProfileHandler implements ProfileHandler {
- /** SAML Version for this profile handler. */
- public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
-
- /** Class logger. */
- private static Logger log = Logger.getLogger(AbstractProfileHandler.class);
-
- /** For building XML. */
- private XMLObjectBuilderFactory builderFactory;
-
- /** For generating random ids. */
- private IdentifierGenerator idGenerator;
-
- /** For decoding requests. */
- private MessageDecoder<ServletRequest> decoder;
-
- /** For encoding responses. */
- private MessageEncoder<ServletResponse> encoder;
-
- /** For resolving attributes. */
- private AttributeResolver resolver;
-
- /** To determine releasable attributes. */
- private FilteringEngine engine;
-
- /** For encrypting XML. */
- private Encrypter encrypter;
-
- /** Builder for Response elements. */
- protected XMLObjectBuilder responseBuilder;
-
- /** Builder for Status elements. */
- private XMLObjectBuilder statusBuilder;
-
- /** Builder for StatusCode elements. */
- private XMLObjectBuilder statusCodeBuilder;
-
- /** Builder for StatusMessage elements. */
- private XMLObjectBuilder statusMessageBuilder;
-
- /** Builder for Issuer elements. */
- protected XMLObjectBuilder issuerBuilder;
-
- /**
- * Default constructor.
- */
- public AbstractProfileHandler() {
- builderFactory = Configuration.getBuilderFactory();
- idGenerator = new SecureRandomIdentifierGenerator();
-
- responseBuilder = builderFactory
- .getBuilder(Response.DEFAULT_ELEMENT_NAME);
- statusBuilder = builderFactory.getBuilder(Status.DEFAILT_ELEMENT_NAME);
- statusCodeBuilder = builderFactory
- .getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
- statusMessageBuilder = builderFactory
- .getBuilder(StatusMessage.DEFAULT_ELEMENT_NAME);
- issuerBuilder = 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 SecureRandomIdentifierGenerator getIdGenerator() {
- return idGenerator;
- }
-
- /**
- * Sets the decoder.
- *
- * @param d
- * <code>MessageDecoder</code>
- */
- public void setDecoder(MessageDecoder<ServletRequest> d) {
- decoder = d;
- }
-
- /**
- * Returns the decoder.
- *
- * @return <code>MessageDecoder</code>
- */
- public MessageDecoder<ServletRequest> getDecoder() {
- return decoder;
- }
-
- /**
- * Sets the encoder.
- *
- * @param e
- * <code>MessageEncoder</code>
- */
- public void setEncoder(MessageEncoder<ServletResponse> e) {
- encoder = e;
- }
-
- /**
- * Returns the encoder.
- *
- * @return <code>MessageEncoder</code>
- */
- public MessageEncoder<ServletResponse> getEncoder() {
- return encoder;
- }
-
- /**
- * Sets the attribute resolver.
- *
- * @param r
- * <code>AttributeResolver</code>
- */
- public void setAttributeResolver(AttributeResolver r) {
- resolver = r;
- }
-
- /**
- * Returns the attribute resolver.
- *
- * @return <code>AttributeResolver</code>
- */
- public AttributeResolver getAttributeResolver() {
- return resolver;
- }
-
- /**
- * Sets the filter engine.
- *
- * @param e
- * <code>FilterEngine</code>
- */
- public void setFilterEngine(FilteringEngine e) {
- engine = e;
- }
-
- /**
- * Returns the filter engine.
- *
- * @return <code>FilterEngine</code>
- */
- public FilteringEngine getFilteringEngine() {
- return engine;
- }
-
- /**
- * Sets the metadata provider.
- *
- * @param p
- * <code>MetadataProvider</code>
- */
- public void setMetadataProvider(MetadataProvider p) {
- provider = p;
- }
-
- /**
- * Returns the metadata provider.
- *
- * @return <code>MetadataProvider</code>
- */
- public MetadataProvider getMetadataProvider() {
- return provider;
- }
-
- /**
- * Returns the relying party configuration.
- *
- * @return Returns the relyingParty.
- */
- public RelyingPartyConfiguration getRelyingPartyConfiguration() {
- return relyingPartyConfiguration;
- }
-
- /**
- * Sets the relying party configuration.
- *
- * @param c
- * The relyingParty to set.
- */
- public void setRelyingPartyConfiguration(RelyingPartyConfiguration c) {
- relyingPartyConfiguration = c;
- }
-
- /**
- * Returns the encrypter.
- *
- * @return Returns the encrypter.
- */
- public Encrypter getEncrypter() {
- return encrypter;
- }
-
- /**
- * Sets the encrypter.
- *
- * @param e
- * The encrypter to set.
- */
- public void setEncrypter(Encrypter e) {
- encrypter = e;
- }
-
- /**
- * This decodes the attribute query message from the supplied request.
- *
- * @param request
- * <code>ServletRequest</code>
- * @return <code>SAMLObject</code>
- * @throws BindingException
- * if the request cannot be decoded
- */
- protected SAMLObject decodeMessage(ServletRequest request)
- throws BindingException {
-
- decoder.setRequest(request);
- decoder.decode();
- if (log.isDebugEnabled()) {
- log.debug("decoded servlet request");
- }
-
- return decoder.getSAMLMessage();
- ;
- }
-
- /**
- * This encodes the supplied response.
- *
- * @param response
- * <code>SAMLObject</code>
- * @throws BindingException
- * if the response cannot be encoded
- */
- protected void encodeResponse(SAMLObject response) throws BindingException {
-
- encoder.setSAMLMessage(response);
- encoder.encode();
- }
-
- /**
- * 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 = (Status) statusBuilder
- .buildObject(Status.DEFAULT_ELEMENT_NAME);
- StatusCode statusCode = (StatusCode) statusCodeBuilder
- .buildObject(StatusCode.DEFAULT_ELEMENT_NAME);
-
- statusCode.setValue(topLevelCode);
- if (secondLevelCode != null) {
- StatusCode secondLevelStatusCode = (StatusCode) statusCodeBuilder
- .buildObject(StatusCode.DEFAULT_ELEMENT_NAME);
- secondLevelStatusCode.setValue(secondLevelCode);
- statusCode.setStatusCode(secondLevelStatusCode);
- }
-
- if (secondLevelFailureMessage != null) {
- StatusMessage msg = (StatusMessage) statusMessageBuilder
- .buildObject(StatusMessage.DEFAULT_ELEMENT_NAME);
- msg.setMessage(secondLevelFailureMessage);
- status.setMessage(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 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, String issuer,
- final Status status) {
-
- Response response = (Response) responseBuilder
- .buildObject(Response.DEFAULT_ELEMENT_NAME);
-
- Issuer i = (Issuer) issuerBuilder
- .buildObject(Issuer.DEFAULT_ELEMENT_NAME);
- i.setValue(issuer);
-
- response.setVersion(SAML_VERSION);
- response.setId(getIdGenerator().generateIdentifier());
- response.setInResponseto(inResponseTo);
- response.setIssueInstance(new DateTime());
- response.setIssuer(i);
- response.setStatus(status);
-
- return response;
- }
-
- protected Assertion buildAssertion(final Subjcet subject,
- final Conditions conditions, String issuer, final String[] audiences) {
-
- Assertion assertion = (Assertion) assertionBuilder
- .buildObject(Assertion.DEFAULT_ELEMENT_NAME);
- assertion.setID(getIdGenerator().generateIdentifier());
- assertion.setVersion(SAML_VERSION);
- assertion.setIssueInstant(new DateTime());
- assertion.setConditions(conditions);
- assertion.setSubject(subject);
-
- Issuer i = (Issuer) issuerBuilder
- .buildObject(Issuer.DEFAULT_ELEMENT_NAME);
- 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 = (Audience) audienceBuilder
- .buildObject(Audience.DEFAULT_ELEMENT_NAME);
- audience.setAudienceURI(audienceURI);
-
- AudienceRestriction audienceRestriction = (AudienceRestriction) audienceRestrictionBuilder
- .buildObject(AudienceRestriction.DEFAULT_ELEMENT_NAME);
- List<Audience> audienceList = audienceRestriction
- .getAudiences();
- audienceList.add(audience);
-
- audienceRestrictionConditions.add(audienceRestriction);
- }
- }
-
- return assertion;
- }
+ /** SAML Version for this profile handler. */
+ public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
+
+ /** Class logger. */
+ private static Logger log = Logger.getLogger(AbstractProfileHandler.class);
+
+ /** For building XML. */
+ private XMLObjectBuilderFactory builderFactory;
+
+ /** For generating random ids. */
+ private IdentifierGenerator idGenerator;
+
+ /** Relying party configuration. */
+ private RelyingPartyManager relyingPartyManager;
+
+ /** Builder for Response elements. */
+ protected XMLObjectBuilder responseBuilder;
+
+ /** Builder for Status elements. */
+ private XMLObjectBuilder statusBuilder;
+
+ /** Builder for StatusCode elements. */
+ private XMLObjectBuilder statusCodeBuilder;
+
+ /** Builder for StatusMessage elements. */
+ private XMLObjectBuilder statusMessageBuilder;
+
+ /** Builder for Issuer elements. */
+ protected XMLObjectBuilder issuerBuilder;
+
+ /**
+ * Default constructor.
+ */
+ public AbstractProfileHandler() {
+ builderFactory = Configuration.getBuilderFactory();
+ idGenerator = new SecureRandomIdentifierGenerator();
+
+ responseBuilder = builderFactory.getBuilder(Response.DEFAULT_ELEMENT_NAME);
+ statusBuilder = builderFactory.getBuilder(Status.DEFAULT_ELEMENT_NAME);
+ statusCodeBuilder = builderFactory.getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
+ statusMessageBuilder = builderFactory.getBuilder(StatusMessage.DEFAULT_ELEMENT_NAME);
+ issuerBuilder = 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;
+ }
+
+ /**
+ * Returns the relying party manager.
+ *
+ * @return Returns the relyingPartyManager.
+ */
+ public RelyingPartyManager getRelyingPartyManager() {
+ return relyingPartyManager;
+ }
+
+ /**
+ * Sets the relying party manager.
+ *
+ * @param m The relyingPartyManager to set.
+ */
+ public void setRelyingPartyManager(RelyingPartyManager m) {
+ relyingPartyManager = m;
+ }
+
+ /**
+ * This decodes the attribute query message from the supplied request.
+ *
+ * @param decoder <code>MessageDecoder</code>
+ * @param request <code>ServletRequest</code>
+ * @return <code>SAMLObject</code>
+ * @throws BindingException if the request cannot be decoded
+ */
+ public static SAMLObject decodeMessage(MessageDecoder<ServletRequest> decoder, ServletRequest request)
+ throws BindingException {
+
+ decoder.setRequest(request);
+ decoder.decode();
+ if (log.isDebugEnabled()) {
+ log.debug("decoded servlet request");
+ }
+
+ return decoder.getSAMLMessage();
+ }
+
+ /**
+ * This encodes the supplied response.
+ *
+ * @param encoder <code>MessageEncoder</code>
+ * @param response <code>SAMLObject</code>
+ * @throws BindingException if the response cannot be encoded
+ */
+ public static void encodeResponse(MessageEncoder<ServletResponse> encoder, SAMLObject response)
+ throws BindingException {
+
+ encoder.setSAMLMessage(response);
+ encoder.encode();
+ }
+
+ /**
+ * 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 = (Status) statusBuilder.buildObject(Status.DEFAULT_ELEMENT_NAME);
+ StatusCode statusCode = (StatusCode) statusCodeBuilder.buildObject(StatusCode.DEFAULT_ELEMENT_NAME);
+
+ statusCode.setValue(topLevelCode);
+ if (secondLevelCode != null) {
+ StatusCode secondLevelStatusCode = (StatusCode) statusCodeBuilder
+ .buildObject(StatusCode.DEFAULT_ELEMENT_NAME);
+ secondLevelStatusCode.setValue(secondLevelCode);
+ statusCode.setStatusCode(secondLevelStatusCode);
+ }
+
+ if (secondLevelFailureMessage != null) {
+ StatusMessage msg = (StatusMessage) statusMessageBuilder.buildObject(StatusMessage.DEFAULT_ELEMENT_NAME);
+ msg.setMessage(secondLevelFailureMessage);
+ status.setMessage(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 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, String issuer, final Status status) {
+
+ Response response = (Response) responseBuilder.buildObject(Response.DEFAULT_ELEMENT_NAME);
+
+ Issuer i = (Issuer) issuerBuilder.buildObject(Issuer.DEFAULT_ELEMENT_NAME);
+ i.setValue(issuer);
+
+ response.setVersion(SAML_VERSION);
+ response.setId(getIdGenerator().generateIdentifier());
+ response.setInResponseto(inResponseTo);
+ response.setIssueInstance(new DateTime());
+ 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.DEFAULT_ELEMENT_NAME);
+ assertion.setID(getIdGenerator().generateIdentifier());
+ assertion.setVersion(SAML_VERSION);
+ assertion.setIssueInstant(new DateTime());
+ assertion.setConditions(conditions);
+ assertion.setSubject(subject);
+
+ Issuer i = (Issuer) issuerBuilder.buildObject(Issuer.DEFAULT_ELEMENT_NAME);
+ 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 = (Audience) audienceBuilder.buildObject(Audience.DEFAULT_ELEMENT_NAME);
+ audience.setAudienceURI(audienceURI);
+
+ AudienceRestriction audienceRestriction = (AudienceRestriction) audienceRestrictionBuilder
+ .buildObject(AudienceRestriction.DEFAULT_ELEMENT_NAME);
+ List<Audience> audienceList = audienceRestriction.getAudiences();
+ audienceList.add(audience);
+
+ audienceRestrictionConditions.add(audienceRestriction);
+ }
+ }
+
+ return assertion;
+ }
}
* 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.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
import edu.internet2.middleware.shibboleth.common.profile.ProfileHandler;
+import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
+import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
/**
- * SAML 2.0 Artifact resolution profile handler
+ * SAML 2.0 Artifact resolution profile handler.
*/
public class ArtifactResolution implements ProfileHandler {
/** {@inheritDoc} */
- public boolean processRequest(ServletRequest request, ServletResponse response) throws ServletException {
+ public boolean processRequest(ProfileRequest request, ProfileResponse response) throws ServletException {
// TODO Auto-generated method stub
return false;
}
package edu.internet2.middleware.shibboleth.idp.profile.saml2;
import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.opensaml.saml2.core.Advice;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.AttributeStatement;
+import org.opensaml.saml2.core.Audience;
+import org.opensaml.saml2.core.AudienceRestriction;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.Issuer;
+import org.opensaml.saml2.core.ProxyRestriction;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.Status;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.core.Subject;
+import org.opensaml.saml2.encryption.Encrypter;
+import org.opensaml.saml2.metadata.provider.MetadataProvider;
+import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.xml.encryption.EncryptionException;
+import org.opensaml.xml.security.credential.Credential;
-import edu.internet2.middleware.shibboleth.common.attribute.filtering.FilteringException;
-import edu.internet2.middleware.shibboleth.common.attribute.resolver.AttributeResolutionException;
+import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
+import edu.internet2.middleware.shibboleth.common.attribute.SAML2AttributeAuthority;
+import edu.internet2.middleware.shibboleth.common.attribute.provider.ShibbolethAttributeRequestContext;
+import edu.internet2.middleware.shibboleth.common.attribute.provider.ShibbolethSAML2AttributeAuthority;
+import edu.internet2.middleware.shibboleth.common.attribute.resolver.AttributeResolver;
+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.AttributeQueryConfiguration;
+import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
/**
* SAML 2.0 Attribute Query profile handler.
/** Class logger. */
private static Logger log = Logger.getLogger(AttributeQuery.class);
+ /** For building response. */
+ private SAMLObjectBuilder<Response> responseBuilder;
+
+ /** For building status. */
+ private SAMLObjectBuilder<Status> statusBuilder;
+
+ /** For building statuscode. */
+ private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
+
+ /** For building assertion. */
+ private SAMLObjectBuilder<Assertion> assertionBuilder;
+
+ /** For building issuer. */
+ private SAMLObjectBuilder<Issuer> issuerBuilder;
+
+ /** For building subject. */
+ private SAMLObjectBuilder<Subject> subjectBuilder;
+
+ /** For building conditions. */
+ private SAMLObjectBuilder<Conditions> conditionsBuilder;
+
+ /** For building audience restriction. */
+ private SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder;
+
+ /** For building audience. */
+ private SAMLObjectBuilder<Audience> audienceBuilder;
+
+ /** For building advice. */
+ private SAMLObjectBuilder<Advice> adviceBuilder;
+
+ /** Provider id. */
+ private String providerId;
+
+ /** Attribute authority. */
+ private SAML2AttributeAuthority attributeAuthority;
+
+ /** Attribute query configuration. */
+ private AttributeQueryConfiguration config;
+
+ /**
+ * This creates a new attribute query.
+ *
+ * @param ar <code>AttributeResolver</code>
+ */
+ public AttributeQuery(AttributeResolver<ShibbolethAttributeRequestContext> ar) {
+ // instantiate configuration
+ config = new AttributeQueryConfiguration();
+ providerId = config.getProfileId();
+
+ // instantiate attribute authority
+ attributeAuthority = new ShibbolethSAML2AttributeAuthority(ar);
+
+ // instantiate XML builders
+ responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
+ statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
+ statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
+ StatusCode.DEFAULT_ELEMENT_NAME);
+ assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
+ .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
+ issuerBuilder = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
+ subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
+ conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
+ Conditions.DEFAULT_ELEMENT_NAME);
+ audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(
+ AudienceRestriction.DEFAULT_ELEMENT_NAME);
+ audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
+ adviceBuilder = (SAMLObjectBuilder<Advice>) getBuilderFactory().getBuilder(Advice.DEFAULT_ELEMENT_NAME);
+ }
+
/** {@inheritDoc} */
- public boolean processRequest(ServletRequest request, ServletResponse response) throws ServletException {
+ public boolean processRequest(ProfileRequest request, ProfileResponse response) throws ServletException {
if (log.isDebugEnabled()) {
log.debug("begin processRequest");
}
// get message from the decoder
org.opensaml.saml2.core.AttributeQuery message = null;
try {
- message = (org.opensaml.saml2.core.AttributeQuery) decodeMessage(request);
+ message = (org.opensaml.saml2.core.AttributeQuery) decodeMessage(request.getMessageDecoder(), request
+ .getRequest());
} catch (BindingException e) {
log.error("Error decoding attribute query message", e);
throw new ServletException("Error decoding attribute query message");
}
- // get attribute statement from attribute authority
- AttributeAuthority aa = new AttributeAuthority();
- aa.setAttributeResolver(getAttributeResolver());
- aa.setFilteringEngine(getFilteringEngine());
- aa.setRelyingPartyConfiguration(getRelyingPartyConfiguration());
- aa.setSecurityPolicy(getDecoder().getSecurityPolicy());
- aa.setRequest(request);
+ // TODO get user data from the session
+ ServiceInformation serviceInformation = null;
+ String principalName = serviceInformation.getSubjectNameID().getSPProvidedID();
+ String authenticationMethod = serviceInformation.getAuthenticationMethod().getAuthenticationMethod();
+
+ // create attribute request for the attribute authority
+ ShibbolethAttributeRequestContext requestContext = null;
+ try {
+ MetadataProvider metadataProvider = getRelyingPartyManager().getMetadataProvider();
+ RelyingPartyConfiguration relyingPartyConfiguration = getRelyingPartyManager()
+ .getRelyingPartyConfiguration(providerId);
+ requestContext = new ShibbolethAttributeRequestContext(metadataProvider, relyingPartyConfiguration);
+ requestContext.setPrincipalName(principalName);
+ requestContext.setPrincipalAuthenticationMethod(authenticationMethod);
+ requestContext.setRequest(request.getRequest());
+ } catch (MetadataProviderException e) {
+ log.error("Error creating ShibbolethAttributeRequestContext", e);
+ throw new ServletException("Error retrieving metadata", e);
+ }
+
+ // resolve attributes with the attribute authority
AttributeStatement statement = null;
try {
- statement = aa.performAttributeQuery(message);
- } catch (AttributeResolutionException e) {
+ statement = attributeAuthority.performAttributeQuery(requestContext);
+ } catch (AttributeRequestException 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");
+ throw new ServletException("Error resolving attributes", e);
}
// construct attribute response
Response samlResponse = null;
try {
- samlResponse = buildResponse(message, request.getRemoteHost(), new DateTime(), statement);
+ ProfileResponseContext profileResponse = new ProfileResponseContext(request, message);
+ profileResponse.setAttributeStatement(statement);
+ samlResponse = buildResponse(profileResponse);
} catch (EncryptionException e) {
log.error("Error encrypting SAML response", e);
- throw new ServletException("Error encrypting SAML response");
+ throw new ServletException("Error encrypting SAML response", e);
}
if (log.isDebugEnabled()) {
log.debug("built saml2 response: " + samlResponse);
}
+ // encode response
try {
- encodeResponse(samlResponse);
+ encodeResponse(response.getMessageEncoder(), samlResponse);
} catch (BindingException e) {
log.error("Error encoding attribute query response", e);
- throw new ServletException("Error encoding attribute query response");
+ throw new ServletException("Error encoding attribute query response", e);
}
return true;
/**
* This builds the response for this SAML request.
*
- * @param message <code>AttributeQuery</code>
- * @param dest <code>String</code>
- * @param issueInstant <code>DateTime</code>
- * @param statement <code>AttributeStatement</code> of attributes
+ * @param responseContext <code>ProfileResponseContext</code>
* @return <code>Response</code>
* @throws EncryptionException if an error occurs attempting to encrypt data
*/
- public Response buildResponse(org.opensaml.saml2.core.AttributeQuery message, String dest, DateTime issueInstant,
- AttributeStatement statement) throws EncryptionException {
- SAMLObjectBuilder<Response> responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(
- Response.DEFAULT_ELEMENT_NAME);
+ private Response buildResponse(ProfileResponseContext responseContext) throws EncryptionException {
/*
* required: samlp:Status, ID, Version, IssueInstant
*/
Response response = responseBuilder.buildObject();
response.setVersion(SAML_VERSION);
response.setID(getIdGenerator().generateIdentifier());
- response.setInResponseTo(getDecoder().getSecurityPolicy().getIssuer().toString());
- response.setIssueInstant(issueInstant);
- response.setDestination(dest);
+ response.setInResponseTo(responseContext.getRequest().getMessageDecoder().getSecurityPolicy().getIssuer()
+ .toString());
+ response.setIssueInstant(responseContext.getIssueInstant());
+ response.setDestination(responseContext.getRequest().getRequest().getRemoteHost());
response.setIssuer(buildIssuer());
- // TODO set consent
+
+ // TODO get consent configuration
/*
* if (consent != null) { response.setConsent(consent); }
*/
- // TODO set extensions
+
+ // TODO get extension configuration
/*
* if (extensions != null) { response.setExtensions(extensions); }
*/
- response.setStatus(buildStatus());
- if (getRelyingPartyConfiguration().encryptAssertion()) {
- response.getEncryptedAssertions().add(
- getEncrypter().encrypt(buildAssertion(message.getSubject(), issueInstant, statement)));
+ if (config.getSignAssertions()) {
+ // TODO sign assertion: Credential credential = config.getSigningCredential();
+ if (config.getEncryptAssertion()) {
+ // TODO load encryption parameters
+ Encrypter encrypter = null;
+ response.getEncryptedAssertions().add(encrypter.encrypt(buildAssertion(responseContext)));
+ } else {
+ response.getAssertions().add(buildAssertion(responseContext));
+ }
} else {
- response.getAssertions().add(buildAssertion(message.getSubject(), issueInstant, statement));
+ if (config.getEncryptAssertion()) {
+ // TODO load encryption parameters
+ Encrypter encrypter = null;
+ response.getEncryptedAssertions().add(encrypter.encrypt(buildAssertion(responseContext)));
+ } else {
+ response.getAssertions().add(buildAssertion(responseContext));
+ }
}
+ response.setStatus(buildStatus(StatusCode.SUCCESS_URI));
return response;
}
/**
* This builds the status response for this SAML request.
*
+ * @param statusCodeUri <code>String</code> to set
* @return <code>Status</code>
*/
- private Status buildStatus() {
- // build status
- SAMLObjectBuilder<Status> statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(
- Status.DEFAULT_ELEMENT_NAME);
+ private Status buildStatus(String statusCodeUri) {
Status status = statusBuilder.buildObject();
-
- // build status code
- SAMLObjectBuilder<StatusCode> statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory()
- .getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
StatusCode statusCode = statusCodeBuilder.buildObject();
- statusCode.setValue(StatusCode.SUCCESS_URI);
+ statusCode.setValue(statusCodeUri);
status.setStatusCode(statusCode);
return status;
}
/**
* This builds the assertion for this SAML request.
*
- * @param messageSubject <code>Subject</code>
- * @param issueInstant <code>DateTime</code>
- * @param statement <code>AttributeStatement</code> of attributes
+ * @param responseContext <code>ProfileResponseContext</code>
* @return <code>Assertion</code>
* @throws EncryptionException if an error occurs attempting to encrypt data
*/
- private Assertion buildAssertion(Subject messageSubject, DateTime issueInstant, AttributeStatement statement)
- throws EncryptionException {
- // build assertions
- SAMLObjectBuilder<Assertion> assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory().getBuilder(
- Assertion.DEFAULT_ELEMENT_NAME);
+ private Assertion buildAssertion(ProfileResponseContext responseContext) throws EncryptionException {
/*
* required: saml:Issuer, ID, Version, IssueInstant
*/
Assertion assertion = assertionBuilder.buildObject();
assertion.setID(getIdGenerator().generateIdentifier());
- assertion.setIssueInstant(issueInstant);
+ assertion.setIssueInstant(responseContext.getIssueInstant());
assertion.setVersion(SAML_VERSION);
assertion.setIssuer(buildIssuer());
// build subject
- assertion.setSubject(buildSubject(messageSubject));
+ assertion.setSubject(buildSubject(responseContext.getMessage().getSubject()));
// build conditions
- assertion.setConditions(buildConditions(issueInstant));
+ assertion.setConditions(buildConditions(responseContext.getIssueInstant()));
// build advice
assertion.setAdvice(buildAdvice());
// add attribute statement
- assertion.getAttributeStatements().add(statement);
+ assertion.getAttributeStatements().add(responseContext.getAttributeStatement());
return assertion;
}
* @return <code>Issuer</code>
*/
private Issuer buildIssuer() {
- // build status
- SAMLObjectBuilder<Issuer> issuerBuilder = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(
- Issuer.DEFAULT_ELEMENT_NAME);
+ RelyingPartyConfiguration relyingPartyConfiguration = getRelyingPartyManager().getRelyingPartyConfiguration(
+ providerId);
Issuer issuer = issuerBuilder.buildObject();
- issuer.setValue(getRelyingPartyConfiguration().getProviderID());
+ issuer.setValue(relyingPartyConfiguration.getProviderID());
return issuer;
}
* @throws EncryptionException if encryption of the name id fails
*/
private Subject buildSubject(Subject messageSubject) throws EncryptionException {
- // build subject
- SAMLObjectBuilder<Subject> subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(
- Subject.DEFAULT_ELEMENT_NAME);
Subject subject = subjectBuilder.buildObject();
- if (getRelyingPartyConfiguration().encryptNameID()) {
- subject.setEncryptedID(getEncrypter().encrypt(messageSubject.getNameID()));
+ if (config.getEncryptNameID()) {
+ // TODO load encryption parameters
+ Encrypter encrypter = null;
+ subject.setEncryptedID(encrypter.encrypt(messageSubject.getNameID()));
} else {
subject.setNameID(messageSubject.getNameID());
// TODO when is subject.setBaseID(newBaseID) called, if ever?
* @return <code>Conditions</code>
*/
private Conditions buildConditions(DateTime issueInstant) {
- SAMLObjectBuilder<Conditions> conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory()
- .getBuilder(Conditions.DEFAULT_ELEMENT_NAME);
Conditions conditions = conditionsBuilder.buildObject();
conditions.setNotBefore(issueInstant);
- // TODO conditions.setNotOnOrAfter();
+ conditions.setNotOnOrAfter(issueInstant.plus(config.getAssertionLifetime()));
+
+ // add audience restrictions
+ AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject();
+ for (String s : config.getAssertionAudiences()) {
+ Audience audience = audienceBuilder.buildObject();
+ audience.setAudienceURI(s);
+ audienceRestriction.getAudiences().add(audience);
+ }
+ conditions.getAudienceRestrictions().add(audienceRestriction);
+
+ // add proxy restrictions
+ ProxyRestriction proxyRestriction = conditions.getProxyRestriction();
+ for (String s : config.getProxyAudiences()) {
+ Audience audience = audienceBuilder.buildObject();
+ audience.setAudienceURI(s);
+ proxyRestriction.getAudiences().add(audience);
+ }
+ proxyRestriction.setProxyCount(new Integer(config.getProxyCount()));
+
// TODO add additional conditions : conditions.getConditions().add(Condition);
- // TODO what about AudienceRestriction, OneTimeUse, ProxyRestriction?
+ // TODO what about OneTimeUse?
return conditions;
}
* @return <code>Advice</code>
*/
private Advice buildAdvice() {
- SAMLObjectBuilder<Advice> adviceBuilder = (SAMLObjectBuilder<Advice>) getBuilderFactory().getBuilder(
- Advice.DEFAULT_ELEMENT_NAME);
Advice advice = adviceBuilder.buildObject();
+ // TODO set advice
// advice.getAssertionIDReferences().add();
// advice.getAssertionURIReferences().add();
// advice.getAssertions().add();
* 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.InputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.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;
-
import org.apache.log4j.Logger;
-import org.joda.time.DateTime;
import org.opensaml.Configuration;
-import org.opensaml.DefaultBootstrap;
+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.AuthnContextComparisonTypeEnumeration;
import org.opensaml.saml2.core.AuthnRequest;
-import org.opensaml.saml2.core.Conditions;
+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.LogoutRequest;
+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.MetadataProvider;
-import org.opensaml.saml2.metadata.provider.ProviderException;
+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.ParserPool;
-import org.opensaml.xml.XMLObjectBuilder;
import org.opensaml.xml.parse.XMLParserException;
-
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import org.xml.sax.InputSource;
+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.RelyingPartyManager;
+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 AbstractProfileHandler {
- 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 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;
-
- /** 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 ParserPool();
- 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(RelyingPartyManager 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(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());
- throw new ServletException("Received a non-HTTP request");
- }
-
- HttpServletRequest httpReq = (HttpServletRequest) request;
- HttpServletResponse httpResp = (HttpServletResponse) response;
- HttpSession httpSession = httpReq.getSession();
-
- AuthnRequest authnRequest;
- try {
- authnRequest = decodeMessage(request); // this will need to change
- // 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;
- }
+ 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 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;
+
+ /** 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 ParserPool();
+ 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(RelyingPartyManager 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;
+ }
}
* 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.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
import edu.internet2.middleware.shibboleth.common.profile.ProfileHandler;
+import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
+import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
/**
- * SAML 2.0 Logout Request profile handler
+ * SAML 2.0 Logout Request profile handler.
*/
public class LogoutRequest implements ProfileHandler {
/** {@inheritDoc} */
- public boolean processRequest(ServletRequest request, ServletResponse response) throws ServletException {
+ public boolean processRequest(ProfileRequest request, ProfileResponse response) throws ServletException {
// TODO Auto-generated method stub
return false;
}
--- /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 org.joda.time.DateTime;
+import org.opensaml.saml2.core.SubjectQuery;
+import org.opensaml.saml2.core.AttributeStatement;
+
+import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
+
+/**
+ * Contains contextual information used in processing profile responses.
+ */
+public class ProfileResponseContext {
+
+ /** Profile request. */
+ private ProfileRequest request;
+
+ /** Profile request message. */
+ private SubjectQuery message;
+
+ /** Issue instant for the response. */
+ private DateTime issueInstant;
+
+ /** Response statement. */
+ private AttributeStatement attributeStatement;
+
+ /**
+ * Constructor.
+ *
+ * @param r serlvet request
+ * @param m decoded profile request message
+ */
+
+ public ProfileResponseContext(ProfileRequest r, SubjectQuery m) {
+ request = r;
+ message = m;
+ issueInstant = new DateTime();
+ }
+
+ /**
+ * Gets the initiating profile request.
+ *
+ * @return profile request
+ */
+ public ProfileRequest getRequest() {
+ return request;
+ }
+
+ /**
+ * Gets the decoded profile request message.
+ *
+ * @return profile request message
+ */
+ public SubjectQuery getMessage() {
+ return message;
+ }
+
+ /**
+ * Gets the issue instant for the response.
+ *
+ * @return issue instant
+ */
+ public DateTime getIssueInstant() {
+ return issueInstant;
+ }
+
+ /**
+ * Sets a attribute statement associated with this response.
+ *
+ * @param s to sets
+ */
+ public void setAttributeStatement(AttributeStatement s) {
+ attributeStatement = s;
+ }
+
+ /**
+ * Gets the statement associated with this response.
+ *
+ * @return response statement
+ */
+ public AttributeStatement getAttributeStatement() {
+ return attributeStatement;
+ }
+}