Name

sn_entitlement.RoleAnalyzerService

Description

No description available

Script

var RoleAnalyzerService = Class.create();
RoleAnalyzerService.prototype = {
  // synced to 0.15.3.0a from Adams role analyzer script

  APP_FAMILY_TIMECARD: gs.getProperty('sn_submgmt_roles.apps.timecard', 'time_card_management'),

  ACL_SCOPE_LEVEL: ['table', 'field'],

  ACL_USER_RESTRICTED: 'ur',    

  initialize: function (roleInfoService, tableInfoService, aclInfoService, appFamilyInfoService,
                        aclAnalyzer, tableAclClassifierSource, roleTypeInferer) {
      this.appFamilyInfoService = appFamilyInfoService ? appFamilyInfoService : new AppFamilyInfoService();
      this.roleInfoService = roleInfoService ? roleInfoService : new RoleInfoService();
      this.tableInfoService = tableInfoService ? tableInfoService : new TableInfoService(this.appFamilyInfoService);
      this.aclInfoService = aclInfoService ? aclInfoService : new AclInfoService();
      this.aclAnalyzer = aclAnalyzer ? aclAnalyzer : new AclAnalyzer();
      this.tableAclClassifierSource = tableAclClassifierSource ? tableAclClassifierSource : function(aclScopeLevels) { return new TableAclClassifier(aclScopeLevels); };
      this.roleTypeInferer = roleTypeInferer ? roleTypeInferer : new RoleTypeInferer();

      this.IMPORTANT_OPS = ['write', 'read', 'commentWrite']; //this.getImportantACLOperations(); , 'delete'

      this.OUTPUT_SPACER = '';
      this.verbosity = {};

      this.arrayUtil = new global.ArrayUtil();
  },

  /**
   * Add a verbosity level for output
   * detail - in each section header more information than just id
   * table - add table level information
   * count - return acl counts
   * global_scope - return information about global scope
   * @param {string} name one of [detail, table, count]
   */
  addVerbosity: function(name) {
      this.verbosity[name] = name;
  },

  /**
   * analyzeRoles
   * Given array of role names, analyze the system and return information about what licensing role type
   * should be associated with the role with breakdown by table, application and scope. Roles which are
   * contained within are also included in the analysis and output.
   * If the optional flag aclRolesOnly is passed, limit roles to those used in ACLs
   * @param {array} roleNames names of roles
   * @param {array} tableNames names of tables
   * @param {boolean} includeNoRoleACLs
   * @param {boolean} aclRolesOnly
   * @returns {object} the least verbose version returns
   * app_detail -> "role" -> app_id -> detectedType,
   * role_detail -> "role" -> acl information, and summary statistics about ACLs.
   * The app_id will be one of package name from license_family or scope id or 'platform' and be used to
   * filter out applications which are not relevant.
   */
  analyzeRoles: function(roleNames, aclRolesOnly) {
      var output = {
          'app_detail': {},
          'role_detail': {}
      };
      var statsList = [];
      var filterAclOps = aclRolesOnly ? this.IMPORTANT_OPS : null;

      var roles = this.roleInfoService.getRolesByName(roleNames, filterAclOps);

      var analysisResult;
      for (var r in roles) {
          var role = roles[r];
          analysisResult = this._analyzeRoleById(role.id);

          this._addResult(output, role.id, role.name, analysisResult);

          statsList.push(analysisResult.acls);
      }

      output.summary_stats = this._computeSummaryStats(roles, statsList);

      return output;
  },

  /**
   * Obtain custom roles, return information about what licensing role type should be associated with the role with breakdown
   * by table, application and scope. Roles which are contained within are also included in the analysis and output.
   * @returns {object} the least verbose version returns
   * app_detail -> "role" -> app_id -> detectedType,
   * role_detail -> "role" -> acl information, and summary statistics about ACLs.
   * The app_id will be one of package name from license_family or scope id or 'platform' and be used to
   * filter out applications which are not relevant.
   */
  analyzeCustomRoles: function() {
      var output = {
          'app_detail': {},
          'role_detail': {}
      };

      var statsList = [];

      var roles = this.roleInfoService.getCustomRoles();

      for (var r in roles) {
          var role = roles[r];
          var analysisResult = this._analyzeRoleById(role.id);
          this._addResult(output, role.id, role.name, analysisResult);
          statsList.push(analysisResult.acls);
      }

      output.stats = this._computeSummaryStats(roles, statsList);

      return output;
  },

  _addResult: function(output, roleId, roleName, analysisResult) {
      output.app_detail[roleName] = analysisResult.appDetail;
      // any scope details
      if (Object.keys(analysisResult.scopeDetail).length > 0) {
          if (!output.scope_detail)
              output.scope_detail = {};
          output.scope_detail[roleName] = analysisResult.scopeDetail;
      }
      if (this.verbosity['table']) {
          if (!output.table_detail)
              output.table_detail = {};
          output.table_detail[roleName] = analysisResult.tableDetail;
      }

      if (this.verbosity['detail'] && roleId)
          output.role_detail[roleName] = this.roleInfoService.getRoleInfo(roleId);
      else
          output.role_detail[roleName] = {};
      output.role_detail[roleName].acls = analysisResult.acls;
  },

  _getFieldType: function (tableName, fieldName) {
      var table = new GlideRecord(tableName);
      if (tableName == '*') {
          return false;
      }
      if (!table.isValid()) {
          gs.warn('ACL on invalid table: ' + tableName);
          return false;
      }
      if (!table.isValidField(fieldName)) {
          gs.warn('ACL on invalid field: ' + tableName + '.' + fieldName);
          return false;
      }
      var field = table.getElement(fieldName);
      var fieldEd = field.getED();
      return fieldEd.getInternalType();
  },

  _initACLCounts: function () {
      return {
          'total': 0,
          'write': 0,
          'read': 0,
          'commentWrite': 0
      }; // , 'skip': {'write': 0}, 'delete': 0 // removed delete form important ops;
  },


  _getDetectedTypePerApp: function (appId, tableDetail) {
      var detectedType = this.roleTypeInferer.getDefaultDetectedType();
      for (var t in tableDetail) {
          if (appId == tableDetail[t].tableInfo.subscriptionAppId) {
              //gs.info(JSON.stringify(['getAppPredictedType', t, appId, tableDetail[t].tableInfo.subscriptionAppId, tableDetail[t].detectedType.roleType]));
              detectedType = this.roleTypeInferer.mergeDetectedTypes(detectedType, tableDetail[t].detectedType);
          }
      }
      // manual override Time Card requestors to Time Card Users
      if (appId == this.APP_FAMILY_TIMECARD)
          detectedType = this.roleTypeInferer.fixTimeCardDetectedType(detectedType);

      return detectedType;
  },

  _getDetectedTypePerScope: function (scopeId, tableDetail) {
      var detectedType = this.roleTypeInferer.getDefaultDetectedType();
      for (var t in tableDetail) {
          if (scopeId == tableDetail[t].tableInfo.scope) {
              //gs.info(JSON.stringify(['getScopePredictedType', t, scopeId, tableDetail[t].tableInfo.scope, tableDetail[t].roleType]));
              detectedType = this.roleTypeInferer.mergeDetectedTypes(detectedType, tableDetail[t].detectedType);
          }
      }

      return detectedType;
  },

  _analyzeRoleById: function (roleId) {
      var ignoredTables = this.tableInfoService.getIgnoredTables();
      var aclRecordIterator = this.aclInfoService.getAllByRole(roleId, this.IMPORTANT_OPS, ignoredTables);
      return this._analyzeRole(aclRecordIterator, this.IMPORTANT_OPS, ignoredTables);
  },

  _analyzeNonRoleAcls: function () {
      var customTables = this.tableInfoService.getCustomTableList();
      var ignoredTables = this.tableInfoService.getIgnoredTables();
      var aclRecordIterator = this.aclInfoService.getNonRoleAclsByTables(customTables, this.IMPORTANT_OPS, ignoredTables);
      return this._analyzeRole(aclRecordIterator, this.IMPORTANT_OPS, ignoredTables);
  },

  _analyzeRole: function (aclRecords, filterAclOps, ignoredTables) {
      var tableDetail = {};
      var appDetail = {};
      var scopeDetail = {};

      var aclStats = this._processAcls(aclRecords, tableDetail);

      this._decorateReadAcls(tableDetail, filterAclOps, ignoredTables);

      var aclCountClassifierPerApp = {};
      var aclCountClassifierPerScope = {};

      this._processTables(tableDetail, appDetail, scopeDetail, aclCountClassifierPerApp, aclCountClassifierPerScope);
      this._processApps(tableDetail, appDetail, aclCountClassifierPerApp, aclCountClassifierPerScope);
      this._processScopes(tableDetail, scopeDetail, aclCountClassifierPerScope);

      // clean up table detail if not needed
      if (this.verbosity['table']) {
          if (!this.verbosity['detail'] || !this.verbosity['count']) {
              for (var t in tableDetail) {
                  if (!this.verbosity['detail']) {
                      delete tableDetail[t].tableInfo;
                      delete tableDetail[t].tableType;
                  }
                  if (!this.verbosity['count'])
                      delete tableDetail[t].acls;
              }
          }
      }

      var output = {
          'acls' : aclStats,
          'appDetail': appDetail,
          'scopeDetail': scopeDetail
      };
      if (this.verbosity['table'])
          output.tableDetail = tableDetail;

      return output;
  },

  _processAcls: function(aclRecordIterator, tableDetail) {
      var stats = {
          'count': 0,
          'list': {
              'table': [],
              'field': []
          }
      };

      while (aclRecordIterator.next()) {
          stats.count++;

          // acl operation can change, keep local copy
          var aclOp = aclRecordIterator.getOperation();

          var aclScopeLevel;
          var aclName = aclRecordIterator.getName();
          var tableName = aclName;
          if (tableName.contains('.')) {
              tableName = tableName.split('.')[0];
              aclScopeLevel = 'field';
          } else {
              aclScopeLevel = 'table';
          }

          // check if we handle table
          if (!this._ensureTableDetail(tableDetail, tableName))
              continue;

          if (aclName.contains('.')) {
              var aclParts = aclName.split('.');
              if (aclParts[1] != '*') {
                  var fieldType = this._getFieldType(aclParts[0], aclParts[1]);
                  if (fieldType == 'journal_input') {
                      aclOp = 'commentWrite';
                      // skip any field level journaled field writes
                      //gs.debug('Field level: ' + aclRecord.name + ' type: ' + fieldType);
                      //continue;
                  }
              }

          }

          stats.list[aclScopeLevel].push(aclRecordIterator.getId());

          var aclType = this.aclAnalyzer.getACLType(aclRecordIterator);
          this._addTableAclCount(tableDetail[tableName].acls, aclScopeLevel, aclType, aclOp);

          var aclAttributes = this.aclAnalyzer.getACLScriptAttributes(aclRecordIterator);
          this._mergeAclAttributes(aclAttributes, tableDetail[tableName].acls.attr);
      }

      return stats;
  },

  /**
   * For tables where there's no read access, try to walk up hierarchy and check
   */
  _decorateReadAcls: function(tableDetail, filterAclOps, ignoredTables) {
      var checkTableNames = {};
      for (var tableName in tableDetail) {
          // for write ACLs at the field level, don't worry about it if there a user restricted ACL on to restrict reading
          if (this._hasNoReadAcls(tableDetail[tableName].acls)) {
              //gs.info('No read ACLs found for ' + tableName + ' checking parent tables');
              var tableHierarchy = this.tableInfoService.getTableAncestors(tableName);
              for (var t in tableHierarchy)
                  checkTableNames[tableHierarchy[t]] = tableHierarchy[t];
          }
      }

      var tableNames = Object.keys(checkTableNames);
      if (tableNames.length == 0)
          return;

      var newTableDetail = {};
      var aclRecordIterator = this.aclInfoService.getAllAclsByTables(tableNames, filterAclOps, ignoredTables);
      this._processAcls(aclRecordIterator, newTableDetail);

      for (var tt in tableDetail) {
          if (this._hasNoReadAcls(tableDetail[tt].acls)) {
              // check read access in hierarchy
              var addURACL = this._findReadAclsInHierarchy(tt, newTableDetail);
              // add to ACL count because we effectively found one
              if (addURACL)
                  this._addTableAclCount(tableDetail[tt].acls, 'table', this.ACL_USER_RESTRICTED, 'read');
          }
      }
  },

  _processTables: function(tableDetail, appDetail, scopeDetail, aclCountClassifierPerApp, aclCountClassifierPerScope) {
      for (var t in tableDetail) {
          // don't include exempt tables in the roll up or database views
          //if (tableDetail[t].tableInfo.isExempt)
              //continue;

          var appId = tableDetail[t].tableInfo.appId;
          var appName = tableDetail[t].tableInfo.appName;
          var scopeName = tableDetail[t].tableInfo.scope;

          if (!Object.prototype.hasOwnProperty.call(appDetail, appId)) {
              appDetail[appId] = this._createAppDetail(appId, appName, tableDetail[t]);
              if (this.verbosity['count'])
                  aclCountClassifierPerApp[appId] = this.tableAclClassifierSource.call(this, this.ACL_SCOPE_LEVEL);
          }

          if (this.verbosity['detail'])
              appDetail[appId].source.all = this.arrayUtil.union(tableDetail[t].tableInfo.source, appDetail[appId].source.all);


          // need only create scope detail for global scope. Other scopes are captured in appDetail
          if (this.verbosity['global_scope'] && scopeName == 'global') {
              if (!Object.prototype.hasOwnProperty.call(scopeDetail, scopeName)) {
                  scopeDetail[scopeName] = this._createScopeDetail(scopeName, appId);
                  if (this.verbosity['count'])
                      aclCountClassifierPerScope[scopeName] = this.tableAclClassifierSource.call(this, this.ACL_SCOPE_LEVEL);
              }
          }

          var aclTypes = this.aclAnalyzer.getAclTypes();

          var aclCountClassifierPerTable = this.tableAclClassifierSource.call(this, this.ACL_SCOPE_LEVEL);
          aclCountClassifierPerTable.addAll(aclTypes, this.IMPORTANT_OPS, tableDetail[t].tableType, tableDetail[t].acls);

          if (aclCountClassifierPerApp[appId])
              aclCountClassifierPerApp[appId].addAll(aclTypes, this.IMPORTANT_OPS, tableDetail[t].tableType, tableDetail[t].acls);

          if (aclCountClassifierPerScope[scopeName])
              aclCountClassifierPerScope[scopeName].addAll(aclTypes, this.IMPORTANT_OPS, tableDetail[t].tableType, tableDetail[t].acls);

          if (this.verbosity['detail']) {
              var aclsPerTableClass = aclCountClassifierPerTable.getAclsPerClass();
              for (var l in this.ACL_SCOPE_LEVEL) {
                  var asl = this.ACL_SCOPE_LEVEL[l];
                  if (aclsPerTableClass[asl].total > 0) {
                      appDetail[appId].tables[asl].tables.push(t);
                      if (Object.prototype.hasOwnProperty.call(scopeDetail, scopeName))
                          scopeDetail[scopeName].tables[asl].tables.push(t);
                  }
              }
          }

          this._cleanupAclCounts(tableDetail[t].acls);

          tableDetail[t].detectedType = this.roleTypeInferer.getDetectedType(tableDetail[t].tableInfo, aclCountClassifierPerTable.getAclsPerClass());
      }
  },

  _processApps: function(tableDetail, appDetail, aclCountClassifierPerApp) {
      for (var appId in appDetail) {
          if (this.verbosity['count'])
              appDetail[appId].acls = this._cleanupAclCounts(aclCountClassifierPerApp[appId].getAclsPerClass());
          if (this.verbosity['detail']) {
              appDetail[appId].tables.table.tables = this.arrayUtil.unique(appDetail[appId].tables.table.tables);
              appDetail[appId].tables.field.tables = this.arrayUtil.unique(appDetail[appId].tables.field.tables);
          }

          //gs.info('appDetail: ' + JSON.stringify(appId, null, rau.OUTPUT_SPACER));
          var detectedType = this._getDetectedTypePerApp(appId, tableDetail);

          appDetail[appId].detectedType = detectedType;

          //gs.info('appDetail: ' + JSON.stringify(appDetail[appId], null, rau.OUTPUT_SPACER));
      }
  },

  _processScopes: function(tableDetail, scopeDetail, aclCountClassifierPerScope) {
      for (var scopeName in scopeDetail) {
          if (this.verbosity['count'])
              scopeDetail[scopeName].acls = this._cleanupAclCounts(aclCountClassifierPerScope[scopeName].getAclsPerClass());
          if (this.verbosity['detail']) {
              scopeDetail[scopeName].tables.table.tables = this.arrayUtil.unique(scopeDetail[scopeName].tables.table.tables);
              scopeDetail[scopeName].tables.field.tables = this.arrayUtil.unique(scopeDetail[scopeName].tables.field.tables);
          }

          var detectedType = this._getDetectedTypePerScope(scopeName, tableDetail);

          scopeDetail[scopeName].detectedType = detectedType;
      }
  },

  _ensureTableDetail: function(tableDetail, tableName) {
      if (Object.prototype.hasOwnProperty.call(tableDetail, tableName))
          return true;

      var ti = this.tableInfoService.getTableInfo(tableName);
      if (ti == null) {
          // skip non-tables
          // gs.info("Skipping non table " + tableName);
          return false;
      }

      if (ti.isRemoteTable) {
          // skip remote tables
          return false;
      }

      // identify the type of table for the summary
      var tableAclClassifier = this.tableAclClassifierSource.call(this, this.ACL_SCOPE_LEVEL);
      var tableType = tableAclClassifier.getTableType(ti);

      tableDetail[tableName] = {
          'tableInfo': ti,
          'tableType': tableType,
          'detectedType': null,
          'acls': this._createTableAclCounts()
      };
      return true;
  },

  _mergeAclAttributes: function(aclAttributes, attrs) {
      for (var a in aclAttributes) {
          if (aclAttributes[a]) {
              if (Object.prototype.hasOwnProperty.call(attrs, a))
                  attrs[a]++;
              else
                  attrs[a] = 1;
          }
      }
  },

  _hasNoReadAcls: function(acls) {
      return (acls.table.u.read == 0 && acls.table.r.read == 0 && acls.table.a.read == 0 && acls.table.ur.read == 0);
  },

  _findReadAclsInHierarchy: function(tableName, newTableDetail) {
      var foundACL = false;
      var tableHierarchy = this.tableInfoService.getTableAncestors(tableName);
      for (var t in tableHierarchy) {
          // table has matching user restircted ACL for this role
          if (newTableDetail[tableHierarchy[t]].acls.table.ur.read > 0) {
              //gs.info('Found a user restricted ACL on ' + tableHierarchy[t] + ' when looking for one on ' + tableName);
              //gs.info(roleName + ' on ' + aclRecord.name + ' -- Has personalized read ACL was found for the parent table (' + tableName + ')');
              foundACL = true;
              break;
          }
          // table has ANY read ACLs, we stop looking for others
          if (newTableDetail[tableHierarchy[t]].acls.table.u.read > 0 ||
              newTableDetail[tableHierarchy[t]].acls.table.r.read > 0 ||
              newTableDetail[tableHierarchy[t]].acls.table.a.read > 0) {
              //gs.info('Read ACLs found on ' + tableHierarchy[t] + ', stop looking at ' + tableName + ' base tables');
              break;
          }
      }

      //if (!foundACL)
          //gs.info('No matching personalized read ACL was found for the table so recording the ACL for: ' + roleName + ' on ' + aclRecord.name);

      return foundACL;
  },

  _cleanupAclCounts: function(acls) {
      for (var l in this.ACL_SCOPE_LEVEL) {
          var asl = this.ACL_SCOPE_LEVEL[l];
          if (acls[asl].total == 0) {
              delete acls[asl];
              continue;
          }
          for (var aclType in acls[asl]) {
              if (acls[asl][aclType].total == 0)
                  delete acls[asl][aclType];
              //else {
              //    for (var o in this.IMPORTANT_OPS) {
              //        if (acls[asl][tt][o] == 0)
              //            delete acls[asl][tt][o];
              //    }
              //}
          }
      }
      return acls;
  },

  // return an ACL count structure which looks the same as TableAclClassifier
  // and can be used for common cleanup
  _createTableAclCounts: function() {
      var acls = {
          'table': {},
          'field': {},
          'attr': {}
      }

      var aclTypes = this.aclAnalyzer.getAclTypes();
      for (var l in this.ACL_SCOPE_LEVEL) {
          var asl = this.ACL_SCOPE_LEVEL[l];
          for (var i = 0; i < aclTypes.length; ++i) {
              acls[asl][aclTypes[i]] = this._initACLCounts();
              acls[asl].total = 0;
          }
      }

      return acls;
  },

  _addTableAclCount: function(acls, aclScopeLevel, aclType, aclOp) {
      acls[aclScopeLevel][aclType][aclOp]++;
      acls[aclScopeLevel][aclType].total++;
      acls[aclScopeLevel].total++;
  },

  _createAppDetail: function(appId, appName, tableDetail) {
      var appDetail = {
          'appName': null,
          'appId': null,
          'detectedType': null,
          // optional information
          'scope': null,
          'source': {
              'primary': null
          }
          /*
          'acls': {},
          'tables': {
              'table': {
                  'tables': []
              },
              'field': {
                  'tables': []
              }
          },
          'perUser': null
          */
      };

      appDetail.appName = appName;
      appDetail.scope = tableDetail.tableInfo.scope;
      appDetail.source.primary = tableDetail.tableInfo.source;
      appDetail.appId = tableDetail.tableInfo.appId;

      if (this.verbosity['detail']) {
          appDetail.acls = {};
          appDetail.tables = {
              'table': {
                  'tables': []
              },
              'field': {
                  'tables': []
              }
          };

          // perUser and source all info is not being used for current scenario, hide for now
          // appDetail.perUser = this.appFamilyInfoService.isPerUserApp(appId);
          // if (appDetail.source.all.length) {
          //    appDetail.source.all = this.arrayUtil.union(tableDetail.tableInfo.source, appDetail.source.all);
          // } else {
          //    appDetail.source.all = [tableDetail.tableInfo.source];
          // }
      }

      return appDetail;
  },

  _createScopeDetail: function(scopeName, appId) {
      var scopeDetail = {
          'scopeName' : scopeName,
          'detectedType': null,
          // optional information
          /*
          'acls': {},
          'tables': {
              'table': {
                  'tables': []
              },
              'field': {
                  'tables': []
              }
          },
          'perUser': this.appFamilyInfoService.isPerUserApp(appId),
          */
      };

      if (this.verbosity['detail']) {
          scopeDetail.acls = {};
          scopeDetail.tables = {
              'table': {
                  'tables': []
              },
              'field': {
                  'tables': []
              }
          };
         // scopeDetail.perUser = this.appFamilyInfoService.isPerUserApp(appId);
      }

      return scopeDetail;
  },

  _computeSummaryStats : function(roles, statsList) {
      var stats = {
          'roles': 0,
          'acls': {
              'table': 0,
              'field': 0
          }
      };
      var tableAclsList = [];
      var fieldAclsList = [];
      stats.roles = roles.length;
      for (var i = 0; i < statsList.length; ++i) {
          tableAclsList = this.arrayUtil.union(tableAclsList, statsList[i].list.table);
          fieldAclsList = this.arrayUtil.union(fieldAclsList, statsList[i].list.field);
      }
      stats.acls.table = this.arrayUtil.unique(tableAclsList).length;
      stats.acls.field = this.arrayUtil.unique(fieldAclsList).length;

      return stats;
  },

  type: 'RoleAnalyzerService'
};

Sys ID

d6a8c60e430121102aeb1ca57bb8f273

Offical Documentation

Official Docs: