What is a before query Business rule?
Before query Business rule (qBR from now on) is a special type of business rule in ServiceNow (SN from now on) that is used when we want to limit which records can users access from a given table. While most of you would probably think that Contextual security Access control lists (ACLs from now on) are the default way to go, there are some drawbacks that stem from the way SN is built. In this article, we will discuss what options we have to secure our records, and in which use cases they are fit for purpose.
Why do we need qBR?
Historically, Contextual security ACLs were added to the SN system later than the table queries themselves, this results in some interesting conclusions. If we print out all user records like (sys_user.list), the records are first queried and then ACL rules are applied. If ACL then denies access to that record, instead of not displaying it on the list at all, you will see the “Security constraints” message:
This is very confusing for end-users and also degrades the user experience heavily in case this user is able to open only 5/1000 records as the records cannot be reasonably ordered to display only the visible entries. The user has to click many times to view entries that are available to him. This is not the desired experience!
Here comes the qBR for the rescue. It effectively allows us to apply a filter before the initial query happens, thus eliminating the “Security constraints” message if done right. In turn, this can also be used as a replacement for some ACL entities; it is even considered a best practice by SN organizations to use either qBR or ACL.
When you have a few SN implementations under your belt, you will realize that despite best practices, we sometimes just know better (I mean we know the even better way which is fit for use & fit for purpose). It can be for example the SN platform must be secured not only from a normal user querying confidential records but also against an external user querying confidential records via API where qBR will not be applied, thus leaving a backdoor open for a person with malicious intents.
Coping with the most common qBR complication
In one of my projects the security was not only doubled (e.g. both qBR and ACL required) and complex (many rules for access), but also very sensitive to performance as users were frequently querying a table with almost 100 000 records. I will keep the ACL performance optimization for a separate article and here we will only discuss the qBR challenge I’ve faced and the possible solutions I’ve tried. I will also say why they failed, when they would work and what ultimately secured the desired functionality.
Consider the following case – we are querying documents, and some of them are classified. The user is only able to see unclassified documents and where he is a deputy owner or documents, that he is a direct owner of. At first sight, this query is not easily achievable to produce in a GlideRecord as we are missing a ‘master OR’ operator in GlideRecord class.
Let’s describe it with pseudocode:
addQuery(classification == none);
addQuery(deputy_owner == me);
addMasterOrCondition(owner == me);
//
this GlideRecord class method does not really exist // If we used standard addOrCondition() the or would be applied only to the last query statement, not to the whole query. Hence need a master OR which is not available. How easy would this be in a standard SQL query, right?
So, what are our options? I’ll start from the least plausible to the one, that actually worked:
1) Creation of multiple list views with hardcoded filters
In case there are just two separate lists (one for my documents, the second for documents related to me + nonclassified) this is a feasible solution. Our use case however was more complex and would require about 5 of these list views which is not really user convenient.
2) Using a system property – glide.security.ui.filter
Not many people know that there is actually a system property that can single-handedly replace a qBR. It can be used either as a global system property or just a dictionary attribute (for a single table). This makes sure that ACLs are processed for each queried record before they are displayed. Sounds too good to be true, right? Yeah, there is a price to pay in terms of performance. Especially if there are more than just a few records on the table and the ACL’s themselves are not really lightweight. Also do note that this property is not really documented and there might be more issues with it. Maybe that’s why the SN organization is not really referencing it anywhere.
For the issue, we are now discussing this did not work. But in some other projects, this proved to be a fit-for-purpose solution.
For more information, please follow this link: https://gist.github.com/icerge/ad7d7197c2ad1990c416d1383bbb4ffe
3) Using an encoded query in qBR
Encoded queries are very easy to build in case we are looking for static values. They are still possible to implement if we need dynamic values. However, they are subject to known errors in case we use the ‘master OR’ condition featured in the encoded query builder.
Let’s start with the easiest use case and finish with the known error limitation.
3.1) Simple query with static values
This is the easiest scenario – you can build a query using the visual builder, then right-click and just paste it into your script.
The script would look like this:
var encodedQuery = ‘active=true^priority=1^ORpriority=2’;
current.addEncodedQuery(encodedQuery);
This is nice, simple and effective. But only for static use cases.
3.2) More complex query with dynamic values using the ‘contains’ operator
If we want to have dynamic value (let’s say the assignee can see the record + his manager and manager of his manager), it’s a bit more complicated.
The script would look like this:
var managers = randomScriptInclude.giveManagers();
// imagine a function that returns an array with sys_id’s of this user’s employees etc.
var encodedQuery = ‘assigned_toLIKE’ + managers;
// I replaced the ‘aaa’ with the ‘+ managers’, which contains a string of sys_id’s
current.addEncodedQuery(encodedQuery);
This way we can build most of the query in the query builder and just copy it. Add temporary values where we need dynamic values and then replace them in the script.
3.3) Known error preventing the use of ‘master OR’ condition in qBR
There actually IS a possibility to provide ‘master OR’ condition to a GlideRecord class. It is in encoded query; it looks like this:
It’s the top OR button that does the trick (it’s slightly highlighted in this picture). This query can also be exported – in the query string the master OR element looks like this (the ^NQ):
assigned_toDYNAMIC90d1921e5f510100a9ad2572f2b477fe^priority=1^NQpriorityIN2,3,4,5
However, as always, it’s not so easy. Using the master OR in qBR will cause incorrect records to open. E.g. you click on INC000001 in the incident list and INC000005 is opened instead. Too bad, this doesn’t resolve our issue with ‘master OR’ condition.
For more details on this known error and the reason for the strange quirk when opening a record, please see the following link: https://hi.service-now.com/kb_view.do?sysparm_article=KB0564887
4) Propositional logic in queries and the final solution
A university course I nearly forgot finally proved to be able to secure the desired solution.
It literally took only a few minutes of switching the order of conditions and we had a nice and clean piece of code. What I am presenting is a very simplified version
We build a query where instead of master OR we use master AND
addQuery(deputy_owner == me);
addOrCondition(owner == me);
// now we have our documents but without regard for classification
addQuery(classification = none); // this is our master AND condition
addOrCondition(owner == me);
// again we can do additional filtering with addOrCondition,
Literally what I did, in the end, is just flip the logic upside down and separate the query statements into two segments.
If you did not study computer science OR law OR philosophy, you can just read up on this topic here: http://ovid.cs.depaul.edu/Classes/CS202-S07/handout1.pdf It really is quite useful in the long run!
5) Conclusion
Who doesn’t love a challenge right? But it’s always nice to find some guidance. This was my motivation for writing this article because when I was making the effort to meet the requirements of the project it was very hard to find any resources on my specific struggles. So if anyone else finds himself in a similar situation I hope you find this article and get some ideas about what can be done.
If you read this all the way down here, thank you and bless you!
Should you have any questions, get in touch.