Name

sn_access_analyzer.AccessAnalyzerUtil

Description

No description available

Script

var AccessAnalyzerUtil = Class.create();
AccessAnalyzerUtil.prototype = {
  sequence: 1,
  scriptExecutionListForOperation: [],
  TRUE_ACL_API_STATUS: "true",
  FALSE_ACL_API_STATUS: "false",
  NOT_EVALUATED_ACL_API_STATUS: "not_evaluated",
  PASS_UI_STATUS: "passed",
  BLOCK_UI_STATUS: "blocked",
  SKIP_UI_STATUS: "skipped",
  UNDEFINED: "undefined",
  initialize: function() {},

  saveRequest: function(requestObj) {
      var requestGR = new GlideRecord("sn_access_analyzer_request");
      requestGR.initialize();
      requestGR.setValue("analyze_by", requestObj.analyze_by);
      requestGR.setValue("user", requestObj.user);
      requestGR.setValue("group", requestObj.group);
      requestGR.setValue("role", requestObj.role);
      requestGR.setValue("resource_type", requestObj.resource_type);
      requestGR.setValue("target_table", requestObj.target_table);
      requestGR.setValue("target_record", requestObj.target_record);
      requestGR.setValue("target_field", requestObj.target_field);
      requestGR.setValue("target_client_callable_script_include", requestObj.target_client_callable_script_include);
      requestGR.setValue("target_rest_endpoint", requestObj.target_rest_endpoint);
      requestGR.setValue("target_rest_endpoint_method", requestObj.target_rest_endpoint_method);
      requestGR.setValue("target_ui_page", requestObj.target_ui_page);
      requestGR.setValue("operations", requestObj.operations);
      requestGR.setValue("short_description", requestObj.short_description);
      requestGR.setValue("last_run", new GlideDateTime());
      requestGR.insert();
      return requestGR;
  },

  saveResult: function(resp, accessRequestGR) {
      var apiResponse = JSON.parse(resp);
      var operations = apiResponse.request.targetOperations;
      var executionTime = accessRequestGR.last_run;
      var accessRequestId = accessRequestGR.sys_id.toString();
      for (var i = 0; i < operations.length; i++) {
          this.sequence = 1; //reset for every operation
          var operation = operations[i].toLowerCase();
          var resultId = this._saveAccessResult(accessRequestGR, apiResponse, operation, executionTime);
          this.scriptExecutionListForOperation = [];
          this._saveAllAccessHandlerDebugLogs(apiResponse.securityDebug, operation, resultId, accessRequestId);
          this._saveAllBRDebugLogs(apiResponse.securityDebug, operation, resultId, accessRequestId);
          this._saveAllDFDebugLogs(apiResponse.securityDebug, operation, resultId, accessRequestId);
          this._saveAllACLDebugLogs(apiResponse.securityDebug, operation, resultId, accessRequestId);
          this._addScriptExecutionListToResult(resultId);
      }
  },

  _getAdminOverrideApplied: function(apiResponse, operationDebug, accessRequestGR, operation) {
      var prop = gs.getProperty('glide.security.admin.override.accessterm');

      if (prop === 'true' || !operationDebug || !operationDebug.debugLogs)
          return "n/a";

      if (accessRequestGR.resource_type.toString() !== "record" || !apiResponse.request.targetRecord)
          return "n/a";

      var table = apiResponse.request.targetName;
      var field = apiResponse.request.targetField;

      var path = "record/" + table + (field ? "." + field : "") + "/" + operation;
      for (var i = 0; i < operationDebug.debugLogs.length; i++) {
          var debug = operationDebug.debugLogs[i];
          if (debug && debug.path && debug.path === path)
              return debug.isCumulativeOverride ? "yes" : "no";
      }
      return "n/a";
  },

  _saveAccessResult: function(accessRequestGR, apiResponse, operation, executionTime) {
      var accessResultGR = new GlideRecord("sn_access_analyzer_access_result");
      accessResultGR.initialize();
      accessResultGR.setValue("access_analyzer_request", accessRequestGR.sys_id.toString());
      accessResultGR.setValue("execution_time", executionTime);
      accessResultGR.setValue("analyzed_by", accessRequestGR.analyzed_by);
      accessResultGR.setValue("operation", operation);
      accessResultGR.setValue("admin_override_applied", this._getAdminOverrideApplied(apiResponse, apiResponse.securityDebug[operation], accessRequestGR, operation));
      this._populateAccessInfo(accessRequestGR, apiResponse, operation, accessResultGR);
      return accessResultGR.insert();
  },

  // populates status for access handler, data filtration and acl
  _populateAccessInfo: function(accessRequestGR, apiResponse, operation, accessResultGR) {
      var accessInfo = apiResponse.securityDebug[operation];
      accessResultGR.setValue("overall_access", accessInfo.status ? this.PASS_UI_STATUS : this.BLOCK_UI_STATUS);

      if (accessRequestGR.resource_type.toString() === "record" && !gs.nil(accessRequestGR.target_field.toString())) {
          var readOnlyField = apiResponse.detail.readOnlyProperty === "true";
          accessResultGR.setValue("read_only_field", readOnlyField ? "yes" : "no");

          if (operation === "write" && readOnlyField) {
              this._populateResultsForReadOnlyField(accessRequestGR.target_field.toString(),
                  operation, accessResultGR);
              return;
          }

          if (operation === "create" && readOnlyField) {
              accessResultGR.setValue("insights", "The field " + accessRequestGR.target_field.toString() +
                  " is read only");
          }
      }

      var debugAclsPresent = accessInfo.debugLogs.some(function(debug) {
          return debug.acls && debug.acls.length > 0;
      });
      if (accessInfo.logType === "AccessHandlerInfo") {
          if (this._isDFLogPresent(accessInfo)) {
              accessResultGR.setValue("accesshandler_result", this.SKIP_UI_STATUS);
              accessResultGR.setValue("datafiltration_result", accessInfo.status ? this.PASS_UI_STATUS :
                  this.BLOCK_UI_STATUS);
          } else {
              if (accessInfo.status) {
                  accessResultGR.setValue("accesshandler_result", this.PASS_UI_STATUS);
              } else {
                  //do not populate if insights is already set (for readOnly field)
                  if (accessResultGR.getValue("insights") === null)
                      accessResultGR.setValue("insights", this._findFirstBlockingAccessHandler(accessInfo) + " is blocking access");
                  accessResultGR.setValue("accesshandler_result", this.BLOCK_UI_STATUS);
              }
          }
      } else {
          if (!debugAclsPresent)
              accessResultGR.setValue("acl_result", this.UNDEFINED);
          accessResultGR.setValue("accesshandler_result", this.SKIP_UI_STATUS);
          if (this._isDFLogPresent(accessInfo)) {
              //if df log is present, the result should be passed in this case
              accessResultGR.setValue("datafiltration_result", this.PASS_UI_STATUS);
          } else {
              // if no df log, then df not available
              accessResultGR.setValue("datafiltration_result", this.SKIP_UI_STATUS);
          }
      }

      if (accessInfo.logType === "SecurityRulesInfo" && debugAclsPresent) {
          accessResultGR.setValue("acl_result", accessInfo.status ? this.PASS_UI_STATUS : this.BLOCK_UI_STATUS);
      }

  },

  _populateResultsForReadOnlyField: function(targetField, operation, accessResultGR) {
      accessResultGR.setValue("insights", "The field " + targetField + " is read only. " +
          operation + " operation is not allowed for read only fields");
      accessResultGR.setValue("accesshandler_result", this.SKIP_UI_STATUS);
      accessResultGR.setValue("datafiltration_result", this.SKIP_UI_STATUS);
      accessResultGR.setValue("acl_result", this.SKIP_UI_STATUS);
  },


  _findFirstBlockingAccessHandler: function(accessInfo) {
      var debugLogs = accessInfo.debugLogs;
      for (var i = 0; i < debugLogs.length; i++) {
          var debugLog = debugLogs[i];
          if (debugLog.logType === "AccessHandlerInfo" && !debugLog.result) {
              return debugLog.accessHandler;
          }
      }
  },

  _isDFLogPresent: function(accessInfo) {
      var debugLogs = accessInfo.debugLogs;
      for (var i = 0; i < debugLogs.length; i++) {
          var debugLog = debugLogs[i];
          if (debugLog.logType === "DataFiltrationInfo") {
              return true;
          }
      }
      return false;
  },

  _saveAllBRDebugLogs: function(securityDebugInfo, operation, accessResultRef, accessRequestId) {
      var accessInfo = securityDebugInfo[operation];
      var debugLogs = accessInfo.debugLogs;
      if (!debugLogs) {
          gs.info("_saveAllBRDebugLogs: No debug log found for operation " + operation +
              " in accessRequestID: " + accessRequestId);
          return;
      }
      for (var i = 0; i < debugLogs.length; i++) {
          var debugLog = debugLogs[i];
          if (debugLog.logType !== "BusinessRuleInfo") {
              continue;
          }
          this._saveBRDebugLog(debugLog, accessResultRef, accessRequestId);
      }
  },

  _saveBRDebugLog: function(dfLog, accessResultRef, accessRequestId) {
      var brDebugLogGR = new GlideRecord("sn_access_analyzer_debug_log");
      brDebugLogGR.initialize();
      brDebugLogGR.setValue("access_analyzer_request", accessRequestId);
      brDebugLogGR.setValue("access_result", accessResultRef);
      brDebugLogGR.setValue("log_table_name", "sys_script");
      brDebugLogGR.setValue("record_ref", dfLog.sysId);
      brDebugLogGR.setValue("type", "Business Rule");
      brDebugLogGR.log_time.setDisplayValue(dfLog.time);
      brDebugLogGR.setValue("log_sequence", this.sequence++);
      brDebugLogGR.insert();
  },

  _saveAllDFDebugLogs: function(securityDebugInfo, operation, accessResultRef, accessRequestId) {
      var accessInfo = securityDebugInfo[operation];
      var debugLogs = accessInfo.debugLogs;
      if (!accessInfo.debugLogs) {
          return;
      }
      for (var i = 0; i < debugLogs.length; i++) {
          var debugLog = debugLogs[i];
          if (debugLog.logType !== "DataFiltrationInfo") {
              continue;
          }
          this._saveDFDebugLog(debugLog, accessResultRef, accessRequestId);
      }
  },

  _saveDFDebugLog: function(dfLog, accessResultRef, accessRequestId) {
      var dfDebugLogGR = new GlideRecord("sn_access_analyzer_debug_log");
      dfDebugLogGR.initialize();
      dfDebugLogGR.setValue("access_analyzer_request", accessRequestId);
      dfDebugLogGR.setValue("access_result", accessResultRef);
      dfDebugLogGR.setValue("log_table_name", "sys_df_data_filtration");
      dfDebugLogGR.setValue("record_ref", dfLog.ref.split("=")[1]);
      dfDebugLogGR.setValue("path", dfLog.path);
      dfDebugLogGR.setValue("type", "Data Filtration");
      dfDebugLogGR.log_time.setDisplayValue(dfLog.time);
      dfDebugLogGR.setValue("log_sequence", this.sequence++);
      if (dfLog.status === true) {
          dfDebugLogGR.setValue("status", this.PASS_UI_STATUS);
      } else {
          dfDebugLogGR.setValue("status", this.BLOCK_UI_STATUS);
      }
      dfDebugLogGR.insert();
  },

  _saveAllACLDebugLogs: function(securityDebugInfo, operation, accessResultRef, accessRequestId) {
      var accessInfo = securityDebugInfo[operation];
      if (!accessInfo.debugLogs) {
          return;
      }
      var debugLogs = accessInfo.debugLogs;
      var scriptExecutedInACLs = false;
      for (var i = 0; i < debugLogs.length; i++) {
          var debugLog = debugLogs[i];
          var acls = debugLog.acls;
          if (debugLog.logType !== "SecurityRulesInfo" || !acls) {
              continue;
          }
          for (var j = 0; j < acls.length; j++) {
              var debugLogId = this._saveACLDebugLog(acls[j], debugLog.time, accessResultRef, accessRequestId);
              if (!scriptExecutedInACLs && this._debugLogHasScriptExecution(debugLogId))
                  scriptExecutedInACLs = true;
          }
      }

      if (scriptExecutedInACLs)
          this.scriptExecutionListForOperation.push("ACL");
  },

  _saveACLDebugLog: function(acl, logTime, accessResultRef, accessRequestId) {
      var aclDebugLogGR = new GlideRecord("sn_access_analyzer_debug_log");
      var scriptExecutionListForACL = [];

      aclDebugLogGR.initialize();
      aclDebugLogGR.setValue("access_analyzer_request", accessRequestId);
      aclDebugLogGR.setValue("log_table_name", "sys_security_acl");
      aclDebugLogGR.setValue("record_ref", acl.sysId);
      aclDebugLogGR.setValue("access_result", accessResultRef);
      aclDebugLogGR.setValue("roles_result", this._getUIResultString(acl.rolesResult));
      aclDebugLogGR.setValue("roles", acl.roles.join(", "));
      aclDebugLogGR.setValue("condition_result", this._getUIResultString(acl.conditionResult));
      aclDebugLogGR.setValue("is_acl_customized", this._isAclCustomized(acl.sysId));

      aclDebugLogGR.setValue("script_result", this._getUIResultString(acl.scriptResult));
      if (this._isScriptedACL(acl.sysId) && (acl.scriptResult.toLowerCase() === this.TRUE_ACL_API_STATUS ||
              acl.scriptResult.toLowerCase() === this.FALSE_ACL_API_STATUS)) {
          scriptExecutionListForACL.push("Script");

      }

      aclDebugLogGR.setValue("path", acl.path);
      aclDebugLogGR.setValue("applies_to", this._getEvaluationLevel(acl.path));
      aclDebugLogGR.setValue("type", "ACL");
      aclDebugLogGR.setValue("app", acl.app);
      aclDebugLogGR.log_time.setDisplayValue(logTime);
      aclDebugLogGR.setValue("status", this._getCumulativeACLStatus(acl.rolesResult, acl.conditionResult,
          acl.scriptResult, acl.sysId, acl.securityAttributeResult));
      aclDebugLogGR.setValue("log_sequence", this.sequence++);
      aclDebugLogGR.setValue("security_attribute", this._getUIResultString(acl.securityAttributeResult));

      if (scriptExecutionListForACL.length > 0)
          aclDebugLogGR.setValue("script_execution", scriptExecutionListForACL.join(','));

      return aclDebugLogGR.insert();
  },

  _saveAllAccessHandlerDebugLogs: function(securityDebugInfo, operation, accessResultRef, accessRequestId) {
      var accessInfo = securityDebugInfo[operation];
      var debugLogs = accessInfo.debugLogs;
      if (!debugLogs) {
          gs.info("_saveAllAccessHandlerDebugLogs: No debug log found for operation " + operation +
              " in accessRequestID: " + accessRequestId);
          return;
      }
      for (var i = 0; i < debugLogs.length; i++) {
          var debugLog = debugLogs[i];
          if (debugLog.logType !== "AccessHandlerInfo" ||
              (debugLog.logType == "AccessHandlerInfo" && debugLog.accessHandler === "DataFiltrationAccessHandler")) {
              continue;
          }
          this._saveAccessHandlerDebugLog(debugLog, accessResultRef, accessRequestId);
      }
  },

  _saveAccessHandlerDebugLog: function(debugLog, accessResultRef, accessRequestId) {
      var accessHandlerDebugLogGR = new GlideRecord("sn_access_analyzer_debug_log");
      accessHandlerDebugLogGR.initialize();
      accessHandlerDebugLogGR.setValue("access_analyzer_request", accessRequestId);
      accessHandlerDebugLogGR.setValue("access_result", accessResultRef);
      accessHandlerDebugLogGR.setValue("path", debugLog.path);
      accessHandlerDebugLogGR.setValue("type", "Access Handler");
      accessHandlerDebugLogGR.setValue("applies_to", debugLog.accessHandler);
      accessHandlerDebugLogGR.log_time.setDisplayValue(debugLog.time);
      if (debugLog.result === true) {
          accessHandlerDebugLogGR.setValue("status", this.PASS_UI_STATUS);
      } else {
          accessHandlerDebugLogGR.setValue("status", this.BLOCK_UI_STATUS);
      }
      accessHandlerDebugLogGR.setValue("log_sequence", this.sequence++);
      accessHandlerDebugLogGR.insert();
  },

  _getEvaluationLevel: function(path) {
      var aclName = path.split("/")[1];
      if (aclName.includes('.'))
          return "Field";
      else
          return "Table";
  },

  _getUIResultString: function(result) {
      if (!result)
          return "";
      var resultLC = result.toLowerCase();
      if (resultLC === this.TRUE_ACL_API_STATUS) {
          return this.PASS_UI_STATUS;
      } else if (resultLC === this.FALSE_ACL_API_STATUS) {
          return this.BLOCK_UI_STATUS;
      } else if (resultLC === this.NOT_EVALUATED_ACL_API_STATUS) {
          return this.SKIP_UI_STATUS;
      }
      return "";
  },

  _getCumulativeACLStatus: function(role, condition, script, aclSysId, securityAttr) {
      var roleStatus = role.toLowerCase();
      var condStatus = condition.toLowerCase();
      var scriptStatus = script.toLowerCase();
      var securityAttrStatus = securityAttr ? securityAttr.toLowerCase() : "";

      if (roleStatus === this.TRUE_ACL_API_STATUS && condStatus === this.TRUE_ACL_API_STATUS &&
          scriptStatus === this.TRUE_ACL_API_STATUS && securityAttrStatus === this.TRUE_ACL_API_STATUS) {
          return this.PASS_UI_STATUS;
      }
      // For table level access check, condition and script will always be not evaluated
      // cumulative status should be based on role & security attribute status in this case
      if (roleStatus === this.TRUE_ACL_API_STATUS && securityAttrStatus !== this.FALSE_ACL_API_STATUS &&
          condStatus === this.NOT_EVALUATED_ACL_API_STATUS && scriptStatus === this.NOT_EVALUATED_ACL_API_STATUS) {
          return this.PASS_UI_STATUS;
      }
      if (roleStatus === this.NOT_EVALUATED_ACL_API_STATUS && condStatus === this.NOT_EVALUATED_ACL_API_STATUS &&
          scriptStatus === this.NOT_EVALUATED_ACL_API_STATUS && securityAttrStatus === this.NOT_EVALUATED_ACL_API_STATUS) {
          return this.SKIP_UI_STATUS;
      }
      return this.BLOCK_UI_STATUS;
  },

  // To determine an ACL with aclId has customisations from Customer. Below customisations are identified:
  // 1) Check for the changes on ACL fields 2) Addition/Modification of ACL roles 3) Removal of any ACL role
  _isAclCustomized: function(aclId) {
      if (gs.getProperty("glide.identity.aclanalyzer.retrieve_acl_customization_info", "false").toString() !== "true")
          return "";

      // Check for the changes on ACL fields
      var gr = this._queryUpdateXml("sys_security_acl_" + aclId);
      if (gr.hasNext()) {
          return true;
      }

      var secAttrId = this._getAclSecAttribute(aclId);
      if (secAttrId) {
          gr = this._queryUpdateXml("sys_security_attribute_" + secAttrId);
          if (gr.hasNext()) {
              return true;
          }
      }

      // Addition/Modification of ACL roles
      var rolesIds = this._getAclRolesByAclId(aclId);
      gr = this._queryUpdateXml(rolesIds);
      if (gr.hasNext()) {
          return true;
      }

      // check for ACL role deletions
      gr = new GlideRecord("sys_metadata_delete");
      gr.addQuery("sys_parent", aclId);
      gr.query();

      return gr.hasNext();
  },

  _queryUpdateXml: function(name) {
      var gr = new GlideRecord("sys_update_xml");
      gr.addQuery("name", name);
      gr.addQuery("category", "customer");
      gr.query();
      return gr;
  },

  _getAclSecAttribute: function(aclId) {
      var gr = new GlideRecord("sys_security_acl");
      gr.addQuery("sys_id", aclId);
      gr.query();
      if (gr.next()) {
          return gr.getValue("security_attribute");
      }
      return "";
  },

  // Method to retrieve list of acl role Ids from the Acl Sys_id
  _getAclRolesByAclId: function(aclId) {
      var aclRole = new GlideRecord("sys_security_acl_role");
      aclRole.addQuery("sys_security_acl", aclId);
      aclRole.query();

      var rolesIds = [];
      while (aclRole.next()) {
          rolesIds.push("sys_security_acl_role_" + aclRole.sys_id.toString());
      }
      return rolesIds;
  },

  //Method to find out whether an ACL actually has advance = true and a non-empty Script
  _isScriptedACL: function(aclId) {
      var gr = new GlideRecord("sys_security_acl");
      gr.addQuery("sys_id", aclId);
      gr.query();

      if (gr.next()) {
          return !gs.nil(gr.getValue("script"));
      }
      return false;
  },

  _addScriptExecutionListToResult: function(resultId) {
      if (this.scriptExecutionListForOperation.length != 0) {
          var resultGr = new GlideRecord("sn_access_analyzer_access_result");
          resultGr.addQuery("sys_id", resultId);
          resultGr.query();
          if (resultGr.next())
              resultGr.setValue("script_execution", this.scriptExecutionListForOperation.join(','));
          resultGr.update();
      }
  },

  _debugLogHasScriptExecution: function(debugLogId) {
      var debugLogGr = new GlideRecord("sn_access_analyzer_debug_log");
      debugLogGr.addQuery("sys_id", debugLogId);
      debugLogGr.query();
      if (debugLogGr.next()) {
          var scriptExec = debugLogGr.getValue("script_execution");
          return (scriptExec == null || scriptExec == "") ? false : true;
      }
      return false;
  },

  type: 'AccessAnalyzerUtil'
};

Sys ID

e171277777971110638cfe21fe5a994d

Offical Documentation

Official Docs: