MicroStrategy ONE

Single Sign-on Sample: Custom ESM Code Explanation

The custom External Security Module (ESM) of the Single Sign-on (SSO) Sample illustrates how to implement single sign-on when MicroStrategy Web is used with an identity management application that is not supported out-of-the-box. The code in this custom ESM class retrieves a token that has been passed in the URL and validates it using the sample authentication server provided as part of the sample. (In a production environment, the token is validated using the API of the existing identity management application.) If the token is valid, the ESM creates a new MicroStrategy Intelligence Server session and returns the newly created session to MicroStrategy Web. If the token is invalid, the ESM tells MicroStrategy Web to redirect the user to a custom login page.  

The custom ESM for this sample application is included in the plug-in calledSSOSample, which is located under theCustomizationPlugins/AdvancedScenariosfolder in the SDK installation directory.  

The custom ESM class in this sample is packaged in com.microstrategy.sdk.samples.sso. It extends the AbstractExternalsSecurity class to take advantage of the default method implementations in that class. It imports the necessary MicroStrategy and non-MicroStrategy files and declares three variables to hold the values for the properties defined in the ssoesm.properties file. The custom ESM in this sample includes three public methods, five private helper methods, and two private inner classes. This topic explains how the code is constructed in each of these methods and classes.

The three public methods, which override the default methods in the AbstractExternalsSecurity class, include:

The five private helper methods, created just for this sample, include:

The two private inner classes, created just for this sample, include:

The code in each method or class is explained below. The code is explained in sections, with the explanation preceding each section of code.

Public methods

handlesAuthenticationRequest

The handlesAuthenticationRequest method attempts to use an existing session if one is available and alive and, if not, causes the custom login page to be opened so that the user can enter his or her credentials.

  • Explanation: First, the handlesAuthenticationRequest method is overridden to do the following:

    • Create an errorContainer on thread local storage that can be used across method calls. 

    • If a token exists in the RequestKeys parameter that was passed into the method, get the token and then immediately remove it from the RequestKeys for security purposes. 

      Copy
        public inthandlesAuthenticationRequest(RequestKeys reqKeys,
              ContainerServices cntSvcs, int reason) {
              //create errorContainer obj
              errorContainer = new ThreadLocal();
              //get token from request
              String token = reqKeys.getValue(ssoTokenName);
                  
              //remove token from reqKeys to disable token being displayed in the URL
              reqKeys.remove(ssoTokenName);
  • Explanation: If there was no token in the RequestKeys, check to see if there is a token in the ContainerServices’ session. If there is also no token in the ContainerServices, then the user is redirected to a custom login page. This is accomplished by returning USE_CUSTOM_LOGIN_PAGE, which causes MicroStrategy Web to call the getCustomLoginURL method. This method, which is described below, provides the URL of the custom login page to which the user is directed in order to log in.

    Copy
            if (StringUtils.isEmpty(token)) {
                //in this branch, token is empty
                    
                ///////////////////////////////////////////////
                //Step 1. Check session variable for an old authentication server token
                //and validate it if found.
                
                //get old token from Session
                String oldToken = (String) cntSvcs.getSessionAttribute(SSO_TOKEN_NAME);
                //check if the old token exists
                if (StringUtils.isEmpty(oldToken)) {
                    return USE_CUSTOM_LOGIN_PAGE;
                }
  • Explanation:  If there was no token in the RequestKeys, but there was a token in the ContainerServices session, it must be validated to see if it can still be used. If validation fails, then USE_CUSTOM_LOGIN_PAGE is returned, which causes MicroStrategy Web to call the getCustomLoginURL method. However, before this happens, the existing token information is removed from the ContainerServices.

    Copy
                //Check if old token is still valid.
                if (validate(ssoURL, oldToken, cntSvcs) == null) {
                    //Old token is expired, so close the session, clear session state, clear old token, and redirect to custom login page.
                    String sessionState = (String) cntSvcs.getSessionAttribute(SESSION_STATE);
                    if (StringUtils.isEmpty(sessionState)) {
                        closeISSession(sessionState);
                    }
                    cntSvcs.setSessionAttribute(SSO_TOKEN_NAME, null);
                    cntSvcs.setSessionAttribute(SESSION_STATE, null);
                    return USE_CUSTOM_LOGIN_PAGE;
                }
  • Explanation: If there was no token in the RequestKeys, but there was a token in the ContainerService session and it was determined to be valid, the existence of this valid token confirms that the user is who he or she claims to be. The next step is to load the session state if it is available. If it is not available,  then USE_CUSTOM_LOGIN_PAGE is returned, which causes MicroStrategy Web to call the getCustomLoginURL method. Otherwise, there is a valid token and a session state. Next, the session state must be checked to see if can be reconnected. If so, COLLECT_SESSION_NOW is returned, indicating that MicroStrategy Web can obtain the session right away. (COLLECT_SESSION_NOW causes the getWebIServerSession method to be called. The overridden version of this method is explained in a later step.) If the session state cannot be reconnected, then USE_CUSTOM_LOGIN_PAGE is returned, which causes MicroStrategy Web to call the getCustomLoginURL method that redirects the user to the custom login page.

    Copy
                ///////////////////////////////////////////////////////
                //Step 2. Try to restore session state, if it exists, then ensure we can use state to create connection.
                
                String sessionState = (String) cntSvcs.getSessionAttribute(SESSION_STATE);
                if (StringUtils.isEmpty(sessionState)) {
                    //no session state saved, client need to request a token from Authentication Server
                    errorContainer.set(new ErrorInfo(ErrorInfo.NO_TOKEN_FOUND, "No token found and no session state available"));
                    return USE_CUSTOM_LOGIN_PAGE;
                }
                if (reconnectISSession(sessionState, cntSvcs, reqKeys)) {
                    
                    return COLLECT_SESSION_NOW;
                   
                 } else {
                        
                    //Cannot use existing session state; clear session state and token
                    //and redirect to custom login page.
                    cntSvcs.setSessionAttribute(SESSION_STATE, null);
                    cntSvcs.setSessionAttribute(SSO_TOKEN_NAME, null);
                        
                    return USE_CUSTOM_LOGIN_PAGE;
                }
  • Explanation: If there is a token in the RequestKeys, any existing session information can be cleared out and the token can be checked to see if it is valid.  If validation succeeds, the session can be rebuilt. if validation fails,  then USE_CUSTOM_LOGIN_PAGE is returned, which causes MicroStrategy Web to call the getCustomLoginURL method. In this sample application, the validate method returns the name of the user, which is the same as the MicroStrategy user name. If the token validator returns name information that was different from the MicroStrategy user name, that name information has to be mapped to the MicroStrategy user name, as described in the Mapping Credentials using an External Repository scenario.  

    Copy
            } else {
                //in this branch, token is not empty
                    
                //////////////////////////////////////////////////////////
                //Step 1. close Intelligence Server session, clear session state and
                //   clear oldToken
                String sessionState = (String) cntSvcs.getSessionAttribute(SESSION_STATE);
                if (sessionState != null) {
                    closeISSession(sessionState);
                    cntSvcs.setSessionAttribute(SESSION_STATE, null);
                    cntSvcs.setSessionAttribute(SSO_TOKEN_NAME, null);
                }
                    
                ////////////////////////////////////////////////
                //Step 2. validate token
                String SSOUserID = validate(ssoURL, token, cntSvcs);
                if (StringUtils.isEmpty(SSOUserID)) {
                    //invalid token
                    return USE_CUSTOM_LOGIN_PAGE;
                }
  • Explanation: After all the necessary information has been gathered, an attempt is made to create a session. If session creation is successful, COLLECT_SESSION_NOW is returned, which causes MicroStrategy Web to call the getWebIServerSession method. If session creation fails, then USE_CUSTOM_LOGIN_PAGE is returned, which causes MicroStrategy Web to call the getCustomLoginURL method.  

    Copy
                ////////////////////////////////////////////
                //Step 3. create session
                boolean success = createISSession(SSOUserID, reqKeys, cntSvcs);
                if (success) {
                    //session created successfully
                    cntSvcs.setSessionAttribute(SSO_TOKEN_NAME, token);
                    return COLLECT_SESSION_NOW;
                } else {
                    //cannot create session
                    return USE_CUSTOM_LOGIN_PAGE;
                }
                    
            } //end of if token
         }

getCustomLoginURL

The getCustomLoginURL method builds the URL of the custom login page.

  • Explanation: The getCustomLoginURL method builds the URL of the custom login page to which the user is directed to log in when handlesAuthenticationRequest returns USE_CUSTOM_LOGIN_PAGE. (As explained in the earlier steps, this value is returned when no token is found or when a token is found but it is not valid.) If an Intelligence Server session cannot be created based on the information available, the user must be prompted to enter his or her credentials. The first step is to ensure that the original URL parameters passed in include all the parameters that are needed to send the user back into MicroStrategy Web from the custom login page. If the server, port, or project parameter is missing, it is added back.

    Copy
        public StringgetCustomLoginURL(String originalURL, String desiredServer,
            int desiredPort, String desiredProject) {
            //get errorInfo
            ErrorInfo errorInfo = (ErrorInfo) errorContainer.get();
            //clear errorContainer
            errorContainer.set(null);
            
            /////////////////////////////////////////////////////////
            //Generate a URL for the custom logon URL and add URL parameters
            //to this url by using Parameter builder
            
            //add server parameter in the url if it is not there
            if (desiredServer != null && originalURL.toLowerCase().indexOf("server=") == -1) {
                originalURL = originalURL + "&server=" + desiredServer;
            }
            
            //add server port parameter in the originalURL if it is not there
            if (originalURL.toLowerCase().indexOf("port=") == -1) {
                originalURL = originalURL + "&port=" + desiredPort;
            }
            //add project name parameter in the originalURL if it is not there
            if (desiredProject != null && originalURL.toLowerCase().indexOf("project=") == -1) {
                originalURL = originalURL + "&project=" + desiredProject;
            }
  • Explanation: Now, ParameterBuilder is used to construct the URL to the custom login page. This is accomplished by adding all the parameters that are needed and then returning the URL as a string.  For more information on using ParameterBuilder, see the Parameter Builder Infrastructure section of the MSDL.

    Copy
            ParameterBuilder pb = new DefaultURIBuilderImpl();
            try {
                URL url = new URL(customLoginURL);
                pb.setTargetBase(url.getProtocol() + "://" + url.getAuthority() + url.getPath());
                pb.setTargetSuffix(url.getQuery());
                if (errorInfo != null) {
                    pb.addParameter("ErrorCode", Integer.toString(errorInfo.getCode()));
                    pb.addParameter("ErrorMessage", errorInfo.getMessage());
                }
                pb.addParameter("OriginalURL", originalURL);
                pb.addParameter("Server", desiredServer);
                pb.addParameter("Port", Integer.toString(desiredPort));
                pb.addParameter("Project", desiredProject);
                return pb.toString();
            } catch (Exception e) {
                //throw an runtime exception
                throw new MSTRUncheckedException(e);
            }
             
        }

getWebIServerSession

The getWebIServerSession method attempts to retrieve an existing session state.

  • Explanation: The getWebIServerSession method is called when COLLECT_SESSION_NOW is returned from the handlesAuthenticationRequest method, indicating that an Intelligence Server session is ready. In getWebIServerSession, an attempt is first made to get the session state from the ContainerServices. If a session state cannot be retrieved, there is nothing more that can be done and an error is thrown. If a session state can be retrieved, it is used to restore the session itself. If the session is already alive, it is returned. If the session is not alive, it is first reconnected and then the new state is set back to where it was.          

    Copy
        public WebIServerSessiongetWebIServerSession(RequestKeys reqKeys,
            ContainerServices cntSvcs) {
            
            try {
                
                ///////////////////////////////////////////////////
                //Step 1. Confirm session is non-empty - if empty, throw error.
                String sessionState = (String) cntSvcs.getSessionAttribute(SESSION_STATE);
                if (StringUtils.isEmpty(sessionState)) {
                    throw new MSTRUncheckedException("WebIServerSession.getWebIServerSession(): Unable to restore session");
                }
                
                //////////////////////////////////////////////
                //Step 2. Restore session and ensure session is still live.
                WebIServerSession userSession = WebObjectsFactory.getInstance().getIServerSession();
                userSession.restoreState(sessionState);
                if (!userSession.isAlive()) {
                    userSession.reconnect();
                    String newState = userSession.saveState(EnumWebPersistableState.MAXIMAL_STATE_INFO);
                    cntSvcs.setSessionAttribute(SESSION_STATE, newState);
                }
                return userSession;
  • Explanation: If something goes wrong during the session creation process, there is not much that can be done. About the only intelligent thing to do here is to clear the session information from the container and throw an exception back to MicroStrategy Web.

    Copy
           } catch (WebObjectsException e) {
                
                //In case of exception, most likely credential has been changed.
                //clear session state and old session
                cntSvcs.setSessionAttribute(SESSION_STATE, null);
                cntSvcs.setSessionAttribute(SSO_TOKEN_NAME, null);
                throw new MSTRUncheckedException("WebIServerSession.getWebIServerSession(): Unable to restore session");
            }
        }

Private methods

The following methods support the work of this sample, but do not illustrate MicroStrategy-specific functionality. To view the code explanations for any of these methods, simply click the "Click here" link for the appropriate method.

validate

The private validate helper method is used to authenticate the user represented by the user token. If this succeeds, validate returns the MicroStrategy user login for the user. Depending on your particular external authentication system, the implementation of this method can vary considerably. The implementation in this sample is more for the sake of completeness than for instruction. ClosedClick here to see a simple implementation of the validate method and a high-level explanation for this method.

  • Explanation: This explanation will only discuss the highlights of the validate method. The first section of this method does the task of setting up a connection to the external authentication system, building a request, and passing in the token. If all goes well, the XML stream that is returned gives us the result. Otherwise, an exception is thrown, which is handled by returning null to indicate an error.

    Copy
        private Stringvalidate(String authURL, String token, ContainerServices cntSvcs) {
            /////////////////////////////////////////////////////////////
            //Step 1. Send HTTP request to Authentication Server
            
            HttpURLConnection connection;
            InputStream is = null;
            try {
                // open the connection
                URL url = new URL(authURL);
                connection = (HttpURLConnection) url.openConnection();
            
                // set output capability on the connection.
                connection.setDoOutput(true);
            
                // use POST method
                connection.setRequestMethod("POST");
            
                // send the query
                PrintWriter out = new PrintWriter(connection.getOutputStream());
                out.print(ssoTokenName + "=" + token);
                out.close();
            
                //get the response stream
                is = connection.getInputStream();
            
            } catch (Exception e) {
                errorContainer.set(new ErrorInfo(ErrorInfo.HTTP_ERROR, e.getMessage()));
                return null;
            }
  • Explanation: A DOM is used to parse the XML result and prepare it for the processing below. Again, the error case is handled simply.

    Copy
            /////////////////////////////////////////////////////////
            //Step 2: get the response as XML DOM Document
            Document xmlDom = null;
            try {
                //get the response xml as DOM document
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                xmlDom = builder.parse(is);
            } catch (Exception e) {
                errorContainer.set(new ErrorInfo(ErrorInfo.XML_PARSING_ERROR, e.getMessage()));
                return null;
            }
  • Explanation: This large block of code does the grunt work of reading the XML response, verifying that the user is properly authenticated, and then parsing out the MicroStrategy user login. If the process succeeds, the user login is returned. Otherwise, null is returned.

    Copy
            ///////////////////////////////////////////////////////
            //Step 3. validate the returned XML DOM Document
            Element top = xmlDom.getDocumentElement();
            if (top == null) {
                errorContainer.set(new ErrorInfo(ErrorInfo.XML_VALIDATION_ERROR, "return_code node error"));
                return null;
            }
            NodeList passOrFailNodes = top.getChildNodes();
            if (passOrFailNodes.getLength() == 0) {
                errorContainer.set(new ErrorInfo(ErrorInfo.XML_VALIDATION_ERROR, "return_code node error"));
                return null;
            }
            
            //passOrFailNode = the first pass or fail node
            Node passOrFailNode = null;
            for (int i = 0; i < passOrFailNodes.getLength(); i++) {
                Node nd = passOrFailNodes.item(i);
                String name = nd.getNodeName();
                if (name.equals("pass") || name.equals("fail")) {
                    passOrFailNode = nd;
                    break;
                }
            }
            
            // neither pass or nor fail node found
            if (passOrFailNode == null) {
                errorContainer.set(new ErrorInfo(ErrorInfo.XML_VALIDATION_ERROR, "neither pass nor fail node found"));
                return null;
            }
            
            //passOrFailNode is pass node
            if (passOrFailNode.getNodeName().equals("pass")) {
                String userID = passOrFailNode.getAttributes().getNamedItem("userid").getNodeValue();
                if (StringUtils.isEmpty(userID)) {
                    errorContainer.set(new ErrorInfo(ErrorInfo.XML_VALIDATION_ERROR, "no userid attribute"));
                    return null;
                }
                return userID;
            } else {
            
                //passOrFailNode is fail node
                String msg = passOrFailNode.getAttributes().getNamedItem("msg").getNodeValue();
                if (StringUtils.isEmpty(msg)) {
                    errorContainer.set(new ErrorInfo(ErrorInfo.XML_VALIDATION_ERROR, "no msg attribute"));
                    return null;
                }
                errorContainer.set(new ErrorInfo(ErrorInfo.AUTH_FAIL, msg));
                return null;
            }          
      }

