Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save thisnameissoclever/c0cc7420cc237e8974130e52b2821278 to your computer and use it in GitHub Desktop.
Save thisnameissoclever/c0cc7420cc237e8974130e52b2821278 to your computer and use it in GitHub Desktop.
Prevent ACL Script Execution when Advanced is False
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) {
return;
}
var scriptHasUncommentedLines, flyoutText;
var isAdvanced = (newValue === 'true');
var origScriptVal = (g_form.getValue('script').trim());
var doesScriptExist = (!!origScriptVal);
var newScriptVal = '';
if (isAdvanced) {
//If the ACL is advanced, but is empty, then we don't need to do anything.
if (!doesScriptExist) {
return;
}
//If the ACL is advanced, make sure it doesn't contain any commented-out code.
//If it does, un-comment it.
newScriptVal = toggleACLScriptComments(origScriptVal, false);
} else {
//If the ACL is not advanced, make sure it doesn't contain any executable code.
//If it does, comment it out.
scriptHasUncommentedLines = (getUncommentedLines(origScriptVal).length > 0);
//If the script doesn't exist or is already commented out, don't do anything.
if (!doesScriptExist || !scriptHasUncommentedLines) {
return;
}
newScriptVal = toggleACLScriptComments(origScriptVal, true);
}
if (newScriptVal === origScriptVal) {
//If the script has not changed, don't do anything.
return;
}
flyoutText = '<p>As per <a href="https://support.servicenow.com/kb?id=kb_article_view' +
'&sysparm_article=KB0728012" target="_blank">KB0728012</a>, Business Rules and ACLs ' +
'with the "Advanced" checkbox set to <strong>false</strong> <i>will still execute any ' +
'code in the "Script" field</i>. <br />' +
'This can cause performance issues and unexpected behavior that can be very ' +
'difficult to troubleshoot, so it\'s recommended to remove any code in the "Script" ' +
'field if the "Advanced" checkbox is set to <strong>false</strong>.</p>';
if (isAdvanced) {
flyoutText += '<p>In this case, since the "Advanced" checkbox has been set to ' +
'<strong>true</strong>, and the script has previously been commented out by this ' +
'functionality, the script has been un-commented.</p>';
} else {
flyoutText += '<p>In this case, since the "Advanced" checkbox has been set to ' +
'<strong>false</strong> and the script field contained come code that was not ' +
'commented out, the script has been commented out to avoid this potentially ' +
'unexpected behavior.</p>';
}
flyoutText += '<p>Original script value:</p>\n' +
'<div style="width:75%;overflow:auto">\n' +
'<pre>' + origScriptVal + '</pre>\n' +
'</div>';
g_form.setValue('script', newScriptVal);
g_form.clearMessages();
showExpandingFormMessage(
('The ACL\'s script has been ' + (isAdvanced ? 're-enabled' : 'commented out') +
' since the "Advanced" checkbox has been set to ' + newValue + '.<br />'),
flyoutText,
'More info',
'Hide info'
);
//Helper functions
/**
* Accepts some code and a boolean indicating whether the code should be commented or
* uncommented. Returns the code with the appropriate comments added or removed. If the code has
* already been commented out using this function, it will not change the code.
* @param {string} codeInput - The code to comment or uncomment
* @param {boolean} [shouldBeCommented=true] - A boolean indicating whether to comment out the
* code in the codeInput argument (true) or uncomment it (false).
* Defaults to true.
* @returns {string}
*/
function toggleACLScriptComments(codeInput, shouldBeCommented) {
var i, currentLine, linesOfCode;
var commentExplanation = '/*\n' +
'\tThis code has been disabled because this ACL\'s "Advanced" field has been set to \n' +
'\tfalse. To enable this code, set the "Advanced" field to true. If you\'re seeing \n' +
'\tthis code as disabled and the advanced field is already set to true (which \n' +
'\tshould be the only way to see this code), something has gone wrong. Please \n' +
'\trefresh this form and then disable and re-enable the Advanced checkbox or \n' +
'\tmanually un-comment this code.\n' +
'*/\n\n';
var commentPrefix = '//~Disabled~// ';
var output = [];
//Set default value of shouldBeCommented
shouldBeCommented = (typeof shouldBeCommented === 'undefined' ? true : !!shouldBeCommented);
if (shouldBeCommented && codeInput.startsWith(commentExplanation)) {
//If the code should be commented, but is already commented out, return it as-is.
return codeInput;
}
if (shouldBeCommented) {
output.push(commentExplanation);
} else {
//If the code should be uncommented, remove the comment explanation if it exists.
codeInput = codeInput.replaceAll(commentExplanation, '').trim();
}
linesOfCode = codeInput.split('\n');
for (i = 0; i < linesOfCode.length; i++) {
currentLine = linesOfCode[i];
if (shouldBeCommented) {
output.push(commentPrefix + currentLine);
} else {
output.push(currentLine.replaceAll(commentPrefix, ''));
}
}
return output.join('\n');
}
//Let's use ES6 for this cause it's easier, and why not.
function getUncommentedLines(codeToCheck) {
var uncommentedLines = [];
var isMultilineComment = false;
const lines = codeToCheck.split('\n');
lines.forEach(line => {
if (isMultilineComment) {
if (line.includes('*/')) {
isMultilineComment = false;
const remaining = line.split('*/')[1];
if (remaining.trim() !== '') {
uncommentedLines.push(remaining);
}
}
} else {
if (!line.trim() || line.trim().startsWith('//')) {
return;
}
if (line.trim().startsWith('/*')) {
isMultilineComment = true;
const remaining = line.split('/*')[0];
if (remaining.trim() !== '') {
uncommentedLines.push(remaining);
}
return;
}
uncommentedLines.push(line);
}
});
return uncommentedLines;
}
/**
* Display an expandable form message. This message will be shown at the top of whatever form
* this code is executed on. The text in firstLine will be shown, but the text in flyoutText
* will be hidden until the user clicks the 'expand' link.
*
* @param {String} firstLine - The first line of text in the message, which will be shown
* immediately when this code executes. Unlike the text in flyoutText, this text will not
* be hidden.
* @param {String|HTML_TEXT} flyoutText - This text will be hidden by default, but will be shown
* once the user clicks the 'expand' link (which you can customize by setting expandLinkText).
* @param {String} [expandLinkText="Show more"] - Optionally specify the text to be shown as
* a clickable link, which will cause the form message to expand and display the text
* specified in flyoutText.
* @param {String} [collapseLinkText="Hide details"] - Optionally specify the text to be shown
* after the user clicks the 'expand' link text (specified in expandLinkText).
* This text will be shown when the message is expanded and the text specified in flyoutText
* is shown. Upon clicking this text, the message will be collapsed, flyoutText will be hidden,
* and the link text will revert back to that specified in expandLinkText.
*
* @example
* showExpandingFormMessage(
* 'This message expands',
* flyoutListHTML,
* 'Show more',
* 'Hide details'
* );
*/
function showExpandingFormMessage(firstLine, flyoutText, expandLinkText, collapseLinkText) {
var formMsg = firstLine;
expandLinkText = (typeof expandLinkText !== 'string') ? 'Show more' : expandLinkText;
collapseLinkText = (typeof collapseLinkText !== 'string') ? 'Hide details' : collapseLinkText;
formMsg += '<div>';
formMsg += '<p><a href="#" onclick="javascript:jQuery(this.parentNode).next().toggle(200); ' +
'this.innerText = ((this.innerText === \'' + expandLinkText + '\')?\'' + collapseLinkText +
'\':\'' + expandLinkText + '\');">' + expandLinkText + '</a></p>';
formMsg += '<div style="display: none;">';
formMsg += flyoutText;
formMsg += '</div>';
formMsg += '</div>';
g_form.addInfoMessage(formMsg);
}
}
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) {
return;
}
var scriptHasUncommentedLines, flyoutText;
var isAdvanced = (newValue === 'true');
var origScriptVal = (g_form.getValue('script').trim());
var doesScriptExist = (!!origScriptVal);
var newScriptVal = '';
if (isAdvanced) {
//If the BR is advanced, but is empty, then we don't need to do anything.
if (!doesScriptExist) {
return;
}
//If the BR is advanced, make sure it doesn't contain any commented-out code.
//If it does, un-comment it.
newScriptVal = toggleBRScriptComments(origScriptVal, false);
} else {
//If advanced is un-checked, but "action_delete" or "action_query" are selected, then
// un-check "action_delete" and "action_query".
if (
g_form.getValue('action_delete') === 'true' ||
g_form.getValue('action_query') === 'true'
) {
g_form.setValue('action_delete', false);
g_form.setValue('action_query', false);
g_form.showFieldMsg(
'advanced',
'"Advanced" was un-checked. "Delete" and "Query" have also been un-checked and hidden.',
'info',
true
);
}
//If the BR is not advanced, make sure it doesn't contain any executable code.
//If it does, comment it out.
scriptHasUncommentedLines = (getUncommentedLines(origScriptVal).length > 0);
//If the script doesn't exist or is already commented out, don't do anything.
if (!doesScriptExist || !scriptHasUncommentedLines) {
return;
}
newScriptVal = toggleBRScriptComments(origScriptVal, true);
}
if (newScriptVal === origScriptVal) {
//If the script has not changed, don't do anything.
return;
}
flyoutText = '<p>As per <a href="https://support.servicenow.com/kb?id=kb_article_view' +
'&sysparm_article=KB0728012" target="_blank">KB0728012</a>, Business Rules and ACLs ' +
'with the "Advanced" checkbox set to <strong>false</strong> <i>will still execute any ' +
'code in the "Script" field</i>. <br />' +
'This can cause performance issues and unexpected behavior that can be very ' +
'difficult to troubleshoot, so it\'s recommended to remove any code in the "Script" ' +
'field if the "Advanced" checkbox is set to <strong>false</strong>.</p>';
if (isAdvanced) {
flyoutText += '<p>In this case, since the "Advanced" checkbox has been set to ' +
'<strong>true</strong> and the script has previously been commented out by this ' +
'functionality, the Business Rule script has now been un-commented.<br />' +
'Please thoroughly review the BR script before saving, to ensure that it contains ' +
'the functionality and behavior you expect.</p>';
} else {
flyoutText += '<p>In this case, since the "Advanced" checkbox has been set to ' +
'<strong>false</strong> and the script field contained some code that was not ' +
'commented out, the script has been commented out to avoid this potentially ' +
'unexpected behavior.</p>';
flyoutText += '<p>Original script value:</p>\n' +
'<div style="width:75%;overflow:auto">\n' +
'<pre>' + origScriptVal + '</pre>\n' +
'</div>';
flyoutText += '<p>If the BR\'s Advanced field is later set back to true, this script ' +
'will be un-commented automatically, and will continue executing as normal.</p>';
}
g_form.setValue('script', newScriptVal);
g_form.clearMessages();
showExpandingFormMessage(
('The BR\'s script has been ' + (isAdvanced ? 're-enabled' : 'commented out') +
' since the "Advanced" checkbox has been set to ' + newValue + '.<br />'),
flyoutText,
'More info',
'Hide info'
);
//Helper functions
/**
* Accepts some code and a boolean indicating whether the code should be commented or
* uncommented. Returns the code with the appropriate comments added or removed. If the code has
* already been commented out using this function, it will not change the code.
* @param {string} codeInput - The code to comment or uncomment
* @param {boolean} [shouldBeCommented=true] - A boolean indicating whether to comment out the
* code in the codeInput argument (true) or uncomment it (false).
* Defaults to true.
* @returns {string}
*/
function toggleBRScriptComments(codeInput, shouldBeCommented) {
var i, currentLine, linesOfCode;
var commentExplanation = '/*\n' +
'\tThis code has been disabled because this BR\'s "Advanced" field has been set to \n' +
'\tfalse. To enable this code, set the "Advanced" field to true. If you\'re seeing \n' +
'\tthis code as disabled and the advanced field is already set to true (which \n' +
'\tshould be the only way to see this code), something has gone wrong. Please \n' +
'\trefresh this form and then disable and re-enable the Advanced checkbox or \n' +
'\tmanually un-comment this code.\n' +
'*/\n\n';
var commentPrefix = '//~Disabled~// ';
var output = [];
//Set default value of shouldBeCommented
shouldBeCommented = (typeof shouldBeCommented === 'undefined' ? true : !!shouldBeCommented);
if (shouldBeCommented && codeInput.startsWith(commentExplanation)) {
//If the code should be commented, but is already commented out, return it as-is.
return codeInput;
}
if (shouldBeCommented) {
output.push(commentExplanation);
} else {
//If the code should be uncommented, remove the comment explanation if it exists.
codeInput = codeInput.replaceAll(commentExplanation, '').trim();
}
linesOfCode = codeInput.split('\n');
for (i = 0; i < linesOfCode.length; i++) {
currentLine = linesOfCode[i];
if (shouldBeCommented) {
output.push(commentPrefix + currentLine);
} else {
output.push(currentLine.replaceAll(commentPrefix, ''));
}
}
return output.join('\n');
}
//Let's use ES6 for this cause it's easier, and why not.
function getUncommentedLines(codeToCheck) {
var uncommentedLines = [];
var isMultilineComment = false;
const lines = codeToCheck.split('\n');
lines.forEach(line => {
if (isMultilineComment) {
if (line.includes('*/')) {
isMultilineComment = false;
const remaining = line.split('*/')[1];
if (remaining.trim() !== '') {
uncommentedLines.push(remaining);
}
}
} else {
if (!line.trim() || line.trim().startsWith('//')) {
return;
}
if (line.trim().startsWith('/*')) {
isMultilineComment = true;
const remaining = line.split('/*')[0];
if (remaining.trim() !== '') {
uncommentedLines.push(remaining);
}
return;
}
uncommentedLines.push(line);
}
});
return uncommentedLines;
}
/**
* Display an expandable form message. This message will be shown at the top of whatever form
* this code is executed on. The text in firstLine will be shown, but the text in flyoutText
* will be hidden until the user clicks the 'expand' link.
*
* @param {String} firstLine - The first line of text in the message, which will be shown
* immediately when this code executes. Unlike the text in flyoutText, this text will not
* be hidden.
* @param {String|HTML_TEXT} flyoutText - This text will be hidden by default, but will be shown
* once the user clicks the 'expand' link (which you can customize by setting expandLinkText).
* @param {String} [expandLinkText="Show more"] - Optionally specify the text to be shown as
* a clickable link, which will cause the form message to expand and display the text
* specified in flyoutText.
* @param {String} [collapseLinkText="Hide details"] - Optionally specify the text to be shown
* after the user clicks the 'expand' link text (specified in expandLinkText).
* This text will be shown when the message is expanded and the text specified in flyoutText
* is shown. Upon clicking this text, the message will be collapsed, flyoutText will be hidden,
* and the link text will revert back to that specified in expandLinkText.
*
* @example
* showExpandingFormMessage(
* 'This message expands',
* flyoutListHTML,
* 'Show more',
* 'Hide details'
* );
*/
function showExpandingFormMessage(firstLine, flyoutText, expandLinkText, collapseLinkText) {
var formMsg = firstLine;
expandLinkText = (typeof expandLinkText !== 'string') ? 'Show more' : expandLinkText;
collapseLinkText = (typeof collapseLinkText !== 'string') ? 'Hide details' : collapseLinkText;
formMsg += '<div>';
formMsg += '<p><a href="#" onclick="javascript:jQuery(this.parentNode).next().toggle(200); ' +
'this.innerText = ((this.innerText === \'' + expandLinkText + '\')?\'' + collapseLinkText +
'\':\'' + expandLinkText + '\');">' + expandLinkText + '</a></p>';
formMsg += '<div style="display: none;">';
formMsg += flyoutText;
formMsg += '</div>';
formMsg += '</div>';
g_form.addInfoMessage(formMsg);
}
}
function onLoad() {
var scriptHasUncommentedLines;
var isAdvanced = (g_form.getValue('advanced') === 'true');
var origScriptVal = (g_form.getValue('script').trim());
var doesScriptExist = (!!origScriptVal);
if (!isAdvanced && doesScriptExist) {
scriptHasUncommentedLines = (getUncommentedLines(origScriptVal).length > 0);
if (scriptHasUncommentedLines) {
showExpandingFormMessage(
'<strong>ACL Script Contains uncommented Code, but the "Advanced" ' +
'Checkbox is set to false</strong>. <br />' +
'This code will still execute, which can cause performance issues and ' +
'unexpected behavior!',
'<p>As per <a href="https://support.servicenow.com/kb?id=kb_article_view' +
'&sysparm_article=KB0728012" target="_blank">KB0728012</a>, Business Rules and ACLs ' +
'with the "Advanced" checkbox set to <strong>false</strong> <i>will still execute any ' +
'code in the "Script" field</i>. <br />' +
'This can cause performance issues and unexpected behavior that can be very ' +
'difficult to troubleshoot, so it\'s recommended to remove any code in the "Script" ' +
'field if the "Advanced" checkbox is set to <strong>false</strong>.</p>',
'More info',
'Hide info'
);
}
}
function getUncommentedLines(codeToCheck) {
var uncommentedLines = [];
var isMultilineComment = false;
const lines = codeToCheck.split('\n');
lines.forEach(line => {
if (isMultilineComment) {
if (line.includes('*/')) {
isMultilineComment = false;
const remaining = line.split('*/')[1];
if (remaining.trim() !== '') {
uncommentedLines.push(remaining);
}
}
} else {
if (!line.trim() || line.trim().startsWith('//')) {
return;
}
if (line.trim().startsWith('/*')) {
isMultilineComment = true;
const remaining = line.split('/*')[0];
if (remaining.trim() !== '') {
uncommentedLines.push(remaining);
}
return;
}
uncommentedLines.push(line);
}
});
return uncommentedLines;
}
/**
* Display an expandable form message. This message will be shown at the top of whatever form
* this code is executed on. The text in firstLine will be shown, but the text in flyoutText
* will be hidden until the user clicks the 'expand' link.
*
* @param {String} firstLine - The first line of text in the message, which will be shown
* immediately when this code executes. Unlike the text in flyoutText, this text will not
* be hidden.
* @param {String|HTML_TEXT} flyoutText - This text will be hidden by default, but will be shown
* once the user clicks the 'expand' link (which you can customize by setting expandLinkText).
* @param {String} [expandLinkText="Show more"] - Optionally specify the text to be shown as
* a clickable link, which will cause the form message to expand and display the text
* specified in flyoutText.
* @param {String} [collapseLinkText="Hide details"] - Optionally specify the text to be shown
* after the user clicks the 'expand' link text (specified in expandLinkText).
* This text will be shown when the message is expanded and the text specified in flyoutText
* is shown. Upon clicking this text, the message will be collapsed, flyoutText will be hidden,
* and the link text will revert back to that specified in expandLinkText.
*
* @example
* showExpandingFormMessage(
* 'This message expands',
* flyoutListHTML,
* 'Show more',
* 'Hide details'
* );
*/
function showExpandingFormMessage(firstLine, flyoutText, expandLinkText, collapseLinkText) {
var formMsg = firstLine;
expandLinkText = (typeof expandLinkText !== 'string') ? 'Show more' : expandLinkText;
collapseLinkText = (typeof collapseLinkText !== 'string') ? 'Hide details' : collapseLinkText;
formMsg += '<div>';
formMsg += '<p><a href="#" onclick="javascript:jQuery(this.parentNode).next().toggle(200); ' +
'this.innerText = ((this.innerText === \'' + expandLinkText + '\')?\'' + collapseLinkText +
'\':\'' + expandLinkText + '\');">' + expandLinkText + '</a></p>';
formMsg += '<div style="display: none;">';
formMsg += flyoutText;
formMsg += '</div>';
formMsg += '</div>';
g_form.addErrorMessage(formMsg);
}
}
function onLoad() {
var scriptHasUncommentedLines, flyoutText;
var isAdvanced = (g_form.getValue('advanced') === 'true');
var origScriptVal = (g_form.getValue('script').trim());
var doesScriptExist = (!!origScriptVal);
if (!isAdvanced && doesScriptExist) {
scriptHasUncommentedLines = (getUncommentedLines(origScriptVal).length > 0);
if (scriptHasUncommentedLines) {
showExpandingFormMessage(
'<strong>Business Rule Script Contains uncommented Code, but the "Advanced" ' +
'Checkbox is set to false</strong>. <br />' +
'This code will still execute, which can cause performance issues and ' +
'unexpected behavior!',
'<p>As per <a href="https://support.servicenow.com/kb?id=kb_article_view' +
'&sysparm_article=KB0728012" target="_blank">KB0728012</a>, Business Rules and ACLs ' +
'with the "Advanced" checkbox set to <strong>false</strong> <i>will still execute any ' +
'code in the "Script" field</i>. <br />' +
'This can cause performance issues and unexpected behavior that can be very ' +
'difficult to troubleshoot, so it\'s recommended to remove any code in the "Script" ' +
'field if the "Advanced" checkbox is set to <strong>false</strong>.</p>',
'More info',
'Hide info'
);
}
}
function getUncommentedLines(codeToCheck) {
var uncommentedLines = [];
var isMultilineComment = false;
const lines = codeToCheck.split('\n');
lines.forEach(line => {
if (isMultilineComment) {
if (line.includes('*/')) {
isMultilineComment = false;
const remaining = line.split('*/')[1];
if (remaining.trim() !== '') {
uncommentedLines.push(remaining);
}
}
} else {
if (!line.trim() || line.trim().startsWith('//')) {
return;
}
if ( //Specific to BRs
line.indexOf('function executeRule') >= 0 ||
line.indexOf('})(current, previous)') >= 0
) {
return;
}
if (line.trim().startsWith('/*')) {
isMultilineComment = true;
const remaining = line.split('/*')[0];
if (remaining.trim() !== '') {
uncommentedLines.push(remaining);
}
return;
}
uncommentedLines.push(line);
}
});
return uncommentedLines;
}
/**
* Display an expandable form message. This message will be shown at the top of whatever form
* this code is executed on. The text in firstLine will be shown, but the text in flyoutText
* will be hidden until the user clicks the 'expand' link.
*
* @param {String} firstLine - The first line of text in the message, which will be shown
* immediately when this code executes. Unlike the text in flyoutText, this text will not
* be hidden.
* @param {String|HTML_TEXT} flyoutText - This text will be hidden by default, but will be shown
* once the user clicks the 'expand' link (which you can customize by setting expandLinkText).
* @param {String} [expandLinkText="Show more"] - Optionally specify the text to be shown as
* a clickable link, which will cause the form message to expand and display the text
* specified in flyoutText.
* @param {String} [collapseLinkText="Hide details"] - Optionally specify the text to be shown
* after the user clicks the 'expand' link text (specified in expandLinkText).
* This text will be shown when the message is expanded and the text specified in flyoutText
* is shown. Upon clicking this text, the message will be collapsed, flyoutText will be hidden,
* and the link text will revert back to that specified in expandLinkText.
*
* @example
* showExpandingFormMessage(
* 'This message expands',
* flyoutListHTML,
* 'Show more',
* 'Hide details'
* );
*/
function showExpandingFormMessage(firstLine, flyoutText, expandLinkText, collapseLinkText) {
var formMsg = firstLine;
expandLinkText = (typeof expandLinkText !== 'string') ? 'Show more' : expandLinkText;
collapseLinkText = (typeof collapseLinkText !== 'string') ? 'Hide details' : collapseLinkText;
formMsg += '<div>';
formMsg += '<p><a href="#" onclick="javascript:jQuery(this.parentNode).next().toggle(200); ' +
'this.innerText = ((this.innerText === \'' + expandLinkText + '\')?\'' + collapseLinkText +
'\':\'' + expandLinkText + '\');">' + expandLinkText + '</a></p>';
formMsg += '<div style="display: none;">';
formMsg += flyoutText;
formMsg += '</div>';
formMsg += '</div>';
g_form.addErrorMessage(formMsg);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment