Microstrategy SDK - Custom ESM

SDK

How to implement your own Custom ESM / SSO code?

See the Sample code for custom SSO and the How to register your Custom ESM / SSO entries below.

How can we implement SAML authentication?

See the Sample code for custom SSO entry below.

What is the purpose of handlesAuthenticationRequest?

This is the main method that we must implement / override. This method attempts to use an existing session if one is available and alive, otherwise, it does whatever you code it to do.

What happens when handlesAuthenticationRequest returns a value of USE_CUSTOM_LOGIN_PAGE?

USE_CUSTOM_LOGIN_PAGE is a pre-defined constant. When handlesAuthenticationRequest returns USE_CUSTOM_LOGIN_PAGE, MicroStrategy Web will invoke our getCustomLoginURL method.

What are some of the ways that we can use to persist information between requests?

  • Using cntSvcs.setSessionAttribute and cntSvcs.getSessionAttribute. The cntSvcs is passed to our handlesAuthenticationRequest method.
  • Using reqKeys

How to persist information using session?

String userName = "...";
cntSvcs.setSessionAttribute("user", userName);

String userName = cntSvcs.getSessionAttribute("user");

cntSvcs is of the ContainerServices class, and is passed to our getWebIServerSession, handlesAuthenticationRequest and other methods.

What is reqKeys?

A structure that we can use to store some information between requests. This is passed as the first parameter to our handlesAuthenticationRequest method. Probably this structure allows us to obtain information that is part of the request, either as URL parameters or POST parameters.

How to store a value into reqKeys?

reqKeys.add("Server", "...");

where … is whatever you want to store, and "Server" is the name of the key identifying the slot that store this information.

How to get a value from reqKeys?

String v = reqKeys.getValue("...");

where … is a string containing the name of the key.

How to remove a key and its value from reqKeys?

reqKeys.remove("...");

How does MicroStrategy manage reqKeys?

Probably by using a server side session variable, a client side cookie, or append it to the URL of every request. Not sure. Need further investigation.

Sample code for custom SSO:

package com.yourcompany.esm;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;

import org.xml.sax.SAXException;

import sun.misc.BASE64Decoder;

import com.microstrategy.utils.MSTRUncheckedException;
import com.microstrategy.utils.StringUtils;
import com.microstrategy.utils.log.Level;
import com.microstrategy.utils.serialization.EnumWebPersistableState;
import com.microstrategy.web.app.AbstractExternalSecurity;
import com.microstrategy.web.app.ExternalSecurity;
import com.microstrategy.web.app.platform.ServletContainerServices;
import com.microstrategy.web.beans.RequestKeys;
import com.microstrategy.web.objects.WebIServerSession;
import com.microstrategy.web.objects.WebObjectsException;
import com.microstrategy.web.objects.WebObjectsFactory;
import com.microstrategy.web.platform.ContainerServices;
import com.microstrategy.web.platform.GenericCookie;
import com.microstrategy.webapi.EnumDSSXMLAuthModes;
import com.yourcompany.Log;
import com.yourcompany.sql.Connector;
import com.yourcompany.utils.EnumFileNames;

/**   
*  This class is an implementation of MicroStrategy External Security module.
*  It is a part of MicroStrategy Single Sign-On Sample. This class is useful 
*  when using MicroStrategy Web with identity management products. In such 
*  scenario, when a request is made to MicroStrategy, the identity management 
*  product authenticates the user and checks to see if he has access to 
*  MicroStrategy and if yes the request is sent to MicroStrategy Web with a 
*  token. MicroStrategy needs to use this token to validate the user 
*  and handle the request. The code in this class retrieves the token passed, 
*  validates the token by using the identity management product's API. 
*  If the token is valid, the ESM will create a new MicroStrategy Intelligence 
*     Server session and return the newly created session to MicroStrategy Web.  
*  If it is invalid, then the ESM will tell MicroStrategy Web to redirect to 
*  a custom login page.<br>
*  
*/
public class CustomSSO extends AbstractExternalSecurity {

     ////////////////////////////////////////////////
    //private static attributes

    private static final String USER_ID="Uid";
    private static final String PASSWORD="Pwd";
    private static final String SESSION_STATE = "mstrSmgr";
    private String userName;
    private String requestedResouce;
    private String loginURL;
    private String webApp;
    private String uid;
    private String pwd;
    private String QSESSION;
    private String QMSCOOKIE;
    private String fileName;

    /** MicroStrategy Web will call this method when it needs a session. 
    * This method tells MicroStrategy Web how to handle this authentication 
    * request. In this implementation of the handlesAuthenticationRequest 
    * method, we will either direct the application to collect a session 
    * ID from the ESM or redirect to the custom login page. <br>
    *  
    * @param reqKeys request keys
    * @param cntSvcs container services
    * @param reason reason of executing this method
    * @return Depending the reason, runtime 
    * condition, it returns one of the two values: USE_MSTR_DEFAULT_LOGIN, USE_CUSTOM_LOGIN_PAGE
    * and COLLECT_SESSION_NOW. 
    * 
    **/
    public int handlesAuthenticationRequest(RequestKeys reqKeys, ContainerServices cntSvcs, int reason) {
        String methodName = "handlesAuthenticationRequest";
        if (cntSvcs instanceof ServletContainerServices) {
            ((ServletContainerServices)cntSvcs).getResponse().addHeader("P3P","CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"");
        }

        Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, methodName + " invoked.");
        Log.logger.entering(CustomSSO.class.getName(), methodName, new Object[] { reqKeys, cntSvcs, reason });

        switch (reason) {
            case NO_SESSION_FOUND:
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Entering ESM Module because seesion was not found.");
                break;
            case INVALID_CREDENTIALS:
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Entering ESM Module because bredentails were invalid.");
                break;
            case LOGIN_FIRST:
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Entering ESM Module. Login First Option is enabled.");
                break;
            default:
                break;
        }

        // If webApp value is present in request keys, save it into a cookie named webAppCookie.
        // If webApp value is not present in request keys, get it from the cookie named webAppCookie if webAppCookie is available
        webApp = reqKeys.getValue(EnumFileNames.URL_PARAM_NAME); //URL parameter that inform us what kind of version of the app (JSP/NET) we`re using
        loginURL = reqKeys.getValue("hostUrl");
        Log.logger.logp(Level.INFO, CustomSSO.class.getName(), "handlesAuthenticationRequest", "loginURL is not null.  loginURL:" + loginURL + ".  Storing it into cookie name loginURLCookie.");

        if (webApp != null) {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "webApp is not null.  webApp:" + webApp + ".  Storing it into cookie name webAppCookie.");
            GenericCookie gc = cntSvcs.newCookie("webAppCookie", webApp);
            cntSvcs.addCookieToResponse(gc);
        } else {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "webApp is not found in request keys. Trying to see if webApp can be retrieve from cookie named webAppCookie.");
            GenericCookie cookie = cntSvcs.getCookie("webAppCookie");
            if (cookie != null) {
                webApp = cookie.getValue();
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "webAppCookie is present.  webApp:" + webApp);
            } else {
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "webAppCookie is not present.");
            }
        }

        if (loginURL != null) {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "loginURL is not null.  loginURL:" + loginURL + ".  Storing it into cookie name loginURLCookie.");
            GenericCookie gc2 = cntSvcs.newCookie("loginURLCookie", loginURL);
            cntSvcs.addCookieToResponse(gc2);            
        } else {
            GenericCookie cookie = cntSvcs.getCookie("loginURLCookie");
            if (cookie != null) {
                loginURL = cookie.getValue();
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "loginURLCookie is present.  loginURL:" + loginURL);
            } else {
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "loginURLCookie is not present.");
            }

        }

        uid = reqKeys.getValue(USER_ID);
        pwd = reqKeys.getValue(PASSWORD);    

        if ((webApp == null) || (webApp.equals(""))) {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Cannot determine the value for webApp.  Assuming that user is trying to access MicroStrategy directly without going through any application integration.");
            //If Uid and Pwd parameters are passed, invoke method of super class
            if (uid!=null && pwd!=null) {
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "User credential is available in request keys.  Invoking handlesAuthenticationRequest from super class to handle authentication.");
                return super.handlesAuthenticationRequest(reqKeys, cntSvcs, reason); 
            } else {
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "User credential is NOT available in request keys. Display the default login page.");
                return USE_MSTR_DEFAULT_LOGIN; 
            }
        } else if (uid!=null && pwd!=null) {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "webApp was not null or empty (webApp: " + webApp + "), and user credential is present in request keys, returning ExternalSecurity.COLLECT_SESSION_NOW");
            return ExternalSecurity.COLLECT_SESSION_NOW;
        }

        // Determine appropriate property file to use based on the value of webApp
        fileName = getFileName(webApp);

        if ("jsp".equalsIgnoreCase(webApp)){
            // webApp is jsp.  Invoke validateUser to check against QXPLORE.QSSO_MICRO_STRATEGY table
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, " webApp=jsp.");
            boolean isValid = false;
            try {
                isValid = validateUser(cntSvcs);
            } catch (Exception e) {
                Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, methodName, e);
            }
            if (isValid) {
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "validateUser returned true.");
                return ExternalSecurity.COLLECT_SESSION_NOW;
            }
        } else if ("asp".equalsIgnoreCase(webApp)) {
            // webApp is asp. Invoke checkSAMLresponse to validate SAML
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, " webApp=asp.");
            if (cntSvcs instanceof ServletContainerServices) {
                HttpServletRequest request = ((ServletContainerServices) cntSvcs).getRequest();
                requestedResouce = request.getRequestURL() + "?" + request.getQueryString();
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Parameter r was set to "+ requestedResouce);
            }
            try {
                requestedResouce = URLEncoder.encode(requestedResouce, "UTF-8");
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Parameter r was encoded to "+ requestedResouce);
            } catch (UnsupportedEncodingException e) {
                Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, "Problem durring creation r parameter. Unable to encode url using UTF-8.", e);
            }

            String encSAMLRespParam = "";
            // take name of the SAML response parameter
            String parameterName = Connector.getPropertiesSupportInstance(fileName).getSAMLparameter();
            //take expected location of SAML parameter
            String location = Connector.getPropertiesSupportInstance(fileName).getSAMLparameterLocation();
            // depending on property setting try to obtain parameter from:
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Tring to get " + parameterName + " parameter from " + location);
            if ("url".equalsIgnoreCase(location)) {
                encSAMLRespParam = reqKeys.getValue("samlResponse");
            } else if ("cookie".equalsIgnoreCase(location)) {
                encSAMLRespParam = cntSvcs.getCookie(parameterName).getValue();
            } else if ("header".equalsIgnoreCase(location)) {
                encSAMLRespParam = cntSvcs.getHeaderValue("samlResponse");
            }           

            if (StringUtils.isEmpty(encSAMLRespParam)) {
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "No "+ parameterName + " parameter in " + location);
                return USE_CUSTOM_LOGIN_PAGE; 
            } else {
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Encrypted "+ parameterName + " parameter equals "+ encSAMLRespParam);
            }

            // check response

            // long startTime = System.currentTimeMillis();
            boolean samlCheckResult = checkSAMLresponse(encSAMLRespParam);
            // long diff = System.currentTimeMillis() - startTime;
            // Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "SAML response parsing time:" + diff);
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "checkSAMLresponse returns:" + samlCheckResult);

            if (samlCheckResult) {
                return ExternalSecurity.COLLECT_SESSION_NOW;
            } else {
                return USE_CUSTOM_LOGIN_PAGE;
            }
        }
        return USE_MSTR_DEFAULT_LOGIN;
    }    

    /**
     * Redirect user to the custom page in case of login failure 
     * @param reqType type of request.
     * @param cntrSvcs Container Services.
     * @return Url to with borwser will be redirected in case of error.
     */
    public String getFailureURL(int reqType, ContainerServices cntrSvcs) {
        String methodName = "getFailureURL";
        Log.logger.entering(CustomSSO.class.getName(), methodName, new Object[] { reqType, cntrSvcs });
        Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, methodName + " invoked.  webApp:" + webApp + ", reqType:" + reqType);

        // Point to a custom error handler page
        String url = "";
        if (webApp != null) {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "webApp:" + webApp + ".  Getting error URL from property file.");
            url = Connector.getPropertiesSupportInstance(fileName).getErrorUrl();
        } else if (cntrSvcs != null) {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "webApp is null. Getting error URL from the request object.");
            HttpServletRequest request = ((com.microstrategy.web.app.platform.ServletContainerServices)cntrSvcs).getRequest();
            url = request.getRequestURL().toString();
        }
        Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Failure URL:" + url);
        return url; 
    }

    /**
     * Returns string with login page url with appended parameter r with orginal request URL
     * @param originalURL string with oryginal url.
     * @param desiredServer desired server from oryginal request
     * @param desiredPort desired port from oryginal request
     * @param desiredProject desired project from oryginal request
     * @return string with URL
     */
    @Override
    public String getCustomLoginURL(String originalURL, String desiredServer, int desiredPort,String desiredProject) {
        String methodName = "getCustomLoginURL";
        Log.logger.entering(CustomSSO.class.getName(), methodName, new Object[] { originalURL, desiredServer, desiredPort, desiredProject });
        Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, methodName + " invoked.  Parameter originalURL:" + originalURL + ", desiredServer:" + desiredServer);

        String url = Connector.getPropertiesSupportInstance(fileName).getLoginUrl();
        if (loginURL != null) {
            url = loginURL + "SessionSamlTokenProvider.ashx";
        }
        url = url + (url.contains("?")? "&r=" : "?r=") + requestedResouce;

        Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, methodName + " returns:" + url);
        Log.logger.exiting(CustomSSO.class.getName(), methodName, url);
        return url;
    }

    public WebIServerSession getWebIServerSession(RequestKeys reqKeys, ContainerServices cntSvcs) {
        String methodName = "getWebIServerSession";
        Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, methodName + " invoked.");
        Log.logger.entering(CustomSSO.class.getName(), methodName, new Object[] { reqKeys, cntSvcs });
        // try to take session state
        String sessionState = (String) cntSvcs.getSessionAttribute(SESSION_STATE);
        if (StringUtils.isEmpty(sessionState)) {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "sessionState is empty.");
        } else {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "sessionState is not empty.");
        }
        if (StringUtils.isEmpty(reqKeys.getValue("webApp"))) {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "webApp is not present in reqKeys.");            
        } else {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "webApp is present in reqKeys.");
        }
        if (StringUtils.isEmpty(sessionState)) {
            sessionState = reqKeys.getValue(SESSION_STATE);
        }
        // create new session object       
        WebIServerSession iSession = WebObjectsFactory.getInstance().getIServerSession();
        String iServerName = null;
        if (StringUtils.isNotEmpty(sessionState)) {
            // There was a session state.  Try to restore the session.
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, methodName + " There was a session state (" + sessionState + ").  Going to restore the session from sessionState.");
            if (StringUtils.isEmpty(iSession.getServerName())) {
                iSession.restoreState(sessionState);                
            }
            if (StringUtils.isEmpty(userName)) {
                userName = (String)cntSvcs.getSessionAttribute("user");                
            }
        }

        if (StringUtils.isEmpty(iSession.getServerName())) {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Server name is not known from iSession.");            
        }

        // There is no session state so we neeed to create it from scratch.
        // Take server name from keys

        // If the name of the intelligence server is not known, try getting it from the request Keys
        if (StringUtils.isEmpty(iSession.getServerName())) {
            String serverNameFromReqKeys = getServer(reqKeys);
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Server name from reqKeys:"  + serverNameFromReqKeys);
            iSession.setServerName(serverNameFromReqKeys);
        }
        // If we could not determine the name of the intelligence server, but the URL parameter Server is present, use it.
        if (StringUtils.isEmpty(iSession.getServerName())) {
            String serverNameFromRequestKey = reqKeys.getValue("Server");
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Server name from Server reqKeys:"  + serverNameFromRequestKey);
            iSession.setServerName(serverNameFromRequestKey);
        }
        // If the name of the intelligence server is not known, as the last resort, get it from the properties file.
        if (StringUtils.isEmpty(iSession.getServerName())) {
            // Here we hard code the file name to ssoesmASP because it does not matter which properties file we read from.
            // This setting need to be present in both properties files.
            String intelligenceServerName = Connector.getPropertiesSupportInstance("ssoesmASP").getIntelligenceServerName();
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Server name from properties file:"  + intelligenceServerName);
            iSession.setServerName(intelligenceServerName);            
        }

        iServerName = iSession.getServerName();
        if (StringUtils.isNotEmpty(iServerName)) {
            reqKeys.add("Server", iServerName);
        }

        // Set project name
        if (StringUtils.isEmpty(iSession.getProjectName())) {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Project name is unknown inside "  + methodName);
            String projectNameFromReqKeys = getProject(reqKeys);
            iSession.setProjectName(projectNameFromReqKeys);
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Project name from reqKeys:"  + projectNameFromReqKeys);
        }
        if (StringUtils.isEmpty(iSession.getProjectName())) {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Project name is still unknown inside "  + methodName);            
        }
        // Set port
        if (StringUtils.isEmpty(iSession.getServerPort())) {
            iSession.setServerPort(getPort(reqKeys));
        }

        // Set authentication mode
        if (StringUtils.isEmpty(iSession.getAuthMode())) {
            iSession.setAuthMode(EnumDSSXMLAuthModes.DssXmlAuthSimpleSecurityPlugIn);
        }

        iSession.setProjectName(getProject(reqKeys));
        String _trustToken = Connector.getPropertiesSupportInstance(fileName).getTrustToken(iServerName);

        Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "iServerName:" + iServerName + ",userName:" + userName + ", _trustToken:" + _trustToken);

        //set trusted connection between web and iserver
        iSession.setLogin(userName); 

        iSession.setTrustToken(_trustToken);          
        iSession.setLDAPDistinguishedName(userName); // Why do we invoke this method when LDAP is not being used?

        //try to reconnect session
        try {
            if (!iSession.isAlive()) {
                if (StringUtils.isNotEmpty(sessionState)) {
                    // If the session is not alive, and sessionState is not empty, reconnect.
                    iSession.reconnect();
                } else {
                    // If the session is not alive, and sessionState is empty, establish a new session
                    iSession.getSessionID();
                }
                if (cntSvcs instanceof ServletContainerServices) {
                    ((ServletContainerServices)cntSvcs).getResponse().addHeader("P3P","CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"");
                }
            }
            // cookies should only exist in jsp portal type
            if ((QMSCOOKIE != null) && (QSESSION != null)) {
                insertSessionState(iSession.saveState(),QMSCOOKIE, QSESSION);
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName,"sessionState inserted into QSS_MICRO_SSTRATEGY table");
            }
            //update session state 
            String newState = iSession.saveState(EnumWebPersistableState.MAXIMAL_STATE_INFO);
            //save set new state
            cntSvcs.setSessionAttribute("user", userName);
            cntSvcs.setSessionAttribute(SESSION_STATE, newState);
            reqKeys.add(SESSION_STATE, newState);
        } catch (Exception e) {
            Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), "getWebIServerSession", "Unable to restore session.");
            Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), "getWebIServerSession", e.getMessage());
            cntSvcs.invalidateHttpSession();
            throw new MSTRUncheckedException("Unable to create or restore session.");
        }
        return iSession;
    }

    /** 
     * This method validates the user in case of JSP app version. It`s getting cookies value 
     * and hitting QSSO_MICRO_STRATEGY table to get the MSTR trusted user ID 
     * @param cntSvcs.    
     * @return If success,we invoke COLLECT_SESSION_NOW 
     */ 
    private boolean validateUser(ContainerServices cntSvcs) throws Exception {
        String methodName = "validateUser";
        Log.logger.entering(CustomSSO.class.getName(), methodName, new Object[] { cntSvcs });

        HttpServletRequest request = ((com.microstrategy.web.app.platform.ServletContainerServices)cntSvcs).getRequest();
        Cookie[] cookies = request.getCookies();
        String cookieNameFromPropertyFile = Connector.getPropertiesSupportInstance(fileName).getCookieName();
        //We are looking for cookie name defined in properties file and separating  based on "|" symbol
        for (int i = 0; i < cookies.length; i++) {
            if (cookies[i].getName().equals(cookieNameFromPropertyFile)) {
                QSESSION = cookies[i].getValue().split("\\|")[0];
                QMSCOOKIE = cookies[i].getValue().split("\\|")[1];    
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "QMSCOOKIE :"+QMSCOOKIE+ " QSESSION :"+QSESSION);
            }
        }
        boolean isValid = true;

        //If those values are not empty proceed with connecting to the database
        if ((!QMSCOOKIE.equalsIgnoreCase("")) && (!QSESSION.equalsIgnoreCase(""))) {
            ResultSet rset = null;
            PreparedStatement pstmt = null;
            Connection conn = null;
            try {
                conn = new Connector().getConnection(fileName);

                String sqlQuery = "SELECT USERNAME FROM QXPLORE.QSSO_MICRO_STRATEGY WHERE ID=? AND QSESSION=?";
                pstmt = conn.prepareStatement(sqlQuery);
                pstmt.setString(1, QMSCOOKIE);
                pstmt.setString(2, QSESSION);
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "query: " + sqlQuery);
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "ID: " + QMSCOOKIE);
                Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "QSESSION: " + QSESSION);
                rset = pstmt.executeQuery();

                while (rset.next()) {
                        userName = rset.getString("USERNAME").toString();
                    Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "userName:" + userName);
                }

                if ((userName == null) || (userName.equals(""))) {
                    Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, "SQL query returns no result.");
                        isValid = false;
                } else {
                    isValid = true;
                }
            } catch (SQLException e) {
                 Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, "Error durring querying database.");
                 Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, e.getMessage());
                 isValid = false;
            } finally {
                rset.close();
                pstmt.close();
                conn.close(); 
            }
        } else {
            isValid = false;
        }
        return isValid;
    }

    /** 
     * This method validates the user in case of ASP app version. It`s getting SAML response 
     * and parse in steps based according to  properties file
     * @param response - SAML response.    
     * @return If success,we invoke COLLECT_SESSION_NOW 
     */ 
    private boolean checkSAMLresponse(String response) {
        String methodName = "checkSAMLresponse";
        Log.logger.entering(CustomSSO.class.getName(), methodName, new Object[] { response });

        String decSAMLRespParam = null;
        SamlObjectInfo samlResponse = null;
        String trustedLogin = null;
        try {
            decSAMLRespParam = decode(response);
        } catch (Exception e) {
            Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, "Error during decoding saml response.");
            Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, e.getMessage());
            return false; // Validation failed
        } 

        if (StringUtils.isEmpty(decSAMLRespParam)) {
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "Empty SAML Response parameter after decoding.");
            return false; // Validation failed
        } else {
            // Now it's time to parse response xml.
            try {
                samlResponse = new SamlObjectInfo(fileName, decSAMLRespParam);
            } catch (ParserConfigurationException e) {
                Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, "Unable to configure SAML xml parser.", e);
                return false; // Validation failed
            } catch (SAXException e) {
                Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, "SAXException durring parsing saml response xml.", e);
                return false; // Validation failed
            } catch (IOException e) {
                Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, "IOException durring parsing saml response xml.", e);
                return false; // Validation failed
            }
        }

        // If we are here, xml was parsed correctly, and we can check signature
        if (!samlResponse.isValid()) {
            Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, "SAML response certificate is not valid");
            return false; // Validation failed
        }

        try {
            trustedLogin = samlResponse.getTrustedLogin();
            userName = trustedLogin;
        } catch (XPathExpressionException e) {
            Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, "XPathExpressionException when creating XPath Query.", e);
            return false; // Validation failed
        }

        if (StringUtils.isEmpty(trustedLogin)) {
            Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, "SAML validation failed: User trusted token was empty!");
            return false; // Validation failed
        } 

        //everything was ok.
        Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "SAML authentication successful.  Returning true!");
        Log.logger.exiting(CustomSSO.class.getName(), methodName, "SAML authentication successful.  Returning true!");
        return true;
    }

    /**
     * Method for loading property file (ASP/JSP) depending of webApp parameter.
     * @param webApp
     * @return
     */
    private String getFileName(String webApp) {
        String methodName = "getFileName";
        Log.logger.entering(CustomSSO.class.getName(), methodName, new Object[] { webApp });
        Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, methodName + " invoked!");

        Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "type :"+webApp);
        if (webApp.equalsIgnoreCase("jsp")) {
            return EnumFileNames.JSP_FILE;
        } else if (webApp.equalsIgnoreCase("asp")) {
            return EnumFileNames.ASP_FILE;
        } else {
            return webApp;
        }
    }

    /**
     * Decodes String from url,base64 or zip encoding steps are confirgured using property.
     * @param encodedXmlString string with encoded value
     * @return decoded string
     * @throws DataFormatException thrown in case that string was wrongly encoded. 
     * @throws IOException thrown in case that string was wrongly encoded. 
     */
    private String decode(String encodedXmlString) throws DataFormatException, IOException {
        String methodName = "decode";
        if (Log.logger.isLoggable(Level.FINE)) {
            Log.logger.entering(CustomSSO.class.getName(), methodName, new Object[] { encodedXmlString });
        }

        String result = encodedXmlString;
        String[] decodingSteps = Connector.getPropertiesSupportInstance(fileName).getDecodingSteps().split(",");
        for (String step : decodingSteps) {
            if (step.equalsIgnoreCase("base64")) {
                result = decodeBase64(result);
            }
            if (step.equalsIgnoreCase("inflate")) {
                result = inflate(result);
            }
            if (step.equalsIgnoreCase("url")) {
                result = urlDecode(result);
            }
        }
        return result;
    }

    private String urlDecode(String encodedXmlString) throws UnsupportedEncodingException {
        String methodName = "urlDecode";
        if (Log.logger.isLoggable(Level.FINE)) {
            Log.logger.entering(CustomSSO.class.getName(), methodName, new Object[] { encodedXmlString });
        }
        return URLDecoder.decode(encodedXmlString, "UTF-8");
    }

    private String decodeBase64(String encodedXmlString) throws DataFormatException, IOException {
        String methodName = "decodeBase64";
        if (Log.logger.isLoggable(Level.FINE)) {
            Log.logger.entering(CustomSSO.class.getName(), methodName, new Object[] { encodedXmlString });
        }

        BASE64Decoder decoder = new BASE64Decoder();
        byte[] decodedBytes = decoder.decodeBuffer(encodedXmlString);
        // byte[] base64DecodedByteArray = Base64.decode(encodedXmlString);
        return new String(decodedBytes);
    }

    private String inflate(String encodedXmlString) throws UnsupportedEncodingException, DataFormatException {
        String methodName = "inflate";
        if (Log.logger.isLoggable(Level.FINE)) {
            Log.logger.entering(CustomSSO.class.getName(), methodName, new Object[] { encodedXmlString });
        }

        byte[] byteArray = encodedXmlString.getBytes();

        Inflater inflater = new Inflater(true);
        inflater.setInput(byteArray);
        // since we are decompressing, it's impossible to know how much space we
        // might need; hopefully this number is suitably big
        byte[] xmlMessageBytes = new byte[5000];
        int resultLength = inflater.inflate(xmlMessageBytes);
        if (!inflater.finished()) {
            throw new RuntimeException("didn't allocate enough space to hold " + "decompressed data");
        }
        inflater.end();

        String decodedResponse = new String(xmlMessageBytes, 0, resultLength, "UTF-8");
        return decodedResponse;
    }

    /**
     *
     * @param sessionState
     * @param QMSCOOKIE
     * @param QSESSION
     */
    private void insertSessionState(String sessionState, String QMSCOOKIE, String QSESSION) throws Exception {
        String methodName = "insertSessionState";
        Log.logger.entering(CustomSSO.class.getName(), methodName, new Object[] { sessionState, QMSCOOKIE, QSESSION });
        Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, methodName + " invoked!");

        PreparedStatement pstmt = null;
        Connection conn = null;
        ResultSet rset = null;
        try {
            conn = new Connector().getConnection(fileName);
            String sqlQuery = "UPDATE QXPLORE.QSSO_MICRO_STRATEGY SET SESSION_STATE=? WHERE ID=? AND QSESSION=?";

            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "query:" + sqlQuery);
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "sessionState:" + sessionState);
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "QMSCOOKIE:" + QMSCOOKIE);
            Log.logger.logp(Level.INFO, CustomSSO.class.getName(), methodName, "QSESSION:" + QSESSION);

            pstmt = conn.prepareStatement(sqlQuery);
            pstmt.setString(1, sessionState);
            pstmt.setString(2, QMSCOOKIE);
            pstmt.setString(3, QSESSION);
            rset = pstmt.executeQuery();

        } catch (SQLException e) {
            Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, "Unable to set SESSION_STATE in QXPLORE.QSSO_MICRO_STRATEGY due to:" + e.getMessage());
            Log.logger.logp(Level.SEVERE, CustomSSO.class.getName(), methodName, methodName, e);
        } finally {
            rset.close();
            pstmt.close();
            conn.close();             
        }
        Log.logger.exiting(CustomSSO.class.getName(), methodName, "returning..");
    }
}

How to register your Custom ESM / SSO?

See http://khaidoan.wikidot.com/local--files/microstrategy-sdk-ide-setup/MicroStrategySetupSDKDevEnv.docx

Before you begin, obtain the following information from you identity management application vendor's documentation:

  1. After the user is authenticated and authorized, what information is or can be passed as part of the request to the target application (MicroStrategy Web)?
  2. What vendor APIs can be used by the target application (MicroStrategy Web) to verify or validated the information (tokens) passed by connecting to the identity management application?
  3. For any subsequent requests by the same user, what information will be passed to the target application and do any additional checks need to be made?
  4. Where should the user be redirected if the information provided (the token) is invalid or timed-out?
  5. How will the token be validated?
  6. What are the scenarios for valid token?
  7. What are the scenarios for invalid token?
  8. How are the different scenarios above handled for both valid and invalid tokens?
  9. How (in what format) will this information be communicated to MicroStrategy?
  10. How will execution errors / exceptions be handled?

Generally, information passed to MicroStrategy Web includes token (time-based, which means it will time out at some point) and a user ID. MicroStrategy Web must make a call the the vendor APIs to validate the token passed. In the response from the vendor application, the token may be valid or invalid. If it is invalid, the appropriate URL must be defined within the getFailureURL() method to direct the user to the correct location. If the token is valid, additional information may be provided from the vendor application as part of the response.

If the user ID was not provided in the initial request, this information may be passed as part of the valid token response.

A particular example covers a generic single sign-on scenario where users are authenticated before they connect to MicroStrategy.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License