createIServerSession

The private createIServerSession helper method does the actual task of creating an Intelligence Server session. This is a multi-step process because the user password must be randomized with every login for security purposes and new users must be created automatically in MicroStrategy if they do not already exist. In order to accomplish this, an administrator session is needed. ClosedClick here to see a detailed explanation of a simple implementation of the createIServerSession method. 

  • Explanation: In the first part of the code, the AdminSessionCache object (created statically and described below) is used to retrieve an administrator session. Then this administrator session is used to look up users, create users, and change existing password, as shown below. If an administrator session is successfully obtained, it is refreshed and a WebObjectSource is gotten from it.  If the attempt to get an administrator session fails for some reason, null is returned since not much else can be done here.

    Copy
        private booleancreateISSession(String SSOUserID, RequestKeys reqKeys, ContainerServices cntSvcs) {
            
            //////////////////////////////////////////////////////
            //Step 1. get admin session from cache, use (Server Name, Server Port)
            //       as Cache Hint
            
            //get IServer and Port Number from request keys
            String IServer = reqKeys.getValue(EnumWebParameters.WebParameterLoginServerName);
            String port = reqKeys.getValue(EnumWebParameters.WebParameterLoginPort);
            int portNum = 0;
            try {
                if (!StringUtils.isEmpty(port)) {
                    portNum = Integer.parseInt(port);
                }
            } catch (Exception e) {
                //if the port is not a valid number, use 0 as port number
                portNum = 0;
            }
            
            WebObjectsFactory adminFactory;
            try {
                //get adminSession from cache
                WebIServerSession adminSession = (WebIServerSession) asCache.get(new ServerHint(IServer, portNum));
                
                //refresh admin Session
                adminSession.refresh();
                
                //get adminFacotry
                adminFactory = adminSession.getFactory();
                
            } catch (Exception e) {
                    
                errorContainer.set(new ErrorInfo(ErrorInfo.MSTR_EXCEPTION_SEARCH, e.getMessage()));
                return false;
            }
            
            //get WebObjectSource for admin
            WebObjectSource adminOS = adminFactory.getObjectSource();
            WebUser user = null;
  • Explanation: First, a search is made for an existing user with the same login name as the user for whom a session is being created.  This is accomplished using the search API. You simply setup a search object, give it your parameters, set it to asynchronous mode (so that you don’t have to poll), and submit it.  If you get any results back, your user must be the one you found.

    Copy
            ///////////////////////////////////////////////////
            //Step 2. search for user SSOUserID
            try {
               
                 WebSearch search = adminOS.getNewSearchObject();
                //search for login id
                search.setAbbreviationPattern(SSOUserID);
                search.setSearchFlags(search.getSearchFlags() | EnumDSSXMLSearchFlags.DssXmlSearchAbbreviationWildCard);
                //asynchronized search
                search.setAsync(false);
                //search for user
                search.types().add(new Integer(EnumDSSXMLObjectSubTypes.DssXmlSubTypeUser));
                //search on repository
                search.setDomain(EnumDSSXMLSearchDomain.DssXmlSearchDomainRepository);
                search.submit();
                
                WebFolder f = search.getResults();
                //since login id is unique identity, if found, the first one is the user
                if (f.size() > 0) {
                    user = (WebUser) f.get(0);
                }
                
            } catch (Exception e) {
                //catch all exceptions
                errorContainer.set(new ErrorInfo(ErrorInfo.MSTR_EXCEPTION_SEARCH, e.getMessage()));
                return false;
            }
  • Explanation: Generate a random 10-character password using the private getRandomString method described below.  Set the password onto your user below.  You do this for security purposes so that no one will be able to manually log in as this user since they would have an exceedingly hard time guessing the password, and, even if that could, it would change on the next normal log in.

    Copy
            /////////////////////////////////////////////////
            //Step 3. Create or Modify user account
            //The authentication server should assign a User ID to only one user at a time.
            
            //random password
            String pwd = getRandomString(10);
  • Explanation: If you do not find a user in the MicroStrategy metadata, then you need to create one. Luckily, the MicroStrategy Web SDK makes this process easy.  You simply create a new object of type DssXmlTypeUser and specify the login and full name.  (You’ll adjust the password and other parameters below.)

    Copy
            try {
                if (user == null) {
                    //does not exist
                    user = (WebUser) adminOS.getNewObject(EnumDSSXMLObjectTypes.DssXmlTypeUser, EnumDSSXMLObjectSubTypes.DssXmlSubTypeUser);
                    user.setLoginName(SSOUserID);
                    user.setFullName(SSOUserID);
  • Explanation: If you do find a user with our login, then populate the object with all of its metadata properties.

    Copy
                } else {
                    user.populate();
                }
  • Explanation: Now that you have a user object to work with (either a new one or one loaded from metadata), you can populate it.  First you set up the password and make it non-modifiable. Then you give the user a series of useful privileges and save the object.

    Copy
                //change password
                user.getStandardLoginInfo().setPassword(pwd);
                user.getStandardLoginInfo().setPasswordModifiable(false);
                
                //have to enable and set privileges
                user.setEnabled(true);
                
                //change or add more privileges if you need.
                user.getLocalPrivileges().grant(EnumDSSXMLPrivilegeTypes.DssXmlPrivilegesWebUser);
                user.getLocalPrivileges().grant(EnumDSSXMLPrivilegeTypes.DssXmlPrivilegesWebAdvancedDrilling);
                user.getLocalPrivileges().grant(EnumDSSXMLPrivilegeTypes.DssXmlPrivilegesWebViewHistoryList);
                user.getLocalPrivileges().grant(EnumDSSXMLPrivilegeTypes.DssXmlPrivilegesWebExecuteRWDocument);
                user.getLocalPrivileges().grant(EnumDSSXMLPrivilegeTypes.DssXmlPrivilegesWebChangeViewMode);
                user.getLocalPrivileges().grant(EnumDSSXMLPrivilegeTypes.DssXmlPrivilegesWebObjectSearch);
                user.getLocalPrivileges().grant(EnumDSSXMLPrivilegeTypes.DssXmlPrivilegesWebSort);
                user.getLocalPrivileges().grant(EnumDSSXMLPrivilegeTypes.DssXmlPrivilegesWebSwitchPageByElements);
                
                adminOS.save(user);
                
            } catch (Exception e) {
                errorContainer.set(new ErrorInfo(ErrorInfo.MSTR_EXCEPTION_MODIFY_ACCOUNT, e.getMessage()));
                return false;               
            }
  • Explanation: With the user successfully created or obtained, you are ready to create an Intelligence Server session.  You load it with the parameters (including the random password that you just assigned above), and then save the session state.  Finally, you return "true" to indicate success.

    Copy
            ///////////////////////////////////////////////
            //Step 4. create user session
                
            try {
                WebIServerSession userSession = WebObjectsFactory.getInstance().getIServerSession();
                String project = reqKeys.getValue(EnumWebParameters.WebParameterLoginProjectName);
                userSession.setProjectName(project);
                userSession.setServerName(IServer);
                userSession.setLogin(SSOUserID);
                userSession.setPassword(pwd);
                userSession.setServerPort(portNum);
                userSession.getSessionID();
                String sessionState = userSession.saveState(EnumWebPersistableState.MAXIMAL_STATE_INFO);
                cntSvcs.setSessionAttribute(SESSION_STATE, sessionState);
            } catch (Exception e) {
                errorContainer.set(new ErrorInfo(ErrorInfo.MSTR_EXCEPTION_CREATE_SESSION, e.getMessage()));
                return false;
            }        return true;
        }

reconnectISSession

The private reconnectISSession helper method does the job of reconnecting the Intelligence Server session. ClosedClick here to see the code and explanations for this helper method. 

  • Explanation: To start the process, you load the a WebIServerSession object with your session state.

    Copy
        private booleanreconnectISSession(String sessionState, ContainerServices cntSvcs, RequestKeys reqKeys) {
            
           //reconnect session
           WebIServerSession iss = WebObjectsFactory.getInstance().getIServerSession();
           iss.restoreState(sessionState);
        
           String newProject = reqKeys.getValue(EnumWebParameters.WebParameterLoginProjectName);
           if (newProject!=null && StringUtils.isNotEqual(newProject, iss.getProjectName())) {
              //This sample code doesn't support single sign-on across Intelligence Servers
              iss.setProjectName(newProject);
              }
  • Explanation: If the session is already alive or you can reconnect it, save the session state information back into the container. If not, return false to indicate failure.

    Copy
           try {
        
              if (!iss.isAlive()) {
                 iss.reconnect();
                 String newState = iss.saveState(EnumWebPersistableState.MAXIMAL_STATE_INFO);
                 cntSvcs.setSessionAttribute(SESSION_STATE, newState);
                 }
              } catch (WebObjectsException e) {
              //if cannot reconnect, most likely the credential has been changed.
              errorContainer.set(new ErrorInfo(ErrorInfo.NO_TOKEN_FOUND, "No token found and session state cannot be reused"));
              return false;
           }
           return true;
        }

