Name

sn_employee.ep_AccessManagement

Description

Provide APIs to refesh application access for employees that meet a criteria

Script

var ep_AccessManagement = Class.create();
ep_AccessManagement.prototype = {
  initialize: function(appName) {
      this.GRANT_ACTION = 'GRANT';
      this.CREATE_ACTION = 'CREATE';
      this.REVOKE_ACTION = 'REVOKE';
      this.ACTION_EVENT_NAME = 'sn_employee.update.employee.app.access';
      this.REFRESH_APP_EVENT_NAME = 'sn_employee.refresh.employee.app.access';
      this.REFRESH_EVENT_STAGGER_NAME = 'sn_employee.refresh_app_event_stagger';
      this.GRANT_STAGGER_NAME = 'sn_employee.app_access_grant_stagger';
      this.REVOKE_STAGGER_NAME = 'sn_employee.app_access_revoke_stagger';
      this.MAX_USER_PER_EVENT_NAME = 'sn_employee.max_users_per_grant_event';
      this.appGr = null;
      this.appName = null;
      this.accessCondition = '';
      this.profileCondition = '';
      var appGr = new GlideRecord('sn_employee_app');
      if (appGr.get('name', appName)) {
          this.appName = appName;
          this.appGr = appGr;
      }
  },

  /** 
   *  Schedule events to refresh access accross all Licensed Applications (sn_employee_app)
   *  @returns void
   */
  refreshAccessForAllApps: function() {
      var gdtWhenToProcess = new GlideDateTime();
      var employeeAppGr = new GlideRecord('sn_employee_app');
      employeeAppGr.query();
      while (employeeAppGr.next()) {
          gs.eventQueueScheduled(this.REFRESH_APP_EVENT_NAME, employeeAppGr, '', '', gdtWhenToProcess);
          gdtWhenToProcess.addSeconds(this._getNumberProperty(this.REFRESH_EVENT_STAGGER_NAME, 300));
      }
  },

  /** 
   *  Refresh access for a Licensed Application (sn_employee_app)
   *  @param {GlideRecord} employeeAppGr - sn_employee_app record to refresh access
   *  @returns void
   */
  refreshAccessForApp: function(employeeAppGr) {
      if (!employeeAppGr.isValidRecord())
          throw this._getError('Invalid employee app GlideRecord');

      //no criteria for app
      var criteriaGr = new GlideRecord("sn_employee_app_access_criteria");
      if (!criteriaGr.get('app', employeeAppGr.getUniqueValue()))
          return;

      this.appGr = employeeAppGr;
      this.appName = employeeAppGr.getValue('name');
      employeeAppGr.setValue('last_run_time', new GlideDateTime());
      employeeAppGr.update();

      this._setConditions();
      this.grantAccess(this.profileCondition);
      this.createAccess(this.accessCondition);
  },

  /** 
   *  Grant app access by updating sn_employee_app_access to active for each profile that meets the @param condition
   *  @param {String} condition - condition used to evaluate granting access for employee app access
   *  @returns void
   */
  grantAccess: function(condition) {
      var MAX_USERS_PER_EVENT = this._getNumberProperty(this.MAX_USER_PER_EVENT_NAME, 25000);
      var appAccessGr = new GlideRecord('sn_employee_app_access');
      appAccessGr.addEncodedQuery(condition);
      appAccessGr.addQuery('app', this.appGr.getUniqueValue());
      appAccessGr.addQuery('last_granted', '!=', this.appGr.getValue('last_run_time'));
      appAccessGr.addQuery('profile.user.active', true);
      appAccessGr.setLimit(MAX_USERS_PER_EVENT);

      appAccessGr.setValue('last_granted', this.appGr.getValue('last_run_time'));
      appAccessGr.setValue('active', true);
      appAccessGr.updateMultiple();

      //check if we have more records to grant outside of batch size
      var extraGrantAccessGr = new GlideRecord('sn_employee_app_access');
      extraGrantAccessGr.addEncodedQuery(condition);
      extraGrantAccessGr.addQuery('last_granted', '!=', this.appGr.getValue('last_run_time'));
      extraGrantAccessGr.addQuery('app', this.appGr.getUniqueValue());
      extraGrantAccessGr.addQuery('profile.user.active', true);
      extraGrantAccessGr.setLimit(1);
      extraGrantAccessGr.query();
      if (extraGrantAccessGr.hasNext()) {
          var params = {};
          params['appName'] = this.appName;
          params['action'] = this.GRANT_ACTION;
          params['condition'] = condition;

          //schedule another grantAccess
          var gdtWhenToProcess = new GlideDateTime().addSeconds(this._getNumberProperty(this.GRANT_STAGGER_NAME, 30));
          gs.eventQueueScheduled(this.ACTION_EVENT_NAME, this.appGr, JSON.stringify(params), '', gdtWhenToProcess);
      } else
          this.revokeAccess(); //granting complete, move to revoke
  },

  /** 
   *  Grant app access by creating sn_employee_app_access for each profile that meets the @param condition 
   *  @param {String} condition - condition used to evaluate granting access for employee app access
   *  @returns void
   */
  createAccess: function(condition) {
      var counter = this._getNumberProperty(this.MAX_USER_PER_EVENT_NAME, 25000);
      //includes all profiles that meet a condition which have no matching sn_employee_app_access.profile for the current app
      var rlCondition = '^RLQUERYsn_employee_app_access.profile,=0^app=' + this.appGr.getUniqueValue() + '^ENDRLQUERY';
      var profileGr = new GlideRecord('sn_employee_profile');
      profileGr.addQuery('user.active', true);
      profileGr.addEncodedQuery(condition + rlCondition);
      profileGr.setLimit(counter + 1); // limit query results because processing in batch size of counter
      profileGr.query();
      while (counter > 0 && profileGr.next()) {
          try {
              var appAccessGr = new GlideRecord('sn_employee_app_access');
              appAccessGr.setValue('app', this.appGr.getUniqueValue());
              appAccessGr.setValue('profile', profileGr.getUniqueValue());
              appAccessGr.setValue('date_granted', this.appGr.getValue('last_run_time'));
              appAccessGr.setValue('last_granted', this.appGr.getValue('last_run_time'));
              appAccessGr.setValue('active', true);
              appAccessGr.insert();
          } catch (error) {
              throw this._getError('Insert of an employee app access record failed - ' + error);
          }
          counter--;
      }

      if (profileGr.hasNext()) {
          var params = {};
          params['appName'] = this.appName;
          params['action'] = this.CREATE_ACTION;
          params['condition'] = condition;

          //schedule another createAccess
          var gdtWhenToProcess = new GlideDateTime().addSeconds(this._getNumberProperty(this.GRANT_STAGGER_NAME, 30));
          gs.eventQueueScheduled(this.ACTION_EVENT_NAME, this.appGr, JSON.stringify(params), '', gdtWhenToProcess);
      }
  },

  /** 
   *  Revoke app access by setting sn_employee_app_access inactive for employees that did not 
   *  meet the sn_employee_app_access_criteria condition
   *  @returns void
   */
  revokeAccess: function() {
      var MAX_USERS_PER_EVENT = this._getNumberProperty(this.MAX_USER_PER_EVENT_NAME, 25000);
      var appAccessGr = new GlideRecord('sn_employee_app_access');
      appAccessGr.addQuery('last_granted', '!=', this.appGr.getValue('last_run_time'));
      appAccessGr.addQuery('app', this.appGr.getUniqueValue());
      appAccessGr.addActiveQuery();
      appAccessGr.setLimit(MAX_USERS_PER_EVENT);

      appAccessGr.setValue('active', false);
      appAccessGr.setValue('date_revoked', this.appGr.getValue('last_run_time'));
      appAccessGr.updateMultiple();

      //check if we have more records to revoke outside of batch size
      var extraRevokeAccessGr = new GlideRecord('sn_employee_app_access');
      extraRevokeAccessGr.addQuery('last_granted', '!=', this.appGr.getValue('last_run_time'));
      extraRevokeAccessGr.addQuery('app', this.appGr.getUniqueValue());
      extraRevokeAccessGr.addActiveQuery();
      extraRevokeAccessGr.setLimit(1);
      extraRevokeAccessGr.query();
      if (extraRevokeAccessGr.hasNext()){
          var params = {};
          params['appName'] = this.appName;
          params['action'] = this.REVOKE_ACTION;

          //schedule another revokeAccess
          var gdtWhenToProcess = new GlideDateTime().addSeconds(this._getNumberProperty(this.REVOKE_STAGGER_NAME, 30));
          gs.eventQueueScheduled(this.ACTION_EVENT_NAME, this.appGr, JSON.stringify(params), '', gdtWhenToProcess);
      }

  },

  /** 
   *  Set encoded query conditions for all criteria for an sn_employee_app
   *  @returns void
   */
  _setConditions: function() {
      var accessCondition = '';
      var profileCondition = '';

      var criteriaGr = new GlideRecord('sn_employee_app_access_criteria');
      criteriaGr.query('app', this.appGr.getUniqueValue());
      criteriaGr.query();
      while (criteriaGr.next()) {
          if (accessCondition !== '')
              accessCondition += '^NQ';
          if (accessCondition !== '')
              profileCondition += '^NQ';

          var criteria = criteriaGr.getValue('criteria');
          if (criteria && criteria !== '') {
              accessCondition += criteria;
              profileCondition += this._getProfileQuery(criteria, 'profile');
          }
      }

      //used when querying employee profile table for create records
      this.accessCondition = accessCondition; 
      //used when querying sn_employee_app_access with a profile column name preappended to each condition field for grant
      this.profileCondition = profileCondition;
  },

  /*
   * Adapt an employee profile encoded query to work on a non employee profile table using the @param profileColumn
   * @param {String} encodedQuery - Encoded query to adapt
   * @param {String} profileColumn - employee profile field name to use to adapt encoded query
   * @return (String) Parsed encoded query
   */
  _getProfileQuery: function(encodedQuery, profileColumn) {
      if (!encodedQuery || !profileColumn || profileColumn == 'sys_id')
          return encodedQuery;
      if (encodedQuery.endsWith('^EQ'))
          encodedQuery = encodedQuery.substring(0, encodedQuery.length - 3);

      var queries = encodedQuery.split('^NQ');
      var queriesRet = [];
      for (var i = 0; i < queries.length; i++) {
          var orConditions = queries[i].split('^OR');
          var orConditionsRet = [];
          for (var j = 0; j < orConditions.length; j++) {
              var andConditions = orConditions[j].split('^');
              var andConditionsRet = [];
              for (var k = 0; k < andConditions.length; k++)
                  andConditionsRet.push(profileColumn + '.' + andConditions[k]);
              if (andConditionsRet.length > 0)
                  orConditionsRet.push(andConditionsRet.join('^'));
          }
          if (orConditionsRet.length > 0)
              queriesRet.push(orConditionsRet.join('^OR'));
      }
      return queriesRet.join('^NQ');
  },
  
  /** Return a number value for a given property
   * @param {String} propName - Name of a property
   * @param {Number} defaultValue - The default value to use for a property
   * @return {Number} The number value for a given property
   */
  _getNumberProperty: function(propName, defaultValue) {
      var propValue = Number(gs.getProperty(propName, defaultValue));
      if (isNaN(propValue) || propValue <= 0) {
          gs.warn('Script - ' + this.type + ': Overriding invalid property value for ' + propName);
          propValue = defaultValue;
      }
      return propValue;
  },

  /** 
   *  Function to return an error
   *  @param {String} message - error message print in thrown exception
   *  @returns {Error} The error to throw
   */
  _getError: function(message) {
      var errorMessage = 'Script Include - ' + this.type + ' : ' + message;
      return new Error(errorMessage);
  },

  type: 'ep_AccessManagement'
};

Sys ID

a51652e077911110a956b9999a5a99e4

Offical Documentation

Official Docs: