Controlling record access using ‘Before Query’ business rules

Home/Scripting/Business rules/Controlling record access using ‘Before Query’ business rules

Controlling record access using ‘Before Query’ business rules

System security is probably one of the more challenging things to implement in Service-now.com.  While an out-of-box ServiceNow instance comes with the core security built-in, any implementation will inevitably have customizations in this area.  At some point, I plan on writing a basic security guide to help administrators and consultants make informed decisions about how security should be implemented in their systems.

I’ve now added the security guide I promised! Check out the ‘What Everybody Should Know about ServiceNow Security‘ article for more details!

One little-known, but extremely useful access control method is to use business rules to restrict record access in your system.  You can do this by creating what I call a ‘Before Query’ business rule.  These business rules have a ‘When’ value of ‘Before’ and also have the ‘Query’ checkbox selected.  ‘Before Query’ business rules are only used when you need to restrict access to certain rows within a table for certain groups of individuals.  Because the security is controlled by a script, the restriction can be applied based on roles, group membership, company or department information from the user record, or pretty much anything else that you can derive about the user trying to access a set of records.  There are a few of these business rules out-of-box that serve as great examples of how to implement security in this way.  When I need to implement security with a ‘Before Query’ business rule, I usually start with the ‘incident query’ business rule as my template.

The purpose of the ‘incident query’ business rule is to limit the access of records (rows) on the ‘Incident’ table.  Specifically, it says that you need to have the ‘itil’ role to access incident records unless you are the person listed as the Caller or Opened by on the Incident.  It is because of this business rule that your end-users can only see their own incident records in the system!  Below is the script (along with comments explaining exactly how it works).

if (!gs.hasRole('itil') && gs.getSession().isInteractive()) { //Check if the user has the 'itil' role and if the session is an actual user session
   //If they DON'T have the 'itil' role then do the following...
   var u = gs.getUserID(); //Get the sys_id value of the current user
   var q = current.addQuery('caller_id', u); //Modify the current query on the incident table so that if the user is listed in the 'caller_id' field they can see the record
   q.addOrCondition('opened_by', u); //Also allow the user access if they are the one who opened the incident (even if they aren't the caller)
   gs.print('query restricted to user: ' + u);
}

Here’s another example. This time we will restrict visibility to records if the user is a member of the current assignment group.

if (gs.getSession().isInteractive()) {          
   //Restrict to caller or members of assigned group...
   var u = gs.getUserID(); //Get the sys_id value of the current user
   var g = getMyGroups(); //Get the list of the current user groups
   var q = current.addQuery('caller_id', u).addOrCondition('assignment_group', getMyGroups()); //Modify the current query on the incident table
}
Any time you’re working with ‘Before Query’ business rules you’ll want to be sure you account for a specific scenario you may encounter when you’re referencing the records you’re limiting access to. Setting things up incorrectly may mean that you end up with blank reference fields!
By | 2014-03-03T05:45:19+00:00 January 4th, 2010|Categories: Business rules|Tags: , |32 Comments

About the Author:

32 Comments

  1. Garrett Griffin June 14, 2010 at 7:28 am - Reply

    Thanks for this. Exactly what I was looking for. When in doubt, I turn to Service-Now Guru :-).

  2. Matt May 3, 2011 at 12:39 pm - Reply

    Would you happen to know if visibility restrictions can be applied to Support Skills? For instance, can I restrict the appearance of a Support Skill to specific roles?

    • Mark Stanger May 4, 2011 at 12:36 am - Reply

      I haven’t ever seen that done for the specific requirement you mention, but it should be possible to limit the visibility of those records using this technique.

  3. Karen May 12, 2011 at 8:46 pm - Reply

    Thank you for the link to your site – very useful, I shall enjoy exploring it.

    I’ve taken a look at the script above and was wondering if this will restrict the view for all users? What I want to do is restrict it just for a specific group.

    I.e. all staff with an ITIL role can see all Incidents, except for members of a specific group who should only see those that are assigned to their group.

    Is this possible?

    Thanks

    Karen

    • Mark Stanger May 13, 2011 at 12:28 am - Reply

      It will apply the restriction for ALL users. If you only wanted to apply it to a specific group you would need to wrap the whole thing in an additional ‘if’ statement to check and see if they were part of the specific group (or if they have some additional role).

      //Only apply this to members of a specific group
      if(gs.getUser().isMemberOf('GROUP_SYS_ID_HERE')){
         if(!gs.getUser().isMemberOf(current.assignment_group) && gs.getSession().isInteractive()){ //Check if the user is a member of the current assignment group
            //If they DO NOT belong to the Assignment group listed on the ticket...
            var u = gs.getUserID(); //Get the sys_id value of the current user
            var q = current.addQuery('caller_id', u); //Modify the current query on the incident table so that if the user is listed in the 'caller_id' field they can see the record
         }
      }
  4. Mohammed July 20, 2011 at 8:47 am - Reply

    Hi Mark,

    This is exactly what I’m looking for but ran into what looks like a problem.
    I’ve implemented this but it doesn’t apply the restriction to the closed records.

    I pretty much copied and pasted the business rule.

    Thank you,

    • Mark Stanger July 20, 2011 at 10:40 am - Reply

      Hard to say what the problem might be without having access to the instance you’re working in. The scripts here work correctly and have been tested and confirmed. If you pasted directly into a business rule with the syntax editor on then you might have some errant spaces in there. You might try pasting into the script field with the syntax editor disabled if that’s the case.

  5. Dhanraj Poojari July 23, 2011 at 11:44 am - Reply

    Hello Mark, We have run into a situation with our MSP instance where the customers security team has exposed security holes where any system table is readable by any users by accessing it from the url example https://demo.service-now.com/sys_user_has_role_list.do Since we are on “Allow all” model of our instance. What would be the best way to

    1) Identify the System tables Total count of tables in the instance 870.
    2) Should ACL be used or the business rule mentioned above.
    3) In either case we need to create entries for approximately 500 system tables(rest of the tables being data tables) what is the best way to automate this.

    Any guidance with this regards woud be appreciated.

    Regards,
    Dhanraj Poojari

    • Mark Stanger July 23, 2011 at 9:40 pm - Reply

      I don’t think you’re going to find a quick and easy solution to this problem. If I were you, I would probably start by moving to a default deny model and working from there. You’re still going to have to make some ACL modifications, but I think you’ll end up with a much cleaner solution when you’re done. You’ll need to make sure to go through a thorough QA cycle after turning on the plugin to make sure the default deny doesn’t break anything but I still think that’s the best route.
      http://wiki.service-now.com/index.php?title=High_Security_Settings

  6. Alex Rivera August 16, 2011 at 7:05 am - Reply

    Hey Mark,

    In your article you state that the Before Query can be applied to an attribute from the User table. What is the proper way/syntax to use for this? For example, we have an “External Customer” check box on the user table and I want to start by stating “If the user is external, current.AddQuery…”

    Thanks!

    • Mark Stanger August 16, 2011 at 7:21 am - Reply

      Check out my user object cheat sheet for details on accessing specific user information in script. In this case, I think you’ll just need an ‘if’ statement like this…

      if(gs.getUser().getRecord().getValue('u_external_customer') == true){
         current.addQuery...
      }
  7. Before query BR on sc_category table January 16, 2012 at 4:44 am - Reply

    Hallo everybody,

    I tried to apply the logic on to the sc_category table in order to hide some elements dynamically from the service catalog. But whatever I did – the business rule didn’t get executed when the service catalog is requested (catalog_home.do?sysparm_view=catalog_default)

    Regards
    Thomas

  8. Allie Hopkins September 20, 2013 at 10:20 am - Reply

    Has this been tested and used on the latest: Calgary? I don’t seem to have access to the ‘curren’t record when running a before BR having the query box selected. Perhaps something else is stopping this from working properly. It runs when testing against the gs user (for roles, etc.) but not any fields within ‘current’.

    • Mark Stanger September 20, 2013 at 11:52 am - Reply

      It works in Calgary. ServiceNow uses this in several places out-of-box, including the ‘incident query’ business rule.

      • Allie Hopkin September 24, 2013 at 6:54 am - Reply

        Specifically with the assignment group and isMemberOf. The OOB is only testing for role of the currently logged in user. I plugged this into a demo and is does not work nor does it work on my instance. Again, I’m referring to !gs.getUser().isMemberOf(current.assignment_group).

        By the way, this site is such a wealth of information and neat hacks. I love discovering new things to play with.

        • Mark Stanger September 24, 2013 at 10:30 am - Reply

          Thanks Allie. I’m not sure exactly what you’re asking in your comment though. The whole point of a ‘Before Query’ business rule is to secure records based on the currently logged in user. If it’s not working, then there’s probably something else going on in your script.

  9. Allie Hopkins September 24, 2013 at 11:33 am - Reply

    Apologies for not being clear. Let me try again.

    Your example has the following test:

    if (!gs.getUser().isMemberOf(current.assignment_group) && gs.getSession().isInteractive())

    The OOB incident query has this test:

    if (!gs.hasRole(‘itil’) && gs.getSession().isInteractive())

    One is testing the user against the field current.assignment_group and the OOB is testing against the user’s roles and nothing to do with the current records. You don’t start querying the current record fields until you are in the loop and doing a current.addQuery. When I add some debug info messages it appears that the current.assignment_group is never queried and all I get is a blank for group and the test always fails. If I change this to display rather than before, my debug messages work but, of course, the outcome is not what we’re after.

    What I did to ensure it wasn’t something unique to my instance is try this on demo. I copied your exact example, added a few users to the right groups, and it does not work. I haven’t made any changes to the code yet.

    Any chance you can try the example above (with the assignment_group test) in a demo or on a few of your own instances on Calgary? I’m really at a loss.

    • Mark Stanger September 24, 2013 at 12:42 pm - Reply

      The only problem is that the code is wrong! 🙂 You’re right that ‘current’ in this case applies to the query, not to individual records. In order for this to work, you have to modify the query, not try to place a ‘current’ condition around it. I’ve updated the code snippet above with something that should work better. Let me know how it goes.

      • Allie Hopkins September 24, 2013 at 1:10 pm - Reply

        Ahhhh. Thank you much. Works perfectly now.

  10. Michael May 15, 2014 at 7:40 pm - Reply

    How would you control access to sc_cat_item records based on the “Available for company” related list?

    If you create a reference field on a form that references the sc_cat_item table, you get all records.
    We would like to filter the results the same way they are filtered in the Service Catalog.

    An example of this is the “Request item” reference field in the New Call module (from the Service Desk Call plugin) —
    it lists all catalog items, including items that are restricted to other companies.

    • Mark Stanger May 15, 2014 at 8:35 pm - Reply

      ServiceNow really hasn’t made that very easy to do…especially in a reference qualifier scenario where the user accessing the list may be ordering on behalf of someone else. I don’t know of an included script to do this and it would probably be pretty complex to come up with on your own.

  11. Michael June 6, 2014 at 1:20 pm - Reply

    Hi Mark,

    I’m trying to do something similar to the second example in your article. How would you construct the query if the field on the table is a List (glide_list) field referencing the Group[sys_user_group] table?

    I tried the following which seems to work only if the user is a member of all of the groups in the u_group field.

    var myGroups = getMyGroups();
    var q = current.addNullQuery(‘u_group’);
    q.addOrCondition(‘u_group’, myGroups);

    I want the record to be returned if the u_group field is empty, or if the user is a member of any of the groups in the u_group field.

  12. Elizabeth June 18, 2014 at 1:51 am - Reply

    Hi Mark – is it possible to use this function to restrict access to specific Standard Changes? For example if we have a standard change for changing the firewall it would not be prudent to allow anyone to choose this standard change – how would we go about locking the change for anyone other than a specific group or groups so it doesnt appear in the drop down list of changes for that specific Program Element?

    • Mark Stanger June 18, 2014 at 5:57 am - Reply

      That is possible, and may be a very good use for this type of functionality.

  13. Namrata Jain September 12, 2014 at 3:59 am - Reply

    Hi,

    Have you ever tried this on glide list field, the field above is reference field and hence it is doable, but what if the field is glidelist, is it doable or not?

    Best Regards,
    Namrata Jain

    • Mark Stanger September 12, 2014 at 6:12 am - Reply

      This isn’t tied to a particular UI component at all, since it’s tied to the actual database query it should work anywhere in the system.

  14. Shane January 8, 2015 at 10:40 am - Reply

    Hi Mark,

    I am trying to use a business rule to limit the HR Cases (hr_case table) which show via the Open module based on the logged in user’s assignment group AND based on 2 categories.

    In short, if the logged in user is in the “Money” group, then they should only see cases where the Category is Benefits OR Payroll. The OR condition is causing me issues!

    Thank you for your posts. They are always very helpful!

    • Mark Stanger January 8, 2015 at 11:54 am - Reply

      Shane, thanks for reading and for your comment! The examples above should show a couple of ways to add the ‘or’ condition. Honestly, these are usually pretty tricky for me as well. Sometimes I just eliminate the confusion completely by creating a filter in a standard list to show the records I want to show, then right-click the portion of the list breadcrumb to copy the query. Once you’ve done that, you can easily use ‘addEncodedQuery’ to add the exact same query that appears in the list. Check out this guru article for details on ‘addEncodedQuery’.

      https://www.servicenowguru.com/scripting/gliderecord-query-cheat-sheet/

  15. Rafael Mercês January 28, 2016 at 6:02 am - Reply

    Hi Mark,

    We implemented your solution and it affected 1 of our catalog itens in the following way: when requesting the item, on submit, the item is generated (REQ and RITM), but the RITM is generated without workflow associated to it. Have you ever seen this kind of scenario?

    Thanks,

    • Mark Stanger January 28, 2016 at 7:09 am - Reply

      I don’t know of anything I have in this article that would impact request items at all. Depending on how you’ve implemented it, I suppose there’s a possibility that a query business rule could cause an issue but that’s probably a general ServiceNow question rather than something related to my explanations in this article. I’d contact SN support or the SN community for help on this.

  16. Davin April 20, 2016 at 8:52 pm - Reply

    Hi,
    we’ve noticed that the Before Query shows up in Global Text Searches.

    Normally this is probably OK, but one of our Query Rules returns a very long encoded query, which then displays over more than 10 lines before the actual results are shown.

    I’ve hidden the Filter using jQuery in a Global UI Script, eg $j(‘span.searchfilterdisplay’).hide(); but hoping there is a better way!

    Cheers,
    Dav

Leave A Comment