closeISSession

The private closeISSession helper method does the job of closing a session. ClosedClick here to see the code and explanation for this helper method. 

  • Explanation: Populate a WebIServerSession object with the session state and attempt to close it.  If successful, return "true". If not, return "false".

    Copy
        private booleancloseISSession(String sessionState) {
            try {
                WebIServerSession iss = WebObjectsFactory.getInstance().getIServerSession();
                iss.restoreState(sessionState);
                iss.closeSession();
                return true;
            } catch (WebObjectsException e) {
                //most likely the ISeverSession has already expired or closed.
                return false;
            }
        }

getRandomString

The private getRandomString helper method provides the simple-minded logic to generate a random password string. ClosedClick here to see the code and explanation for this helper method. 

  • Explanation: A production implementation would be more robust and could get as fancy as your security requirements dictate. You could do some algorithmic calculation based on the user name, or you could introduce time as part of the randomization. You could even get very fancy and introduce a cryptographically secure hash. While the possibilities are endless, this simple example takes a fairly simple approach which affords some reasonable level of security.

    Copy
        private StringgetRandomString(int length) {
            
            char charPool[] = {
                    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
                    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
            
            StringBuffer strBuf = new StringBuffer();
            for (int i = 0; i < length; i++) {
                strBuf.append(charPool[(int) Math.floor((Math.random() * charPool.length))]);
            }
            
            return strBuf.toString();
        }

Private inner classes

The following classes support the work of this sample, but do not illustrate MicroStrategy-specific functionality. To view the code explanations for either of these classes, simply click the "Click here" link for the apprpriate class.

ServerHint

The private inner ServerHint class holds the information necessary to create an administrator session. It is used internally in the process of finding and loading a cached administrator session. Its main purposes is to provide a key as part of this process. ClosedClick here to see the code and explanation for this inner class. 

  • Explanation:  A ServerHint is a combination of server name and server port. The ServerHint class is used together with the AdminSessionCache class described below to create an administrator session. Note that the login and password are hard-coded in this sample. In a production implementation, however, you would likely want to load them from a secure repository of some type so that they can be changed easily while maintaining security.

    Copy
        /**
         * The inner Class ServerHint is used together with AdminSessionCache.
         * A ServerHint is a combination of server name and server port.
         */
        private classServerHintimplements CacheHint {
            
            //Intelligence Server Name
            private String serverName;
            
            //Intelligence Server Port
            private int port;
            
            /**
             * Constructor
             */
            public ServerHint(String serverName, int port) {
                this.serverName = serverName;
                this.port = port;
            }
            
            /**
             * @return Intelligence Server Name
             */
            public String getServerName() {
                return  serverName;
            }
            /**
             * @return Intelligence Server Port
             */        public int getServerPort() {
                return port;
            }
            
            /**
             * Override getCacheStateId() and setCacheStateId(), if need maintaining
             * cache consistency across different caches. This sample code support this
             * functionality.
             */
            public Long getCacheStateId(String name) {
                return ZERO_STATE_ID;
            }
            
            /**
             * Override getCacheStateId() and setCacheStateId(), if need maintaining
             * cache consistency across different caches. This sample code support this
             * functionality.
             */
            public void setCacheStateId(String name, Long stateId) {}
        }

AdminSessionCache

The private innerAdminSessionCacheclass creates an Intelligence Server administrator session.ClosedClick here to see the code and explanation for this inner class.

  • Explanation: The AdminSessionCache class uses the ServerHint class above to get the Intelligence Server connection information. Then it populates the administrator session with the usual parameters and returns that session.

    Copy
    * AdminSessionCache is used to cache administrator's IServer session.
         */
        private static classAdminSessionCacheextends ServerCacheBase {
            
            /**
             * This method returns the login to use to login as the Intelligence
             * Server administrator.  This login, and the password returned from
             * getAdminPassword, is used to create and change the passwords for
             * users.  This is currently hardcoded with a constant - when deploying
             * this application, this should either be changed to the correct name
             * for the administrator login or code should be added to obtain the
             * administrator login from some source.
             * @return The login that is expected to be used for the administrative user.
             */
            private String getAdminLogin() {
                return "administrator";
            }
            /**    
             * This method returns the password to use to login as the Intelligence
             * Server administrator.  This password, and the login returned from
             * getAdminLogin, is used to create and change the passwords for users. 
             * This is currently hardcoded with a constant - when deploying this
             * application, this should either be changed to the correct password
             * for the administrator login or code should be added to obtain the
             * administrator password from some source. 
             * @return The password that is expected to be used for the administrative user.
             */
            private String getAdminPassword() {
                return "";
            }
            
            /**
             * The constructor.
             * The name of AdminSessionCache is ssoesm.asc.
             *
             */
            public AdminSessionCache() {
                super("ssoesm.asc");
            }
            
            /**
             * Returns ServerName as Cache Key
             * @param hint the hint object (instance of ServerHint).
             * @return the key
             * @throws CacheException if somethig gos wrong.
             * @see com.microstrategy.utils.cache.CacheBase#getKey(com.microstrategy.utils.cache.CacheHint)
             */
            protected Object getKey(CacheHint hint) throws CacheException {
                return hint;
            }
            
            /**
             * Creates a new cached object instance and populates it with data from the
             * persistent storage. In case the object not found in the persistent storage
             * the implementation can return either null or a dummy object.
             *
             * @param hint the hint object.
             * @return WebIServerSession object.
             * @throws CacheException if somethig gos wrong.
             * This method overrides CacheBase.load(CacheHint hint)
             * @see com.microstrategy.utils.cache.CacheBase#load(com.microstrategy.utils.cache.CacheHint)
             */
            protected Object load(CacheHint hint) throws CacheException {
                
                WebObjectsFactory adminFactory = WebObjectsFactory.getInstance();
                WebIServerSession adminSession = adminFactory.getIServerSession();
                
                //get IServer and port from hint
                String IServer = ((ServerHint) hint).getServerName();
                int port = ((ServerHint) hint).getServerPort();
                
                //set up adminSession
                adminSession.setServerName(IServer);
                adminSession.setServerPort(port);
                adminSession.setLogin(getAdminLogin());
                adminSession.setPassword(getAdminPassword());
                
                return adminSession;
            }        
        }