717 views

The facts:

When discussing Business Rules, UI Scripts, Script Includes, Background-Scripts, UI Actions, Client Scripts and the ACL evaluation that occurs at runtime, here are three facts that you need to know right from the start:

  1. The scripts mentioned above run in the context of the current user.
  2. Scripts that leverage ServiceNow's server-side JavaScript API classes to create, read, update, and delete records are not subject to passing or failing Access Control Rules (ACLs). The GlideRecordSecure class is the exception to this.
  3. Scripts that leverage ServiceNow's Client-side JavaScript API functions always observe ACLs.

A lack of understanding regarding these three facts can be a common source of frustration when investigating issues related to client-side and server-side JavaScript.

Examples:

Using just one UI Action, we can prove all three of these facts. Let's start with the following question:

"How can users approve records on table sysapproval_approver when they don't have write access to the State field on the form?"

Navigate to the following URL on a test instance and have a look at this UI Action. 

/nav_to.do?uri=sys_ui_action.do?sys_id=8468ee55c611227d01a072a67bdbd3e7 

Take notice that the "Client" checkbox is not checked meaning this is going to be running server-side JavaScript. The script that the UI Action runs will set the state of the current record to "approved" and then update the record to save the changes. Since, as we now know, ACLs are not observed on the server-side by default, we can see that anyone with access to the UI Action form button will be able to approve the current approval record regardless of whether they can pass or fail the ACLs on table sysapproval_approver.

The condition to see the UI Action form button states that the current record's state must be "requested" and the current user must either be the approver on the record or a delegate of the approver. Problems start occurring when you relax the condition so that more end users can see the UI Action button assuming that ACLs on the table will prevent approval by users that do not have write permissions on the table.

Let's test this out.

  1. Change the condition on the UI Action from this:
    current.state == 'requested' && isApprovalMine(current)

    to this:

    current.state == 'requested'
  1. Search for "Debug" in the Application Navigator and click on "Debug Security"
  2. Impersonate ITIL User. The ITIL User can read all records on table sysapproval_approver but will not pass the write ACL for the State field on an out-of-box instance.
  3. Navigate to /nav_to.do?uri=sysapproval_approver_list.do
  4. Open any record where the state is "requested"
  5. Search the browser page for the text "state/write". (On a Mac search in Chrome with ⌘+Enter. On Windows use Ctrl + f) Once you flip through the results of the text search, you'll notice that the ITIL User fails the write ACL for sysapproval_approver.state.
  6. Press the "Approve" UI Action button.

Intuitively we might think that the ACLs will prevent ITIL user from writing to the State field but in actuality the server-side JavaScript in the UI Action can update the State field without checking ACLs. The end result of this test is that the record is approved by a user that cannot pass the write ACL for the State field. The reason this is possible is because we gave this user access to use the Approve UI Action form button.

If you need your server-side scripts to observe ACLs, here are your options:

  • You can use the canCreate(), canRead(), canWrite(), and canDelete() functions of the server-side GlideRecord class and/or the canRead(), canWrite() functions of the server-side GlideElement class. Use the GlideRecord functions to check ACLs at the table level and use the GlideElement function to check ACLs at the field level. The "can" functions of GlideRecord and GlideElement are quite straight forward; they just return true if the user can and false if the user can't create, read, delete, or write to the table or field.
  • Or, you can use the GlideRecordSecure class which is just like using the GlideRecord class except that GlideRecordSecure observes ACLs on the table that it is trying to read or write to.

Let's test this by continuing to modify the same "Approve" UI Action from the previous example.

  1. Make sure the condition is still:
    current.state == 'requested'
  1. Change the script from this:
    current.state='approved';
    current.update();

    to this:

    if(current.state.canWrite()){
        current.state='approved';
        current.update();
    }
  1. Save the changes to the UI Action, impersonate the ITIL user and try again to approve a request on table sysapproval_approver.

You'll notice that this time the State field does not change from "Requested" to "Approved". This is because we are now using the canWrite() function of the GlideElement class the check whether the ITIL user can pass the write ACLs on sysapproval_approver.state before we allow any updating. The user fails the ACLs and now cannot change the State field!

Using the GlideRecordSecure class is also an option. Let's test this with the same UI Action.

  1. Make sure the condition is still:
    current.state == 'requested'
  1. Change the script from this:
    if(current.state.canWrite()){
        current.state='approved';
        current.update();
    }

    to this:

    var grs = GlideRecordSecure('sysapproval_approver');
    grs.get(current.sys_id);
    grs.state='approved';
    grs.update();
  1. Save the changes to the UI Action, impersonate the ITIL user and try again to approve a request on table sysapproval_approver.

The result is absolutely the same only the techniques are different. The ITIL user should still not be able to change the State field.

If the default behavior on the server side is not to observe ACLs, how does this work on the client-side?

To answer this, let's talk about fact number three. Client-side JavaScript API functions always observe ACLs. The reason being is that we have no control over who runs them! Any technically savvy end user can open the JavaScript console in the browser and run any client-side JavaScript that they want.

You can prove this by changing the previous UI Action to now run client-side JavaScript. We'll write a new script that uses the client-side GlideRecord class to set the State field to "Approved" and update the record.

  1. Make sure the condition is still:
    current.state == 'requested'
  1. Check the "Client" checkbox.
  2. Populate the "Onclick" field with:

    approve()

  1. Change the script from this:
    var grs = GlideRecordSecure('sysapproval_approver');
    grs.get(current.sys_id);
    grs.state='approved';
    grs.update();

    to this:

    function approve(){
        var gr = new GlideRecord('sysapproval_approver');
        gr.get(g_form.getUniqueValue());
        gr.state = 'approved';
        gr.update();
    }
  1. Save the changes to the UI Action, impersonate the ITIL user and try again to approve a request on table sysapproval_approver.

Notice that you don't need to make any special effort to ensure that ACLs are observed like we did on the server-side. ServiceNow's client-side JavaScript APIs always observe ACLs.

I've found that understanding these differences can sometimes be key to solving issues related to ServiceNow's JavaScript API. Hopefully, this article will provide some clarity on this for you as well!

Article Information

Last Updated:2018-03-20 05:21:57
Published:2018-03-20