ITIL role users must only be able to see incidents assigned to groups they are member of AND child groups

scott111
Kilo Expert

REQUIREMENT: ITIL role users must only be able to see incidents assigned to groups they are member of AND that group's child groups unless the user is the caller and then they can see the incident.

Assume a usergroup called Security with three child groups and several grandchild groups. If itil user Fred is a member of a second tier group called Security Physical he would see incidents assigned to that group and to any child groups under Security Physical  --even though he is not a member of said child groups. Fred can see all incidents where he is the caller.

What is the best way to do this?

I am creating a business rule to determine member group and child hierarchy using the code below which adds to the current query to restrict access as per the requirement. You can paste this code into a Fix script, change the var TopLevelGroup to a top tier usergroup and then run it. My output is (note level numbers):

*** Script: SCOTT111: GROUPS=1 Security
*** Script: SCOTT111: GROUPS=2 Security Operations
*** Script: SCOTT111: GROUPS=2 Security Policy & Compliance
*** Script: SCOTT111: GROUPS=3 Security Standard Compliance
*** Script: SCOTT111: GROUPS=4 Security Cloud FedRamp Compliance
*** Script: SCOTT111: GROUPS=2 Security Physical & Personnel
*** Script: SCOTT111: GROUPS=3 Security Physical
[0:00:00.020] Total Time

 

var TopLevelGroup = 'Security';   // Name of top level parent group
var A_Group_Sysid = GetGroupSysidByName(TopLevelGroup);
var ListOfGroupAndChildren = []; // List of sysIDs for top parent group and ALL descendents
var ListOfGroupAndChildrenLevel = [];    //  hierarchy level
var ListOfGroupUNPROCESSED = []; // "UNPROCESSED"= list of groups (by sysid) not yet searched for its child groups
var ListOfGroupUNPROCESSEDLevel = [1];      // save group level number when you first read it in
var LoopLimiter = 0;     // let's not take any chance of this loop getting infinite
var GroupLevel = 1;
var ChildGrps = [];
var NumOfChildGrps = 1;
var i;
var x = 0;
var y;
ListOfGroupUNPROCESSED[0]	= A_Group_Sysid;
while (ListOfGroupUNPROCESSED.length > 0) {
	LoopLimiter++;
	A_Group_Sysid = ListOfGroupUNPROCESSED[0];   //returns 1st element in array
	ChildGrps = GetChildren(A_Group_Sysid); 
	ListOfGroupAndChildren.push(ListOfGroupUNPROCESSED.shift()); //children of A_Group_Sysid search done now move to final list
	ListOfGroupAndChildrenLevel.push(ListOfGroupUNPROCESSEDLevel.shift());
	NumOfChildGrps = ChildGrps.length;
	if (NumOfChildGrps > 0) {
		GroupLevel++;
		Array.prototype.unshift.apply(ListOfGroupUNPROCESSED, ChildGrps);   // prepend ALL newly found children to UNPROCESSED list
		for (y = 0; y < NumOfChildGrps; y++)  { ListOfGroupUNPROCESSEDLevel.unshift(GroupLevel); }
		}
	else {                      // NumOfChildGrps == 0
		if (ListOfGroupUNPROCESSEDLevel.length > 0)  { GroupLevel = ListOfGroupUNPROCESSEDLevel[0]; }
		}
// 	gs.print("SCOTT111: ListOfGroupUNPROCESSEDLevel so far=" + ListOfGroupUNPROCESSEDLevel);
// 	gs.print("SCOTT111: ListOfGroupUNPROCESSED so far=" + ListOfGroupUNPROCESSED);
// 	gs.print("SCOTT111: ListOfGroupAndChildren so far=" + ListOfGroupAndChildren);
// 	gs.print("SCOTT111: ListOfGroupAndChildrenLevel so far=" + ListOfGroupAndChildrenLevel);
	if (LoopLimiter > 100) { throw "SCOTT111: LoopLimiter too high";  }
}

//gs.print("SCOTT111: Using sys_ids in ListOfGroupAndChildren create array of same groups by name");	
var ListOfGroupAndChildrenNames = [];
for (i = 0; i < ListOfGroupAndChildren.length; i++) { 
	ListOfGroupAndChildrenNames.push([GetGroupNameBySysid(ListOfGroupAndChildren[i])]);
	}
for (x = 0; x < ListOfGroupAndChildrenLevel.length; x++) {
		gs.print("SCOTT111: GROUPS=" + ListOfGroupAndChildrenLevel[x] + " " + ListOfGroupAndChildrenNames[x] + "-----------------------------------  sysid=" + ListOfGroupAndChildren[x]);
	}
//gs.print("SCOTT111: ListOfGroupAndChildrenNames=" + ListOfGroupAndChildrenNames);
//gs.print("SCOTT111: ListOfGroupAndChildrenLevel=" + ListOfGroupAndChildrenLevel);

/* ==================================================================== functions =============================*/

function GetChildren(group_sys_id)  {
 var Childgroups = [];
 var Thesys_id = "";
 var gr = new GlideRecord('sys_user_group');
 gr.addQuery('parent', group_sys_id);
 gr.query();
 while (gr.next()) {
  Thesys_id = gr.sys_id.toString();    // must assign this temp var using toString !
  Childgroups.push(Thesys_id);
  }
 //gs.print("SCOTT111: Childgroups=" + Childgroups.toString());
 return Childgroups;
}

function GetGroupSysidByName(group) {        
 var gr = new GlideRecord("sys_user_group");
    if(gr.get('name', group)) 
    {
		//gs.print('SCOTT111: GetGroupSysidByName name is ' + gr.name + ' sys_id=' + gr.sys_id);
		return gr.sys_id;
    }
}

function GetGroupNameBySysid(ThesysID) {       
 var gr = new GlideRecord("sys_user_group");
    if(gr.get('sys_id', ThesysID)) 
    { return gr.name; }
}
8 REPLIES 8

The SN Nerd
Giga Sage
Giga Sage

Why don't you just create a table level read ACL with the condition

Assignment Group IS DYNAMIC One of my Groups

 

This will pick up the parent hierarchy too.

You could do this as a Before Query Business rule too.

 


ServiceNow Nerd
ServiceNow Developer MVP 2020-2022
ServiceNow Community MVP 2019-2022

Ankit P
Mega Guru

Hi,

Please create a Before Query Business rule on Incident table with condition gs.hasRole('itil');

And add below script:

var user = gs.getUserID();
	
	var grMember = new GlideRecord('sys_user_grmember');
	grMember.addQuery('user', user);
	grMember.query();
	grMember.next();
	var firstSysId = grMember.group;
	var query1 = "assignment_group=" + firstSysId;
	var query2 = "";
	var query3 = "";
	
	var grMember2 = new GlideRecord('sys_user_grmember');
	grMember2.addQuery('user', user);
	grMember2.query();
	while(grMember2.next()){
			query2 += "^ORassignment_group=" + grMember2.group;
		}
	
	var grMember3 = new GlideRecord('sys_user_grmember');
	grMember3.addQuery('user', user);
	grMember3.query();
	while(grMember3.next()){
		var childgr = new GlideRecord('sys_user_group');
		childgr.addQuery('parent', grMember3.group);
		childgr.query();
		while(childgr.next()){
			query3 += "^ORassignment_group=" + childgr.sys_id;
		}
	
	}
	var finalQuery = query1 + query2 + query3;
	current.addEncodedQuery(finalQuery.toString());

And it will work as expected. Get you the incidents assigned to your groups and your group's child groups.

 

Please mark correct if it helps, to make it available for other users.

scott111
Kilo Expert

This did not work.  It resulted in a query with duplicate sys_ids and a list of 7 groups. The group that the current user is a member of was not in the output.

I was able to eliminate the duplication and it seems to work well. Continuing testing.