SPACSUtils.java revision 7865e731ddb5646082d96b96b1a11d82e9db794f
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at opensso/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* $Id: SPACSUtils.java,v 1.48 2009/11/20 21:41:16 exu Exp $
*
* Portions Copyrighted 2010-2016 ForgeRock AS.
* Portions Copyrighted 2016 Nomura Research Institute, Ltd.
*/
/**
* This class is used by a service provider (SP) to process the response from
* an identity provider for the SP's Assertion Consumer Service.
*
* @supported.api
*/
public class SPACSUtils {
private SPACSUtils() {}
/**
* Retrieves <code>SAML</code> <code>Response</code> from http request.
* It handles three cases:
* <pre>
* 1. using http method get using request parameter "resID".
* This is the case after local login is done.
* 2. using http method get using request parameter "SAMLart".
* This is the case for artifact profile.
* 3. using http method post. This is the case for post profile.
* </pre>
*
* @param request http servlet request
* @param response http servlet response
* @param orgName realm or organization name the service provider resides in
* @param hostEntityId Entity ID of the hosted service provider
* @param metaManager <code>SAML2MetaManager</code> instance.
* @return <code>ResponseInfo</code> instance.
* @throws SAML2Exception,IOException if it fails in the process.
*/
public static ResponseInfo getResponse(
throws SAML2Exception,IOException
{
{
"unsupportedBinding",
throw new SAML2Exception(
}
{
"unsupportedBinding",
throw new SAML2Exception(
}
} else {
{
"unsupportedBinding",
throw new SAML2Exception(
}
}
} else {
// not supported
"notSupportedHTTPMethod",
throw new SAML2Exception(
}
}
return respInfo;
}
/**
* Retrieves <code>SAML Response</code> from http Get.
* It first uses parameter resID to retrieve <code>Response</code>. This is
* the case after local login;
* If resID is not defined, it then uses <code>SAMLart</code> http
* parameter to retrieve <code>Response</code>.
*/
private static ResponseInfo getResponseFromGet(
throws SAML2Exception,IOException
{
+ resID);
}
synchronized (SPCache.responseHash) {
}
+ "couldn't find Response from resID.");
}
data,
null);
throw new SAML2Exception(
}
return respInfo;
}
+ "string is empty.");
null,
null);
"missingArtifact",
throw new SAML2Exception(
}
}
// Retrieves response using artifact profile.
{
// Try to get source ID and endpointIndex, and then
// decide which IDP and which artifact resolution service
"samlArt = " + samlArt);
}
try {
data,
null);
} catch (SAML2Exception se) {
+ "Unable to decode and parse artifact string:" + samlArt);
"errorObtainArtifact",
throw se;
}
try {
} catch (SAML2MetaException se) {
data,
null);
throw se;
}
// create ArtifactResolve message
try {
if (needArtiResolveSigned != null &&
// or save it somewhere?
sm,
throw new SAML2Exception(
}
throw new SAML2Exception(
}
}
+ "ArtifactResolve=" + resolveString);
}
} catch (SAML2Exception s2e) {
+ "couldn't create ArtifactResolve:", s2e);
data,
null);
"errorCreateArtifactResolve",
throw s2e;
} catch (SOAPException se) {
+ "couldn't get ArtifactResponse. SOAP error:",se);
data,
null);
"errorInSOAPCommunication",
}
}
data,
null);
return result;
}
// Finds the IDP who sends the artifact;
private static String getIDPEntityID(
throws SAML2Exception,IOException
{
// find the idp
try {
iterator();
break;
}
idpEntityID = null;
}
if (idpEntityID == null) {
+ "to find the IDP based on the SourceID in the artifact");
data,
null);
throw new SAML2Exception(
}
} catch (SAML2Exception se) {
data,
null);
throw se;
}
return idpEntityID;
}
// Retrieves the ArtifactResolutionServiceURL for an IDP.
private static String getIDPArtifactResolutionServiceUrl(
int endpointIndex,
throws SAML2Exception,IOException
{
// find the artifact resolution service url
int index;
boolean isDefault = false;
//String binding = ars.getBinding();
if (index == endpointIndex) {
break;
}
if (isDefault) {
}
if (i==0) {
}
}
+ "location of artifact resolution service for "
+ idpEntityID);
data,
null);
"cannotFindArtifactResolutionUrl",
"cannotFindArtifactResolutionUrl"));
throw new SAML2Exception(
"cannotFindArtifactResolutionUrl"));
}
}
}
+ "service url =" + location);
}
return location;
}
/**
* Obtains <code>SAML Response</code> from <code>SOAPBody</code>.
* Used by Artifact profile.
*/
throws SAML2Exception,IOException
{
try {
} catch (SAML2Exception se) {
data,
null);
throw se;
}
try {
} catch (SAML2Exception se) {
+ "ArtifactResponse:", se);
}
data,
null);
throw se;
}
data,
null);
"missingArtifactResponse",
throw new SAML2Exception(
} else {
+ artiResp.toXMLString(true, true));
}
}
// verify ArtifactResponse
sm,
+ "ArtifactResponse's signature is invalid.");
}
data,
null);
throw new SAML2Exception(
}
}
+ "ArtifactResponse's InResponseTo is invalid.");
}
data,
null);
throw new SAML2Exception(
}
+ "ArtifactResponse's Issuer is invalid.");
}
data,
null);
throw new SAML2Exception(
}
// check time?
{
+ "ArtifactResponse's status code is not success."
+ statusCode);
}
}
data,
null);
throw new SAML2Exception(
}
try {
} catch (SAML2Exception se) {
+ "couldn't instantiate Response:", se);
}
data,
null);
throw se;
}
}
/**
* Obtains <code>SAML Response</code> from <code>SOAPBody</code>.
* Used by ECP profile.
*/
private static ResponseInfo getResponseFromPostECP(
throws SAML2Exception,IOException
{
try {
} catch (SOAPException soapex) {
} catch (SOAPBindingException soapex) {
} catch(SOAPFaultException sfex) {
"failedToCreateSOAPMessage", faultString);
throw new SAML2Exception(faultString);
}
try {
break;
} catch (SAML2Exception saml2ex) {
// not ECP RelayState
}
}
}
if (ecpRelayState != null) {
}
"missingSAMLResponse",
throw new SAML2Exception(
}
try {
} catch (SAML2Exception se) {
"Couldn't create Response:", se);
}
throw se;
}
try {
} catch (SAML2MetaException se) {
throw se;
}
Set<X509Certificate> certificates = KeyUtil.getVerificationCerts(idpDesc, idpEntityID, SAML2Constants.IDP_ROLE);
"SPACSUtils.getResponseFromPostECP: " +
" Assertion is not signed.");
}
"assertionNotSigned",
throw new SAML2Exception(
"SPACSUtils.getResponseFromPostECP: " +
" Assertion signature is invalid.");
}
"invalidSignature",
throw new SAML2Exception(
}
}
}
}
// Obtains SAML Response from POST.
{
}
if (samlResponse == null) {
null,
null);
"missingSAMLResponse",
throw new SAML2Exception(
}
// Get Response back
// decode the Response
try {
}
}
} catch (SAML2Exception se) {
+ "when instantiating SAMLResponse:", se);
null,
null);
"errorObtainResponse",
throw new SAML2Exception(
} catch (Exception e) {
+ "when decoding SAMLResponse:", e);
null,
null);
throw new SAML2Exception(
} finally {
try {
+ "Exception when close the input stream:", ie);
}
}
}
}
}
data,
null);
}
"resp is null");
}
return null;
}
/**
* Authenticates user with <code>Response</code>.
* Auth session upgrade will be called if input session is
* not null.
* Otherwise, saml2 auth module is called. The name of the auth module
* is retrieved from <code>SPSSOConfig</code>. If not found, "SAML2" will
* be used.
*
* @param request HTTP Servlet request
* @param response HTTP Servlet response.
* @param out the print writer for writing out presentation
* @param metaAlias metaAlias for the service provider
* @param session input session object. It could be null.
* @param respInfo <code>ResponseInfo</code> to be verified.
* @param realm realm or organization name of the service provider.
* @param hostEntityId hosted service provider Entity ID.
* @param metaManager <code>SAML2MetaManager</code> instance for meta operation.
* @param auditor a <code>SAML2EventLogger</code> auditor object to hook into
* tracking information for the saml request
* @return <code>Object</code> which holds result of the session.
* @throws SAML2Exception if the processing failed.
*/
public static Object processResponse(
) throws SAML2Exception {
respInfo.getResponse());
}
try {
} catch (SAML2Exception se) {
// invoke SPAdapter for failure
throw se;
}
}
// get mappers
boolean needAssertionEncrypted =
"process: NameID was not encrypted.");
"nameIDNotEncrypted"));
// invoke SPAdapter for failure
throw se;
}
try {
} catch (SAML2Exception se) {
// invoke SPAdapter for failure
throw se;
}
}
try {
} catch (SAML2MetaException ex) {
}
"metaDataError"));
throw se;
}
if (nameIDFormat != null) {
"unsupportedNameIDFormatSP", args);
throw se;
}
}
try {
} catch (SessionException se) {
// invoke SPAdapter for failure
throw se2;
}
try {
} catch (SessionException se) {
// invoke SPAdapter for failure
throw se2;
}
}
boolean isNewAccountLink = false;
try {
if (shouldPersistNameID) {
SAML2Utils.debug.message(classMethod + "querying data store for existing federation links: realm = "
}
try {
} catch (DataStoreProviderException dse) {
"information", dse);
}
}
isNewAccountLink = true;
}
} catch (SAML2Exception se) {
// invoke SPAdapter for failure
throw se;
}
// In case we just got authenticated locally, we should accept the freshly authenticated session's principal
// as the username corresponding to the received assertion.
}
}
}
}
}
}
try {
} catch (SAML2Exception se) {
// invoke SPAdapter for failure
se);
throw se;
}
}
}
// return error code for local user login
// If we couldn't determine the username based on the incoming assertion, then we shouldn't automatically
// map the user to the existing session.
try {
} catch (SessionException se) {
}
}
}
}
}
}
// set client info. always use client IP address to prevent
// reverse host lookup
try {
} catch (SessionException se) {
// invoke SPAdapter for failure
int failureCode =
}
}
throw se2;
}
// set metaAlias
try {
session);
} catch (SessionException se) {
// invoke SPAdapter for failure
throw se2;
}
}
"spNotAffiliationMember"));
}
if (isDualRole) {
SAML2Constants.DUAL_ROLE, true);
} else {
SAML2Constants.SP_ROLE, true);
}
} else {
if (isDualRole) {
SAML2Constants.DUAL_ROLE, false);
} else {
SAML2Constants.SP_ROLE, false);
}
}
try {
} catch (SessionException se) {
// invoke SPAdapter for failure
throw se2;
}
props);
// write fed info into data store
if (writeFedInfo) {
try {
} catch (SAML2Exception se) {
// invoke SPAdapter for failure
throw se;
}
}
data,
props);
}
// save info in memory for logout
// invoke SP Adapter
if (redirected) {
} else {
}
try {
} catch (SessionException ex) {
} catch (UnsupportedOperationException ex) {
}
}
try {
if (SAML2FailoverUtils.isSAML2FailoverEnabled()) {
}
} catch (SAML2TokenRepositoryException se) {
"There was a problem saving the assertionID to the SAML2 Token Repository for assertionID:"
+ assertionID, se);
}
}
return session;
}
private static boolean getNeedNameIDEncrypted(boolean needAssertionEncrypted, SPSSOConfigElement spssoconfig) {
if (!needAssertionEncrypted) {
}
return false;
}
public static boolean getNeedAttributeEncrypted(boolean needAssertionEncrypted, SPSSOConfigElement spssoconfig) {
if (!needAssertionEncrypted) {
}
return false;
}
SAML2Exception se) {
try {
} catch (SAML2Exception e) {
"SPACSUtils.invokeSPAdapterForSSOFailure", e);
}
}
}
}
}
throws SAML2Exception {
try {
} else {
}
}
if (isTransient) {
if (nameIDInfoStrs == null) {
} else {
}
}
}
} catch (SessionException sessE) {
throw new SAML2Exception(sessE);
}
if (fedSessions == null) {
synchronized (SPCache.fedSessionListsByNameIDInfoKey) {
fedSessions = (List)
if (fedSessions == null) {
fedSessions = new ArrayList();
}
}
synchronized (fedSessions) {
}
}
if (isIDPProxy) {
//IDP Proxy
tokenID);
}
}
info.getRemoteEntityID(), true));
// end of IDP Proxy
}
} else {
synchronized (fedSessions) {
boolean found = false;
}
if ((idpSessionIndex != null) &&
found = true;
break;
}
}
if (!found) {
metaAlias));
{
size());
}
}
}
}
}
}
try {
} catch (SessionException e) {
"SPACSUtils.saveInfoInMemory: "+
"Unable to add session listener.");
}
}
/** Sets the attribute map in the session
*
* @param sessionProvider Session provider
* @param attrMap the Attribute Map
* @param session the valid session object
* @throws com.sun.identity.plugin.session.SessionException
*/
public static void setAttrMapInSession(
throws SessionException {
"SPACSUtils.setAttrMapInSessioin: AttrMap:" +
}
}
}
}
}
/** Sets Discovery bootstrap credentials in the SSOToken
*
* @param sessionProvider session provider.
* @param assertion assertion.
* @param session the valid session object.
*/
private static void setDiscoBootstrapCredsInSSOToken(
throws SessionException {
return;
}
if (discoBootstrapCreds == null) {
discoBootstrapCreds = new HashSet();
}
}
}
if (discoBootstrapCreds != null) {
}
}
/**
* Obtains relay state. Retrieves the relay state from relay state cache.
* If input relay state is null, retrieve it from <code>SPSSOConfig</code>.
*
* @param relayStateID relay state value received from http request.
* @param orgName realm or organization name the service provider resides in
* @param hostEntityId Entity ID of the hosted service provider
* @param sm <code>SAML2MetaManager</code> instance.
* @return final relay state. Or <code>null</code> if the input
* relayStateID is null and no default relay state is configured.
*/
public static String getRelayState(
) {
} else if (SAML2FailoverUtils.isSAML2FailoverEnabled()) {
// The key is this way to make it unique compared to when
// the same key is used to store a copy of the AuthnRequestInfo
try {
// Try and retrieve the value from the SAML2 repository
if (relayState != null) {
// Get back the relayState
+ " retrieved from SAML2 repository for key: " + key);
}
}
} catch (SAML2TokenRepositoryException se) {
}
} else {
}
}
}
}
}
return relayStateUrl;
}
/**
* Retrieves intermediate redirect url from SP sso config. This url is used
* if you want to goto some place before the final relay state.
*
* @param orgName realm or organization name the service provider resides in
* @param hostEntityId Entity ID of the hosted service provider
* @param sm <code>SAML2MetaManager</code> instance.
* @return intermediate redirect url; or <code>null</code> if the url is
* is not configured or an error occured during the retrieval
* process.
*/
{
}
/**
* Saves response for later retrieval and retrieves local auth url from
* <code>SPSSOConfig</code>.
* If the url does not exist, generate one from request URI.
* If still cannot get it, (shouldn't happen), get it from
* <code>AMConfig.properties</code>.
*
* @param orgName realm or organization name the service provider resides in
* @param hostEntityId Entity ID of the hosted service provider
* @param sm <code>SAML2MetaManager</code> instance to perform meta
* operation.
* @param respInfo to be cached <code>ResponseInfo</code>.
* @param requestURI http request URI.
* @return local login url.
*/
public static String prepareForLocalLogin(
{
// get it from request
try {
if (index != -1) {
+ orgName;
}
} catch (IndexOutOfBoundsException e) {
}
// shouldn't be here, but in case
+ "://"
+ orgName;
}
}
respInfo.setIsLocalLogin(true);
synchronized (SPCache.responseHash) {
respInfo);
}
"localLoginUrl = " + localLoginUrl);
}
return localLoginUrl;
}
/**
* Retrieves attribute value for a given attribute name from
* <code>SPSSOConfig</code>.
* @param orgName realm or organization name the service provider resides in
* @param hostEntityId hosted service provider's Entity ID.
* @param sm <code>SAML2MetaManager</code> instance to perform meta
* operations.
* @param attrName name of the attribute whose value ot be retrived.
* @return value of the attribute; or <code>null</code> if the attribute
* if not configured, or an error occured in the process.
*/
{
try {
return null;
}
}
} catch (SAML2MetaException sme) {
+ "Config:", sme);
}
}
return result;
}
/**
* Gets the attributes from an assert's AttributeStates.
*
* @param assertion The assertion from which to pull the AttributeStates.
* @param needAttributeEncrypted Whether attributes must be encrypted (or else rejected).
* @param privateKeys Private keys used to decrypt those encrypted attributes.
* @return a list of attributes pulled from the provided assertion.
*/
public static List<Attribute> getSAMLAttributes(Assertion assertion, boolean needAttributeEncrypted,
return null;
}
if (attributes != null) {
}
}
}
try {
} catch (SAML2Exception se) {
return null;
}
}
}
}
}
}
return attrList;
}
/**
* Processes response from Identity Provider to Fedlet (SP).
* This will do all required protocol processing, include signature,
* issuer and audience validation etc. A map containing processing
* result will be returned. <br>
* Here is a list of keys and values for the returned map: <br>
* SAML2Constants.ATTRIBUTE_MAP -- Attribute map containing all attributes
* passed down from IDP inside the
* Assertion. The value is a
* <code>java.util.Map</code> whose keys
* are attribute names and values are
* <code>java.util.Set</code> of string
* values for the attributes. <br>
* SAML2Constants.RELAY_STATE -- Relay state, value is a string <br>
* SAML2Constants.IDPENTITYID -- IDP entity ID, value is a string<br>
* SAML2Constants.RESPONSE -- Response object, value is an instance of
* com.sun.identity.saml2.protocol.Response
* SAML2Constants.ASSERTION -- Assertion object, value is an instance of
* com.sun.identity.saml2.assertion.Assertion
* SAML2Constants.SUBJECT -- Subject object, value is an instance of
* com.sun.identity.saml2.assertion.Subject
* SAML2Constants.NAMEID -- NameID object, value is an instance of
* com.sun.identity.saml2.assertion.NameID
*
* @param request HTTP Servlet request
* @param response HTTP Servlet response.
* @param out the print writer for writing out presentation
*
* @return <code>Map</code> which holds result of the processing.
* @throws SAML2Exception if the processing failed due to server error.
* @throws IOException if the processing failed due to IO error.
* @throws SessionException if the processing failed due to session error.
* @throws ServletException if the processing failed due to request error.
*
* @supported.api
*/
throw new ServletException(message);
}
throw new ServletException(message);
}
if (metaManager == null) {
throw new SAML2Exception(
}
// Check in case metaAlias has been supplied as a parameter
// pick the first available one
// get first one
}
throw new ServletException(
}
}
}
try {
} catch (SAML2MetaException sme) {
sme);
throw new SAML2Exception(
}
if (hostEntityId == null) {
// logging?
throw new SAML2Exception(
}
// organization is always root org
try {
} catch (SessionException se) {
se);
throw new SAML2Exception(se);
}
// Throws a SAML2Exception if the response cannot be validated
// or contains a non-Success StatusCode, invoking the SPAdapter SPI
// for taking action on the failed validation.
// The resulting exception has its redirectionDone flag set if
// the SPAdapter issued a HTTP redirect.
// response redirected already in SPAdapter
}
// redirect to relay state
try {
} catch (SessionException se) {
se);
}
}
redirectUrl += "&goto=";
} else {
redirectUrl += "?goto=";
}
try {
} catch (SessionException se) {
"SPACSUtils.processRespForFedlet: rewriting failed.", se);
}
} else {
}
} else {
}
}
private static Map createMapForFedlet(
}
return map;
}
/**
* Returns the username if there was one from the Assertion we were able to map into a local user account. Returns
* null if not. Should only be used from the SP side. Should only be called in conjuncture with the Auth Module.
* In addition, it performs what attribute federation it can.
*
* This method is a picked apart version of the "processResponse" function.
*/
public static String getPrincipalWithoutLogin(Subject assertionSubject, Assertion authnAssertion, String realm,
throws SAML2Exception {
boolean needNameIDEncrypted = false;
}
}
}
try {
} catch (SAML2MetaException ex) {
}
}
if (nameIDFormat != null) {
}
}
boolean isNewAccountLink = false;
try {
if (shouldPersistNameID) {
try {
} catch (DataStoreProviderException dse) {
}
}
//if we can't get an already linked account, see if we'll be generating a new one based on federated data
isNewAccountLink = true; //we'll use this later to inform us
}
} catch (SAML2Exception se) {
return null;
}
//if we're new and we're persistent, store the federation data in the user pref
if (isNewAccountLink && shouldPersistNameID) {
try {
} catch (SAML2Exception se) {
return userName;
}
}
return userName;
}
private static void writeFedData(NameID nameId, String spEntityId, String realm, SAML2MetaManager metaManager,
final NameIDInfo info;
}
throw new SAML2Exception("Unable to locate SP Entity ID in the affiliate descriptor.");
}
if (isDualRole) {
} else {
}
} else {
if (isDualRole) {
} else {
}
}
// write fed info into data store
}
/**
* Gets the attributes for this assertion in a new List.
* @param authnAssertion Assertion from which to reead the attributes.
* @param needAttributeEncrypted Whether the attributes must be encrypted.
* @param decryptionKeys The keys used to decrypt the attributes, if they're encrypted.
* @return a List of the attributes in this assertion.
*/
final List<Attribute> origAttrs = getSAMLAttributes(authnAssertion, needAttributeEncrypted, decryptionKeys);
}
return attrs;
}
}