Name

sn_em_connector.PushConnectorCustomDomainUtil

Description

This script includes utility methods to populate custom domain information for PUSH connector events for instance type push connectors.

Script

var PushConnectorCustomDomainUtil = Class.create();
PushConnectorCustomDomainUtil.prototype = {
  initialize: function() {},

  /**
   *
   * @param {*} connectorSysId - Push Connector sys_id.
   * @param {*} request - HttpRequest for incoming event
   * @param {*} reqBody - Parsed Request body
   * @param {*} event - Event object where domain information is to be populated
   *
   * This method populates sys_domain and sys_domain_path fields in event object using domain metadata properties.
   */
  populateDomain: function(connectorSysId, request, reqBody, event) {

      var customDomainSepEnabled = gs.getProperty('evt_mgmt.connector_enable_custom_domain_separation');

      if (customDomainSepEnabled != null && customDomainSepEnabled == 'true') {

          // Declare metadata variables here to access in catch block
          var domainInfoTableName, domainInfoColumnName, domainRecordFieldValueToMatch, domainIdColumnName, domainPathColumnName;

          try {
  			this._checkCustomDomainHelperAvailability();
  			var domainMetadata = this._readDomainMetadata(connectorSysId, request, reqBody);

  			domainInfoTableName = domainMetadata.domainInfoTableName;
              domainInfoColumnName = domainMetadata.domainInfoColumnName;
              domainIdColumnName = domainMetadata.domainIdColumnName;
              domainPathColumnName = domainMetadata.domainPathColumnName;
              var payloadDomainInfoFieldName = domainMetadata.payloadDomainInfoFieldName;
              var payloadDomainInfoFieldValue = domainMetadata.payloadDomainInfoFieldValue;


  			// Use static value to match record OR read field value from flattened event payload.
  			// Dot-Walking of flattened payload is not yet supported.
  			// The payload field is stored as 'field' or 'flattened.field' in the additional_info
  			var addInfoObj = JSON.parse(event.additional_info);

  			// payloadDomainInfoFieldName takes preference over payloadDomainInfoFieldValue
  			if (payloadDomainInfoFieldName && payloadDomainInfoFieldName !== '') {
  				var flattenedPayloadDomainInfoFieldName = 'flattened.' + payloadDomainInfoFieldName;
  				domainRecordFieldValueToMatch = addInfoObj[payloadDomainInfoFieldName] || addInfoObj[flattenedPayloadDomainInfoFieldName] || '';
  			} else if (payloadDomainInfoFieldValue && payloadDomainInfoFieldValue !== '') {
  				domainRecordFieldValueToMatch = payloadDomainInfoFieldValue;
  			}

  			/*
  			gs.info("CONNECTOR_CUSTOM_DOMAIN:: Domain Info Variables: " + JSON.stringify({
  				"domainInfoTableName": domainInfoTableName,
  				"domainInfoColumnName": domainInfoColumnName,
  				"payloadDomainInfoFieldName": payloadDomainInfoFieldName,
  				"payloadDomainInfoFieldValue": payloadDomainInfoFieldValue,
  				"domainIdColumnName": domainIdColumnName,
  				"domainPathColumnName": domainPathColumnName,
  				"domainRecordFieldValueToMatch": domainRecordFieldValueToMatch
  			}));
  			*/

  			var domainId = '';
  			var domainPath = '';

  			if (domainInfoTableName && domainInfoTableName.trim() !== '' && domainRecordFieldValueToMatch && domainRecordFieldValueToMatch.trim() !== '') {

                  /**
                   * Read value from backend cache - example return value - {"domainPath":"!!!/!!!/","domainID":"09ff3d105f231000b12e3572f2b4775d"}
                   * The helper method will also raise self health events wherever necessary.
                   */
                  var customDomainSepModelString = new global.EvtMgmtEventCustomDomainHelper().getCustomDomainSepModel(domainInfoTableName, domainInfoColumnName, domainRecordFieldValueToMatch, domainIdColumnName, domainPathColumnName);

                  if (customDomainSepModelString && customDomainSepModelString !== '') {
                      var customDomainJson = JSON.parse(customDomainSepModelString);
  					if (customDomainJson)
  					{
  						domainId = customDomainJson.domainID;
                          domainPath = customDomainJson.domainPath;
  					}
                  }

  			} else {
                  throw new Error('domainInfoTableName OR/AND domainRecordFieldValueToMatch could not be resolved');
              }

  			// Check if user has access to resolved domain
  			if (domainId && domainId !== '' && domainPath && domainPath !== '' && this._checkReadAccessOnDomain(domainId)) {
  				event.setValue('sys_domain', domainId);
  				event.setValue('sys_domain_path', domainPath);
  			}

  			//gs.debug("CONNECTOR_CUSTOM_DOMAIN :: Resolved Domain : " + domainId + " : " + domainPath);

  		} catch (err) {

  			gs.debug('CONNECTOR_CUSTOM_DOMAIN :: Error: Unable to set custom domain for event - ' + err);
              /**
               * We will raise health events as part of processing the event in backend. But, we do not want to
               * miss out on any exceptions we get in the parsing and reading stages of domain metadata parsing.
               */
              var msgKey = 'SCRIPT_ERR_' + event.source;
              var cause = gs.getMessage('The push connector script with sys ID {0} had the following error while resolving custom domain information - \n"{1}"', [connectorSysId, err.message]);
              var additionalInfoFields = {};

              // If we have exception after resolving data, add this data to the health event.
              if (domainInfoTableName && domainInfoColumnName && domainRecordFieldValueToMatch) {

                  msgKey += '__' + domainInfoTableName + "__" + domainIdColumnName + "__" + domainRecordFieldValueToMatch;
                  cause += gs.getMessage('\nfor table \'{0}\' with column name \'{1}\' and value \'{2}\'', [domainInfoTableName, domainInfoColumnName, domainRecordFieldValueToMatch]);

                  additionalInfoFields["custom_domain_connectorDomainInfoTableName"] = domainInfoTableName;
                  additionalInfoFields["custom_domain_connectorDomainInfoColumnName"] = domainInfoColumnName;
                  additionalInfoFields["custom_domain_payloadDomainInfoFieldValue"] = domainRecordFieldValueToMatch;
              }

              var remediation = 'Check if domain metadata is valid and that user has access to the specified domain record.';
              this._sendSelfHealthEvent(msgKey, cause, remediation, additionalInfoFields, event.source);
  		}
      }

  },

  /**
   *
   *
   * @returns domainMetadata {domainInfoTableName, domainInfoColumnName, payloadDomainInfoFieldName, payloadDomainInfoFieldValue, domainIdColumnName, domainPathColumnName }
   *
   * This method reads the domain metadata properties form sys_properties table and overrides any properties if passed from the payload request / headers / query parameters.
   * The preference of applying domain metadata properties in DECREASING order is as follows:
   *
   * Push Connector Parameters > URL query Parameters > Request Headers > Request Body > sys_properties defaults
   *
   */
  _readDomainMetadata: function (sysid, request, reqBody) {
      var queryParams = request.queryParams;
      var headers = request.headers;

      // gs.info("CONNECTOR_CUSTOM_DOMAIN:: QUERY: " + JSON.stringify(queryParams));
      // gs.info("CONNECTOR_CUSTOM_DOMAIN:: HEADERS: " + JSON.stringify(headers));

      var defaultDomainMetadata = {
          domainInfoTableName: gs.getProperty('evt_mgmt.connector_domain_info_table_name') || '',
          domainInfoColumnName: gs.getProperty('evt_mgmt.connector_domain_info_column_name') || '',
          payloadDomainInfoFieldName: '',
          payloadDomainInfoFieldValue: '',
          domainIdColumnName: gs.getProperty('evt_mgmt.connector_domain_id_column_name') || '',
          domainPathColumnName: gs.getProperty('evt_mgmt.connector_domain_path_column_name') || ''
      };

  	gs.debug("CONNECTOR_CUSTOM_DOMAIN :: Default Domain Metadata: " + JSON.stringify(defaultDomainMetadata));

      var domainMetadata = {};

      /*
       * props.name - Name of metadata property as it appears in request payload / headers / body
       * props.prop - Variable name to refer in domain metadata object
       */
      var props = [
          {name: 'connectorDomainInfoTableName', prop: 'domainInfoTableName'},
          {name: 'connectorDomainInfoColumnName', prop: 'domainInfoColumnName'},
          {name: 'payloadDomainInfoFieldName', prop: 'payloadDomainInfoFieldName'},
          {name: 'payloadDomainInfoFieldValue', prop: 'payloadDomainInfoFieldValue'},
          {name: 'connectorDomainIdColumnName', prop: 'domainIdColumnName'},
          {name: 'connectorDomainPathColumnName', prop: 'domainPathColumnName'}
      ];

      // Read options from query parameters, headers, and request body
      props.forEach(function(prop) {
          var value = queryParams[prop.name] ? queryParams[prop.name][0] :
                      (headers[prop.name.toLowerCase()] && headers[prop.name.toLowerCase()] !== '' ? headers[prop.name.toLowerCase()] :
                      (reqBody[prop.name] && reqBody[prop.name] !== '' ? reqBody[prop.name] :
                      defaultDomainMetadata[prop.prop]));
          domainMetadata[prop.prop] = value;
      });

      this._readFromConnectorParam(sysid, domainMetadata);

      return domainMetadata;
  },

  _readFromConnectorParam: function(sysid, domainMetadata) {
      var configurations = new sn_em_connector.PushConnectorConfigurations();
  	
  	var connectorParam_payloadDomainInfoFieldName = configurations.getConfigurationValue(sysid, connector_sys_id, 'payloadDomainInfoFieldName');
  	var connectorParam_payloadDomainInfoFieldValue = configurations.getConfigurationValue(sysid, connector_sys_id, 'payloadDomainInfoFieldValue');
  	var connectorParam_connectorDomainInfoTableName = configurations.getConfigurationValue(sysid, connector_sys_id, 'connectorDomainInfoTableName');
  	var connectorParam_connectorDomainInfoColumnName = configurations.getConfigurationValue(sysid, connector_sys_id, 'connectorDomainInfoColumnName');
  	var connectorParam_connectorDomainIdColumnName = configurations.getConfigurationValue(sysid, connector_sys_id, 'connectorDomainIdColumnName');
  	var connectorParam_connectorDomainPathColumnName = configurations.getConfigurationValue(sysid, connector_sys_id, 'connectorDomainPathColumnName');
  	
  	if (connectorParam_payloadDomainInfoFieldName && connectorParam_payloadDomainInfoFieldName !== '') {
  		domainMetadata.payloadDomainInfoFieldName = connectorParam_payloadDomainInfoFieldName;
  	}

  	if (connectorParam_payloadDomainInfoFieldValue && connectorParam_payloadDomainInfoFieldValue !== '') {
  		domainMetadata.payloadDomainInfoFieldValue = connectorParam_payloadDomainInfoFieldValue;
  	}

  	if (connectorParam_connectorDomainInfoTableName && connectorParam_connectorDomainInfoTableName !== '') {
  		domainMetadata.domainInfoTableName = connectorParam_connectorDomainInfoTableName;
  	}

  	if (connectorParam_connectorDomainInfoColumnName && connectorParam_connectorDomainInfoColumnName !== '') {
  		domainMetadata.domainInfoColumnName = connectorParam_connectorDomainInfoColumnName;
  	}

  	if (connectorParam_connectorDomainIdColumnName && connectorParam_connectorDomainIdColumnName !== '') {
  		domainMetadata.domainIdColumnName = connectorParam_connectorDomainIdColumnName;
  	}

  	if (connectorParam_connectorDomainPathColumnName && connectorParam_connectorDomainPathColumnName !== '') {
  		domainMetadata.domainPathColumnName = connectorParam_connectorDomainPathColumnName;
  	}
  },

  _checkReadAccessOnDomain: function(domainId) {
      this._checkCustomDomainHelperAvailability();
      var hasAccess = new global.EvtMgmtEventCustomDomainHelper().userHasDomainAccess(domainId);
      return hasAccess === true;
  },

  _sendSelfHealthEvent: function(msgKey, cause, remediation, additionalInfo, scriptSource) {
      
      var HEALTH_EVENT_SOURCE = 'push_connector_script_' + scriptSource;

  	try {
  		this._checkCustomDomainHelperAvailability();

          var additionalInfoJsonStr;

          try {
              additionalInfoJsonStr = JSON.stringify(additionalInfo);
          } catch (err) {
              gs.debug('CONNECTOR_CUSTOM_DOMAIN :: Error: Unable to process additional info JSON for health event - ' + err);
          }

          // Send health event
  		new global.EvtMgmtEventCustomDomainHelper().sendSelfMonitoringEvent(msgKey, cause, remediation, HEALTH_EVENT_SOURCE, additionalInfoJsonStr);

  	} catch (e) {
  		/**
  		 * EvtMgmtEventCustomDomainHelper will not be available to raise self health event if we are running an unsupported ServiceNow instance version.
  		 * We still want to notify with a health event using EvtMgmtHealthMonitorManager instead. 
  		 */			
  		if (typeof global.EvtMgmtHealthMonitorManager === 'function') {
  			var emHMmanager = new global.EvtMgmtHealthMonitorManager();
  			var healthEventSeverity = '4'; // Warning
  			var remed = gs.getMessage('Verify if custom domain separation pre-requisites such as ServiceNow instance version requirement is fulfilled.');
  			var description = gs.getMessage("Custom domain information could not be set for some of the events.\nUser's domain will be set for these events.\n\nCause: \n{0}\n\nRemediation: \n{1}", [cause, remed]);
  			var monitorName = 'Connector Event Custom Domain Processing';
  			var bindCiName = 'Event Processing';
  			var expirationPeriodInSec = 0;
  			var updateTimeInSec = 0;
  			
  			emHMmanager.sendEvent(msgKey, healthEventSeverity, description, additionalInfo, monitorName, HEALTH_EVENT_SOURCE, bindCiName, 'true', expirationPeriodInSec, updateTimeInSec);
  		} else {
  			// No other way to notify the user. Add a system log.
  			gs.error(e);
  		}
  	}
  },

  _checkCustomDomainHelperAvailability: function() {
      if (typeof global.EvtMgmtEventCustomDomainHelper !== 'function') {
          throw new Error(gs.getMessage('Custom domain separation for push connectors is not supported with this version of ServiceNow instance.'));
      }
  },

  type: 'PushConnectorCustomDomainUtil'
};

Sys ID

3d5bfbd543c2a11044a83912bfb8f2ff

Offical Documentation

Official Docs: