MicroStrategy ONE

Code Explanation for Scenario: Restricting the Number of Open User Sessions

In this customization scenario, you create a custom ESM that applies additional authorization criteria to restrict user access to the application based on the number of open sessions. To do this, you write code to override the isRequestAuthorized method, which is called for every request. This method allows you to tell MicroStrategy whether a request is authorized or not— which is quite powerful because you can make this decision based on almost any criteria that you choose. This particular code sample demonstrates how to create a simple custom ESM that restricts the number of sessions that any named user can have open at any given time, but you can use different logic to apply other authorization criteria. This code sample also illustrates how to use the User Connection API to get connection information from Intelligence Server.  

Custom ExternalSecurity java class   (calledRestrictOpenSessions.javain the corresponding scenario)

A generic explanation of how to create the custom ESM code required to perform this customization is provided below. The code is explained in sections, with the explanation preceding each section of code.

  • Explanation: Specify the package in which this class will reside and import the necessary classes, including AbstractExternalSecurity (which contains default implementations for all of the methods).  In addition to the classes you normally import for ESMs, in this example you also include imports for the User Connection Monitoring API.:

    Copy
    package com.microstrategy.sdk.samples.externalsecurity;
    import java.util.ResourceBundle;
    import com.microstrategy.web.app.AbstractExternalSecurity;
    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.objects.admin.monitors.EnumWebMonitorType;
    import com.microstrategy.web.objects.admin.monitors.MonitorFilter;
    import com.microstrategy.web.objects.admin.monitors.UserConnectionResults;
    import com.microstrategy.web.objects.admin.monitors.UserConnectionSource;
    import com.microstrategy.web.platform.ContainerServices;
    import com.microstrategy.webapi.EnumDSSXMLLevelFlags;
    import com.microstrategy.webapi.EnumDSSXMLMonitorFilterOperator;
    import com.microstrategy.webapi.EnumDSSXMLUserConnectionInfo;
  • Explanation: Set up the class declaration and the private variables you will use. Declare that this custom External Security class (compiled from the sample java file called RestrictOpenSessions.java in the corresponding scenario) extends AbstractExternalSecurity. The first two variables define the name and key for the properties file that will be used to initialize the count limit for open user sessions.  In this example, the properties file is named configSessionRestrictions.properties and the key within that file is named validSessionsPerUser.:

    Copy
    public class RestrictOpenSessions extends AbstractExternalSecurity
    {
        private static final String PROPERTIES_FILE_NAME = "configSessionRestrictions";
        private static final String VALID_SESSIONS = "validSessionsPerUser";
        static int mValidSessions = 1;

    You must create a properties file called configSessionRestrictions.properties and save it in your MicroStrategy Web installation directory, either under the WEB-INF/classes folder or under the plugins/<plugin name>/WEB-INF/classes folder. It should contain one line that looks like this:

    validSessionsPerUser=2

    The line shown above sets the maximum number of sessions that will be allowed for any named user. In this example, the maximum number is sets as "2". The value that is read from the properties file is stored in mValidSessions, the third variable declared in the code snippet above.

  • Explanation: The static initializer is where the properties file information is read.  The MicroStrategy Web infrastructure does most of the work for you.  All you need to do is specify the properties file name (PROPERTIES_FILE_NAME) and key (VALID_SESSIONS).  After you get the value, you parse it as an integer and store the result for future use.  This happens only once when the ESM is first initialized.  :

    Copy
        static
        {
            // Read properties from the properties file
            ResourceBundle lProps = ResourceBundle.getBundle(PROPERTIES_FILE_NAME);
            if(lProps != null)
            {
                String lValidSessionsString = (String) lProps.getObject(VALID_SESSIONS);
                if(lValidSessionsString.length() > 0)
                {
                    // Internal variable for number of valid sessions. Set to 1 by default.
                    mValidSessions = Integer.parseInt(lValidSessionsString);
                }
                else
                {
                   // Add additional code to better handle errors
                   System.out.println("Error reading maximum session count");
                       }
            }
            else
            {
                // Add additional code to better handle errors
                System.out.println("Error reading maximum session count");
                   }
        }

    The error handling in this example is quite sparse. A production version would handle errors more gracefully.

  • Explanation: You override the isRequestAuthorized method to get access to validate every request that comes into the MicroStrategy Web system. The code in this method does the real work of the ESM, as explained below::

    • First, you set up the return variable and a place to store the administrator session.  You set the isReqAuth variable to "true" to indicate that this request is authorized by default.  :

      Copy
          public boolean isRequestAuthorized(RequestKeys iReqKeys, ContainerServices iCntSvcs, WebIServerSession iUser)
          {
              boolean isReqAuth = true;
              WebIServerSession lAdminSession = null;
              try
              {

      It is important to store the admin session outside the try-catch block so that the finally clause can always close the session no matter what happens.  Leaking a session is an error that you want to carefully avoid.

    • Next, you check to see if you already have login credentials. If not, then there is nothing to validate and you can simply return "true" to signal that the request is authorized. If you do have credentials, you proceed to the work of getting the session count for the user.  In order to do that work, you need an administrator session.  

      Copy
                  if(iUser.getProjectName() != null && iUser.getProjectName().length() > 0 && iUser.getLogin().length() > 0)
                  {
                      lAdminSession = getAdminSession();

      You get an administrator session via the helper method that is described in another section of code below.

    • With an administrator session, you can use the User Connection Monitoring API to get the session count for a user.  First, you get the UserConnectionSource object so that you can set up a query.  Then, you set up the level and a filter. Using the filter, you can ask for all user connections that match both the user name (DssXmlUserConnUserName) and the project name (DssXmlUserConnProjectName).:

      Copy
                      if(lAdminSession != null)
                      {
                          UserConnectionSource lUserSource = (UserConnectionSource) lAdminSession.getFactory().getMonitorSource(EnumWebMonitorType.WebMonitorTypeUserConnection);
                          lUserSource.setLevel(EnumDSSXMLLevelFlags.DssXmlDetailLevel);
                          MonitorFilter lFilter = lUserSource.getFilter();
                          lFilter.add(EnumDSSXMLUserConnectionInfo.DssXmlUserConnUserName, EnumDSSXMLMonitorFilterOperator.DssXmlEqual, iUser.getUserInfo().getName());
                          lFilter.add(EnumDSSXMLUserConnectionInfo.DssXmlUserConnProjectName, EnumDSSXMLMonitorFilterOperator.DssXmlEqual, iUser.getProjectName());
    • Once you have a properly configured User Connection Source, you execute the query itself. The query shown below asks Intelligence Server for all the connection information that matches your filter.  In this example, you get a list of results that has one entry for each connection that matches your user/project filter. This allows you to simply get the number of entries in this result set, compare it to your limit (which was set up in the static initializer above), and decide if you should authorize the request or not. If the number of user sessions exceeds the specified limit, then you close the session and return "false" to indicate this request is not authorized. It is very important that you close the user session here. If you don’t, there will be a dangling session that will sit around awaiting timeout. If the limit on open sessions has not yet been reached, you return the default value set above, "true".:

      Copy
                          UserConnectionResults results = lUserSource.getUserConnections();
                          if (results != null && results.getCount() > mValidSessions)
                          {
                              iUser.closeSession();
                              isReqAuth = false;
                          }
                      }
                  }
                  // Return whether the user is authorized
                  return isReqAuth;
              }
    • The exception handling in this example is very rudimentary. It simply prints the error and returns the result that the request is not authorized. In a production application, you would want to handle this case more intelligently.:

      Copy
              catch (Exception x)
              {
                  // Add code to handle exceptions
                  System.out.println("ESM encountered exception: " + x.toString());
                  return false;
              }
    • The finally clause ensures that the administrator session is always closed, no matter what happens in the isRequestAuthorized method. This is important because you don’t want to litter the system with dangling administrator sessions even if your code runs into trouble.:

      Copy
              finally
              {
                  if(lAdminSession != null)
                  {
                      try
                      {
                          // Close session with Intelligence Server
                          lAdminSession.closeSession();
                      }
                      catch(Exception x)
                      {
                          // Add code to handle this case better
                          System.out.println("Error closing admin session: " + x.toString());
                             }
                  }
              }
          }
    • The last section of code is the helper method to get the administrator session used above. Although there is a reasonable amount of code in the snippet shown below, it is primarily boilerplate code.  First, you get a session object and then populate the server information and administrator credentials. In this example, these values are hardcoded, but in a production application you would normally retrieve them dynamically. Once the session has been populated, you call thegetSessionID()method to force a connection to Intelligence Server. If that succeeds, you return the session. If it fails, you catch the exception and return null. In a production application, you would want to handle this error case more robustly.:

      Copy
          private WebIServerSession getAdminSession()
          {
              // Create factory object
              WebObjectsFactory lFactory = WebObjectsFactory.getInstance();
              WebIServerSession lServerSession = lFactory.getIServerSession;
              lServerSession.setServerName("localhost");
              lServerSession.setServerPort(0);
              lServerSession.setLogin("Administrator");
              lServerSession.setPassword("");
              try
              {
                  lServerSession.getSessionID();
                  return lServerSession;
              }
              catch (WebObjectsException x)
              {
                  // Add code to handle this case better
                  System.out.println("ESM encountered exception: " + x.toString());
                  return null;
              }
          }
      }
    • This sample uses the general MicroStrategy Web authorization error page to signal that the limit of open sessions has been reached. This is a very crude way to notify the user of this condition since he or she will likely have no idea why they are hitting a cryptic authorization error. In a production application you would improve this user feedback by creating a custom error page and referencing it from an overridden getFailureURL() method in your ESM. The code would look like this (where the URL is the URL that points to your custom error page).

      Copy
          public String getFailureURL(int reqType, ContainerServices cntrSvcs)
          {
              // Point to a custom error handler page
              return "http://localhost:8080/MSTRWeb/plugins/securityEnforcementModule/jsp/Failure.jsp";
          }