/** * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2010-2014 ForgeRock AS. 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 * http://forgerock.org/license/CDDLv1.0.html * 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 http://forgerock.org/license/CDDLv1.0.html * 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]" * */ package com.sun.identity.saml2.plugins; import com.sun.identity.cot.CircleOfTrustManager; import com.sun.identity.cot.CircleOfTrustDescriptor; import com.sun.identity.cot.COTException; import com.sun.identity.saml2.common.SAML2Exception; import com.sun.identity.saml2.common.SAML2Utils; import com.sun.identity.saml2.common.SAML2Constants; import com.sun.identity.saml2.jaxb.assertion.AttributeElement; import com.sun.identity.saml2.jaxb.assertion.AttributeValueElement; import com.sun.identity.saml2.jaxb.entityconfig.IDPSSOConfigElement; import com.sun.identity.saml2.jaxb.entityconfig.SPSSOConfigElement; import com.sun.identity.saml2.jaxb.metadata.EntityDescriptorElement; import com.sun.identity.saml2.jaxb.metadata.ExtensionsType; import com.sun.identity.saml2.jaxb.metadataattr.EntityAttributesElement; import com.sun.identity.saml2.jaxb.metadata.SPSSODescriptorElement; import com.sun.identity.saml2.meta.SAML2MetaManager; import com.sun.identity.saml2.meta.SAML2MetaUtils; import com.sun.identity.saml2.meta.SAML2MetaException; import com.sun.identity.saml2.profile.IDPSSOUtil; import com.sun.identity.saml2.profile.SPSSOFederate; import com.sun.identity.saml2.profile.SPCache; import com.sun.identity.saml2.protocol.AuthnRequest; import com.sun.identity.saml2.protocol.RequestedAuthnContext; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.StringTokenizer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.lang.StringUtils; /** * This class SAML2IDPProxyFRImpl is used to find a preferred Identity * Authenticating provider to proxy the authentication request. It might use an external * JSP page to interact with the user agent */ public class SAML2IDPProxyFRImpl implements SAML2IDPFinder { public static String IDP_FINDER_ENABLED_IN_SP = "idpFinderEnabled"; public static String SESSION_ATTR_NAME_IDP_LIST = "_IDPLIST_"; public static String SESSION_ATTR_NAME_RELAYSTATE = "_RELAYSTATE_"; public static String SESSION_ATTR_NAME_SPREQUESTER = "_SPREQUESTER_"; public static String SESSION_ATTR_NAME_REQAUTHNCONTEXT = "_REQAUTHNCONTEXT_"; SPSSODescriptorElement spSSODescriptor = null; String relayState = ""; String binding = ""; /* * Constructor. */ public SAML2IDPProxyFRImpl() { } public static String className = "SAML2IDPProxyFRImpl."; /** * Returns a list of preferred IDP providerIDs. * @param authnRequest original authnrequest * @param hostProviderID ProxyIDP providerID. * @param realm Realm * @param request HttpServletRequest * @param response HttpServletResponse * @return a list of providerID's of the authenticating providers to be * proxied or null to disable the proxying and continue * for the localauthenticating provider. * @exception SAML2Exception if error occurs. */ public List getPreferredIDP( AuthnRequest authnRequest, String hostProviderID, String realm, HttpServletRequest request, HttpServletResponse response) throws SAML2Exception { // Entering the class and method String methodName = "getPreferredIDP"; String classMethod = className + methodName + ":"; debugMessage(methodName, "Entering."); Boolean isIdpFinderForAllSPsEnabled = isIDPFinderForAllSPs(realm, hostProviderID); // Start the logic to obtain the list of preferred IdPs try { // Inititate the metadata manager SAML2MetaManager sm = new SAML2MetaManager(); if (sm == null) { throw new SAML2Exception( SAML2Utils.bundle.getString("errorMetaManager")); } // Obtain the SP configuration try { spSSODescriptor = IDPSSOUtil.metaManager.getSPSSODescriptor(realm, authnRequest.getIssuer().getValue().toString()); } catch (SAML2MetaException sme) { SAML2Utils.debug.error(classMethod, sme); spSSODescriptor = null; } // Get the relay state from the request, if exists relayState = request.getParameter(SAML2Constants.RELAY_STATE); binding = SAML2Constants.HTTP_REDIRECT; if (request.getMethod().equals("POST")) { binding = SAML2Constants.HTTP_POST; } // Read the local metadata of the SP that made the request SPSSOConfigElement spEntityCfg = sm.getSPSSOConfig(realm, authnRequest.getIssuer().getValue()); Map spConfigAttrsMap = null; if (spEntityCfg != null) { spConfigAttrsMap = SAML2MetaUtils.getAttributes(spEntityCfg); } // Check if the local configuration of the remote SP wants to use // the Introduction Cookie Boolean isIntroductionForProxyingEnabled = false; String useIntroductionForProxying = SPSSOFederate.getParameter(spConfigAttrsMap, SAML2Constants.USE_INTRODUCTION_FOR_IDP_PROXY); if (useIntroductionForProxying != null) isIntroductionForProxyingEnabled = useIntroductionForProxying.equalsIgnoreCase("true"); // Check if the local configuration of the remote SP wants to use // the IDP Finder Boolean isIdPFinderEnabled = false; String idpFinderEnabled = SPSSOFederate.getParameter(spConfigAttrsMap, IDP_FINDER_ENABLED_IN_SP); if (idpFinderEnabled != null) isIdPFinderEnabled = idpFinderEnabled.equalsIgnoreCase("true"); String idpFinderJSP = getIDPFinderJSP(realm, hostProviderID); // providerIDs will contain the list of IdPs to return from this method List providerIDs = new ArrayList(); // If the SP doesn't want to use the Introduction cookie and does not // want to use the IdP Finder. i.e. just use the manual list in the // extended metadata if (!isIntroductionForProxyingEnabled && !isIdPFinderEnabled && !isIdpFinderForAllSPsEnabled) { debugMessage(methodName, " idpFinder wil use the static list of the SP"); List proxyIDPs = null; if (spConfigAttrsMap != null && !spConfigAttrsMap.isEmpty()) { proxyIDPs = (List) spConfigAttrsMap.get(SAML2Constants.IDP_PROXY_LIST); } debugMessage(methodName, " List from the configuration: " + proxyIDPs); if (proxyIDPs == null || proxyIDPs.isEmpty()) { SAML2Utils.debug.error("SAML2IDPProxyImpl.getPrefferedIDP:" + "Preferred IDPs are null."); return null; } // If there are several IdPs listed in the SP configuration, // give the user the chance to select one interactively if (proxyIDPs.size() > 1) { String idpListSt = selectIDPBasedOnLOA(proxyIDPs, realm, authnRequest); // Construct the IDPFinder URL to redirect to String idpFinder = getRedirect(request, idpFinderJSP); // Generate the requestID String requestID = SAML2Utils.generateID(); // Store the important stuff and the session parameters so the // idpFinderImplemenatation can read them and process them storeSessionParamsAndCache(request, idpListSt, authnRequest, hostProviderID, realm, requestID); debugMessage(methodName, ": Redirect url = " + idpFinder); response.sendRedirect(idpFinder); // return something different than null providerIDs.add(requestID); debugMessage(methodName, " Redirected successfully"); return providerIDs; } providerIDs.add(proxyIDPs.iterator().next()); return providerIDs; } // If the SP wants to use the IdPFinder or it is globally enabled // and it does not want to use the introduction cookie if (!isIntroductionForProxyingEnabled && (isIdPFinderEnabled || isIdpFinderForAllSPsEnabled)) { debugMessage(methodName, "SP wants to use IdP Finder"); String idpListSt = idpList(authnRequest, realm); if (!idpListSt.trim().isEmpty()) { // Construct the IDPFinder URL to redirect to String idpFinder = getRedirect(request, idpFinderJSP); // Generate the requestID String requestID = SAML2Utils.generateID(); // Store the important stuff and the session parameters so the // idpFinderImplemenatation can read them and process them storeSessionParamsAndCache(request, idpListSt, authnRequest, hostProviderID, realm, requestID); debugMessage(methodName, ": Redirect url = " + idpFinder); response.sendRedirect(idpFinder); // return something different than null providerIDs.add(requestID); debugMessage(methodName, " Redirected successfully"); return providerIDs; } else { return null; } } else { // IDP Proxy with introduction cookie List cotList = (List) spConfigAttrsMap.get("cotlist"); String cotListStr = (String) cotList.iterator().next(); CircleOfTrustManager cotManager = new CircleOfTrustManager(); CircleOfTrustDescriptor cotDesc = cotManager.getCircleOfTrust(realm, cotListStr); String readerURL = cotDesc.getSAML2ReaderServiceURL(); if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message(classMethod + "SAMLv2 idp" + "discovery reader URL = " + readerURL); } if (readerURL != null && (!readerURL.equals(""))) { String rID = SAML2Utils.generateID(); String redirectURL = SAML2Utils.getRedirectURL(readerURL, rID, request); if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.error(classMethod + "Redirect url = " + redirectURL); } if (redirectURL != null) { response.sendRedirect(redirectURL); Map aMap = new HashMap(); SPCache.reqParamHash.put(rID, aMap); providerIDs.add(rID); return providerIDs; } } } return null; } catch (SAML2MetaException ex) { SAML2Utils.debug.error(classMethod + "meta Exception in retrieving the preferred IDP", ex); return null; } catch (COTException sme) { SAML2Utils.debug.error(classMethod + "Error retreiving COT ", sme); return null; } catch (Exception e) { SAML2Utils.debug.error(classMethod + "Exception in retrieving the preferred IDP", e); return null; } } private void debugMessage(String methodName, String message) { String classMethod = "SAML2IDPPRoxyFRImpl." + methodName + ":"; if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message(classMethod + message); } } private String idpList( AuthnRequest authnRequest, String realm) { String classMethod = "idpList"; try { List idpList = SAML2Utils.getSAML2MetaManager().getAllRemoteIdentityProviderEntities(realm); return selectIDPBasedOnLOA(idpList, realm, authnRequest); } catch (SAML2MetaException me) { debugMessage(classMethod, "SOmething went wrong: " + me); return null; } } private String selectIDPBasedOnLOA(List idpList, String realm, AuthnRequest authnRequest) { String classMethod = "selectIdPBasedOnLOA"; EntityDescriptorElement idpDesc = null; Set authnRequestContextSet = null; String idps = ""; try { RequestedAuthnContext requestedAuthnContext = authnRequest.getRequestedAuthnContext(); if (requestedAuthnContext == null) { //Handle the special case when the original request did not contain any Requested AuthnContext: //In this case we just simply return all the IdPs as each one should support a default AuthnContext. return StringUtils.join(idpList, " "); } List listOfAuthnContexts = requestedAuthnContext.getAuthnContextClassRef(); debugMessage(classMethod, "listofAuthnContexts: " + listOfAuthnContexts); try { authnRequestContextSet = new HashSet(listOfAuthnContexts); } catch (Exception ex1) { authnRequestContextSet = new HashSet(); } if ((idpList != null) && (!idpList.isEmpty())) { Iterator idpI = idpList.iterator(); while (idpI.hasNext()) { String idp = (String) idpI.next(); debugMessage(classMethod, "IDP is: " + idp); idpDesc = SAML2Utils.getSAML2MetaManager().getEntityDescriptor(realm, idp); if (idpDesc != null) { ExtensionsType et = idpDesc.getExtensions(); if (et != null) { debugMessage(classMethod, "Extensions found for idp: " + idp); List idpExtensions = et.getAny(); if (idpExtensions != null || !idpExtensions.isEmpty()) { debugMessage(classMethod, "Extensions content found for idp: " + idp); Iterator idpExtensionsI = idpExtensions.iterator(); while (idpExtensionsI.hasNext()) { EntityAttributesElement eael = (EntityAttributesElement) idpExtensionsI.next(); if (eael != null) { debugMessage(classMethod, "Entity Attributes found for idp: " + idp); List attribL = eael.getAttributeOrAssertion(); if (attribL != null || !attribL.isEmpty()) { Iterator attrI = attribL.iterator(); while (attrI.hasNext()) { AttributeElement ae = (AttributeElement) attrI.next(); // TODO: Verify what type of element this is (Attribute or assertion) // For validation purposes List av = ae.getAttributeValue(); if (av != null || !av.isEmpty()) { debugMessage(classMethod, "Attribute Values found for idp: " + idp); Iterator avI = av.iterator(); while (avI.hasNext()) { AttributeValueElement ave = (AttributeValueElement) avI.next(); if (ave != null) { List contentL = ave.getContent(); debugMessage(classMethod, "Attribute Value Elements found for idp: " + idp + "-->" + contentL); if (contentL != null || !contentL.isEmpty()) { Set idpContextSet = trimmedListToSet(contentL); debugMessage(classMethod, "idpContextSet = " + idpContextSet); idpContextSet.retainAll(authnRequestContextSet); if (idpContextSet != null && !idpContextSet.isEmpty()) { idps = idp + " " + idps; debugMessage(classMethod, "Extension Values found for idp " + idp + ": " + idpContextSet); } } } } } } } } } } } else { debugMessage(classMethod, " No extensions found for IdP " + idp); } } else { debugMessage(classMethod, "Configuration for the idp " + idp + " was not found in this system"); } } } } catch (SAML2MetaException me) { debugMessage(classMethod, "SOmething went wrong: " + me); } debugMessage(classMethod, " IDPList returns: " + idps); return idps.trim(); } private String selectIDPBasedOnAuthContext(List idpList, String realm, AuthnRequest authnRequest) { String classMethod = "selectIdPBasedOnLOA"; EntityDescriptorElement idpDesc = null; Set authnRequestContextSet = null; String idps = ""; try { List listOfAuthnContexts = authnRequest.getRequestedAuthnContext().getAuthnContextClassRef(); debugMessage(classMethod, "listofAuthnContexts: " + listOfAuthnContexts); try { authnRequestContextSet = new HashSet(listOfAuthnContexts); } catch (Exception ex1) { authnRequestContextSet = new HashSet(); } if ((idpList != null) && (!idpList.isEmpty())) { Iterator idpI = idpList.iterator(); while (idpI.hasNext()) { String idp = (String) idpI.next(); debugMessage(classMethod, "IDP is: " + idp); List supportedAuthnContextsbyIDP = getSupportedAuthnContextsByIDP(realm, idp); if (supportedAuthnContextsbyIDP != null) { debugMessage(classMethod, "Standard Authn Contexts found for idp: " + idp); Set idpContextSet = trimmedListToSet(supportedAuthnContextsbyIDP); debugMessage(classMethod, "idpContextSet = " + idpContextSet); idpContextSet.retainAll(authnRequestContextSet); if (idpContextSet != null && !idpContextSet.isEmpty()) { idps = idp + " " + idps; debugMessage(classMethod, "Standard Authn Contexts found for idp " + idp + ": " + idpContextSet); } } else { debugMessage(classMethod, "The IdP" + idp + " has no standard authentication" + " contexts configured"); } } } } catch (Exception me) { SAML2Utils.debug.error(classMethod + "Error when trying to get the idp's by standard Authn Context: " + me); } debugMessage(classMethod, " IDPList returns: " + idps); return idps.trim(); } private Set trimmedListToSet(List list) { Set trimmedSet= new HashSet(); String classMethod = "trimmedListToSet"; Iterator I = list.iterator(); while (I.hasNext()) { trimmedSet.add(I.next().toString().trim()); debugMessage(classMethod, " element added to Set : "); } return trimmedSet; } private String buildReturnURL(String requestID, HttpServletRequest request) { String methodName = "buildReturnURL"; StringBuffer sb = new StringBuffer(); String baseURL = request.getScheme() + "://" + request.getHeader("host") + request.getRequestURI(); String qs = request.getQueryString(); if (qs != null && !qs.isEmpty()) { baseURL = baseURL + "?" + qs; } StringBuffer retURL = new StringBuffer().append(baseURL); if (retURL.toString().indexOf("?") == -1) { retURL.append("?"); } else { retURL.append("&"); } retURL.append("requestID=").append(requestID); sb.append(retURL); String returnURL = sb.toString(); debugMessage(methodName, " ReturnURL is: " + returnURL); return returnURL; } private void storeSessionParamsAndCache( HttpServletRequest request, String idpListSt, AuthnRequest authnRequest, String hostProviderID, String realm, String requestID) { String methodName = "storeSessionParamsAndCache"; HttpSession hts = request.getSession(); hts.setAttribute(SESSION_ATTR_NAME_IDP_LIST, idpListSt); debugMessage(methodName, " Setting " + SESSION_ATTR_NAME_IDP_LIST + " = " + idpListSt); hts.setAttribute(SESSION_ATTR_NAME_RELAYSTATE, buildReturnURL(requestID, request)); debugMessage(methodName, " Setting " + SESSION_ATTR_NAME_RELAYSTATE); hts.setAttribute(SESSION_ATTR_NAME_SPREQUESTER, authnRequest.getIssuer().getValue().toString()); debugMessage(methodName, " Setting " + SESSION_ATTR_NAME_SPREQUESTER); RequestedAuthnContext requestedAuthnContext = authnRequest.getRequestedAuthnContext(); hts.setAttribute(SESSION_ATTR_NAME_REQAUTHNCONTEXT, requestedAuthnContext == null ? null : requestedAuthnContext.getAuthnContextClassRef()); debugMessage(methodName, " Setting " + SESSION_ATTR_NAME_REQAUTHNCONTEXT); // Save the important param in the reqParamHash so we can // locate them when we return to the IDPSSOFederate. Map paramsMap = new HashMap(); paramsMap.put("authnReq", authnRequest); paramsMap.put("spSSODescriptor", spSSODescriptor); paramsMap.put("idpEntityID", hostProviderID); paramsMap.put("realm", realm); paramsMap.put("relayState", relayState); paramsMap.put("binding", binding); SPCache.reqParamHash.put(requestID, paramsMap); } private String getRedirect( HttpServletRequest request, String idpFinderImplementation) { String methodName = "getRedirect"; // Get the base URL and construct the IdP Finder URL String baseURL = request.getScheme() + "://" + request.getHeader("host") + request.getContextPath(); String idpFinder = baseURL + "/" + idpFinderImplementation; debugMessage(methodName, ": Redirect url = " + idpFinder); return idpFinder; } /** * Returns true or false * depending if the flag isIDPFinderForAllSPs is set in the * IDP Extended metadata * * @param realm the realm name * @param idpEntityID the entity id of the identity provider * * @return the true/false * @exception SAML2Exception if the operation is not successful */ private Boolean isIDPFinderForAllSPs( String realm, String idpEntityID) throws SAML2Exception { String methodName = "isIDPFinderForAllSPs"; Boolean isIdpFinderForAllSPsEnabled = false; try { String idpFinderForAllSPs = IDPSSOUtil.getAttributeValueFromIDPSSOConfig( realm, idpEntityID, SAML2Constants.ENABLE_PROXY_IDP_FINDER_FOR_ALL_SPS); if (idpFinderForAllSPs != null && !idpFinderForAllSPs.isEmpty()) { debugMessage(methodName, "idpFinderForAllSPs is: " + idpFinderForAllSPs); isIdpFinderForAllSPsEnabled = idpFinderForAllSPs.equalsIgnoreCase("true"); } else isIdpFinderForAllSPsEnabled = false; } catch (Exception ex) { SAML2Utils.debug.error(methodName + "Unable to get IDP Proxy Finder.", ex); throw new SAML2Exception(ex); } return isIdpFinderForAllSPsEnabled; } /** * Returns the IDP Finder JSP configured in the extended metadata * * @param realm the realm name * @param idpEntityID the entity id of the identity provider * * @return the IDP Finder JSP * @exception SAML2Exception if the operation is not successful */ private String getIDPFinderJSP( String realm, String idpEntityID) throws SAML2Exception { String methodName = "getIDPFinderJSP"; String idpFinderJSP = SAML2Constants.DEFAULT_PROXY_IDP_FINDER; try { idpFinderJSP = IDPSSOUtil.getAttributeValueFromIDPSSOConfig( realm, idpEntityID, SAML2Constants.PROXY_IDP_FINDER_JSP); if (idpFinderJSP != null && !idpFinderJSP.isEmpty()) { debugMessage(methodName, "idpFinderForAllSPs is: " + idpFinderJSP); } } catch (Exception ex) { SAML2Utils.debug.error(methodName + "Unable to get IDP Proxy Finder.", ex); throw new SAML2Exception(ex); } return idpFinderJSP; } public List getAttributeListValueFromIDPSSOConfig( String realm, String hostEntityId, String attrName) { String classMethod = "IDPSSOUtil.getAttributeValueFromIDPSSOConfig: "; List result = null; try { IDPSSOConfigElement config = SAML2Utils.getSAML2MetaManager().getIDPSSOConfig( realm, hostEntityId); Map attrs = SAML2MetaUtils.getAttributes(config); List value = (List) attrs.get(attrName); if (value != null && value.size() != 0) { result = value; } } catch (SAML2MetaException sme) { if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message(classMethod + "get IDPSSOConfig failed:", sme); } result = null; } return result; } public List getSupportedAuthnContextsByIDP(String realm, String hostEntityId) { List authnContextList = null; List supportedClassRef = null; supportedClassRef = getAttributeListValueFromIDPSSOConfig( realm, hostEntityId, SAML2Constants.IDP_AUTHNCONTEXT_CLASSREF_MAPPING); if (supportedClassRef != null && !supportedClassRef.isEmpty()) { Iterator it = supportedClassRef.iterator(); while (it.hasNext()) { StringTokenizer tokenizer = new StringTokenizer((String) it.next(), "|"); if (tokenizer.countTokens() > 1) { authnContextList.add(tokenizer.nextToken()); } } } return authnContextList; } }