logo

NJP

HowTo secure data access without impacting performance

Import · Feb 14, 2018 · article

Quite frequently we hit the requirement that certain data in a customer instance needs to be secured / protected against accidental access. The obvious choice and also in-line with technical best practice is to use an Access control rules aka ACL.

So far so good, but what if the rule is not as simple as defining a condition or checking a system role? ¨

Assume we have users from multiple companies accessing our system. The easy part is to allow access to the records based on company, i.e. Company A can only see their own incidents. But if we have another company - maybe a partner - who also needs access to Company A's incidents we are screwed. Let's see how we can tackle it.

Allow configuration to define access

Create a new field on the user record to store which companies a user is allowed to see

This field has to be of type glide_list so we can store more than one company.

We could also do a separate table if we do not want to pollute / enhance the sys_user record. In the end all we need is a place where we find what records are ok to be seen for the given user.

Create Access Control Rule (ACL)

With the persisted information on what a user can access, we can define appropriate Read-ACL using a script to read that information from the user record. While this will work perfectly, it does have a significant impact to the performance. Imagine we load a list of 20 incidents, for each and every incident the system now needs to go back to the user record and find our new custom field. This means we fire 20 new select statements to the database always returning the same value. Of course a cache would probably sort most of it out, but imagine your rule being even more complex. I have seen customer scenarios where the information could not be stored directly on the user record, but on related information like a specific other data set that needed to be loaded. Anyhow, fetching this information for every record is surely not recommendable for a read operation.

Store user specific information in session object

With every login to the instance the platform will create a session object. If we store the information we need in here, our ACL has no need to fetch it from the database for every record. Here is how this can be achieved:

With the login the platform will fire an event called 'session.established'. Usually this is used for reporting purposes, but we can leverage it for our case.

Create a script action

Create a new Script action listening to our event 'session.established'. There is one important gotcha in getting this to work. Usually all script actions will run asynchronously causing the session to be established properly, but whatever your script does will have no effect to the session. To avoid that we need to change our action to run synchronously. To do so we need to cheat a little. There is a field on our script action table called synchronous (sysevent_script_action.synchronous), but this column is being hidden from us by ACLs. So elevate your privileges to security admin and search of ACLs with name 'sysevent_script_action.synchronous' - you should find two of them. Simple deactivate them for now. As the column is not on the form we will need to add it to our list view, this will not cause any future harm. Once your see it, change it to 'true' for our new script action. Should look like this:

image

Awesome. Now to the script content:

var session = gs.getSession();
session.putClientData('test1', 'Harry');


 

With this example we store the value of 'Harry' as property named 'test1'. Obviously this needs to be amended to first find the data we need, like querying the user record for the companies we want to allow.

Change the ACL script

Within our read ACL where we used to lookup the user record we now need to change it to fetch the information from the user session:

var session = gs.getSession();
var clientData = session.getClientData('test1');

// now check current record
if (current.company == clientData) {
    answer = true;
} else {
    answer = false;
}

Now we can use the 'clientData' variable to validate the current record. In my example I am using a simple '==' operator, if clientData contains a list of objects make sure you do amend as necessary.

Optional: Create Before Query Business Rule

With the ACL's in place we can rest assured our data is safe. However it is still showing the message to the user that certain records are removed due to security constraints. If we want to avoid that we can add a before query business rule. This would look something like this:

  current.addQuery("company", "IN", gs.getSession().getClientData('test1'));


Now the query to the database will be modified before it hits the database even filtering the records in the first place. This will completely avoid the ACL Read scripts as the data is not even there anymore. Much better as we removed unnecessary operations from the execution making the system faster for our end users.

View original source

https://www.servicenow.com/community/developer-articles/howto-secure-data-access-without-impacting-performance/ta-p/2330180