Name
global.OnCallSecurityNG
Description
New security model checks for a on-call rotas, rotations, and associated data. Access Model rota_admin role gives access to manipulate all aspects of Rotas roster_admin (deprecated, old model) role gives access to manipulate all aspects of Rotas rota_manager role delegated for a specific group gives access for managing a group s Rotas (and associated data) and the Group s manager can manage a group s Rotas (and associated data)
Script
var OnCallSecurityNG = Class.create();
OnCallSecurityNG.prototype = {
initialize: function() {
this.log = new GSLog("com.snc.on_call_rotation.log.level", this.type);
},
setDebug: function(value) {
this.debug = value;
},
/**
* Check if user has access to Rotas and Rota related records for the given group. Check is based on 3 things:
*
* 1. Check if the user is a member of the given group.
* 2. Check if the user is the manager for the given group or has the rota_manager role.
* 3. Check if the user has the rota_admin role.
*
* @param String groupId
*
* @return boolean hasAccess
*/
rotaAccess: function(group) {
var isMember = this._isMemberOf(group); // is user a member of the rota's group?
var isManager = this.rotaMgrAccess(group);
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[rotaAccess] isMember: " + isMember + " isManager: " + isManager);
return (isMember || isManager);
},
/**
* Check if the user is the manager for the given group or has the rota_manager role.
*
* @param String groupSysId
*
* @return boolean hasMgrAccess
*/
rotaMgrAccess: function(groupSysId) {
if (this.rotaAdminAccess())
return true;
if (JSUtil.nil(groupSysId))
return this.isManagerOfAnyGroup();
var groupGr = this._getGroupRecord(groupSysId);
if (!JSUtil.nil(groupGr)) {
var isAccessAllowed = this._isAccessAllowed(groupGr);
if (isAccessAllowed) return true;
}
var groupSettingGr = this._getGroupSettingRecord(groupSysId);
if (!JSUtil.nil(groupSettingGr))
return this.isRotaManagerOfGroup(groupSettingGr);
return false;
},
/**
* Will return true if the user has any of the following roles: rota_admin, rota_manager or
* the currently logged in user is a manager of any group
*
* @return boolean
*/
rotaMgrAny: function() {
if (this.rotaAdminAccess())
return true;
// does user have rota manager role
if (this._hasRole('rota_manager'))
return true;
return this.isManagerOfAnyGroup();
},
rotaMgrAccessByScheduleSpan: function(cmnScheduleSpanGr) {
var answer = false;
if (!cmnScheduleSpanGr.schedule.nil()) {
if (cmnScheduleSpanGr.schedule.type + '' === 'roster' && cmnScheduleSpanGr.schedule.document + '' === 'cmn_rota') {
var cmnRotaGr = new GlideRecord('cmn_rota');
cmnRotaGr.addQuery('schedule', cmnScheduleSpanGr.schedule + '');
cmnRotaGr.query();
if (cmnRotaGr.next())
answer = this.rotaMgrAccess(cmnRotaGr.group + '');
}
}
return answer;
},
rotaManagerAccessBySchedule: function(scheduleGr) {
var answer = false;
if (scheduleGr.type + '' === 'roster' && scheduleGr.document + '' === 'cmn_rota') {
var cmnRotaGr = new GlideRecord('cmn_rota');
cmnRotaGr.addQuery('schedule', scheduleGr.getUniqueValue() + '');
cmnRotaGr.query();
if (cmnRotaGr.next())
answer = this.rotaMgrAccess(cmnRotaGr.group + '');
}
return answer;
},
isManagerOfAnyGroup: function() {
var userSysId = this._getUserID();
var ga = this.getManagedGroups(userSysId, true);
ga.next();
if (parseInt(ga.getAggregate('COUNT'), 10) > 0)
return true;
// Get groups where current user has been delegated rota_manager.
var gaHasRole = this.getDelegatedGroups(userSysId);
gaHasRole.next();
// Get groups where current user has rota manager from any preference record.
var grpsManagedFromPreference = this.getManagedGroupsByPreferences(userSysId);
return (parseInt(gaHasRole.getAggregate('COUNT'), 10) > 0) || (grpsManagedFromPreference && grpsManagedFromPreference.length > 0);
},
/**
* Check if the currently logged in user has access to any roster schedule span (that is not time off)
* that is based on the given schedule.
*
* @param GlideRecord schedule
*
* @return boolean scheduleAccess
*/
rotaAccessSchedule: function( /* cmn_schedule */ current) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[rotaAccessSchedule] called for schedule: " + current.getUniqueValue() + ", user " + this._getUserID());
if (this.rotaMgrAny())
return true;
// search through this schedule's roster spans
var gr = new GlideRecord('roster_schedule_span');
gr.addQuery('schedule', current.getUniqueValue());
gr.setWorkflow(false);
gr.query();
var timeOff = false;
while (gr.next()) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[rotaAccessSchedule] name: " + gr.name);
if (gr.type + '' === 'time_off') {
// Time off entries have blank groups, deal with later
timeOff = true;
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[rotaAccessSchedule] time_off entry found");
continue;
}
if (this.rotaAccess(gr.group))
return true; // you can view this schedule
}
if (!timeOff)
return false;
// this should never happen
if (this._isAccessSameGroupAs(current.name))
return true;
return false;
},
/**
* Will return true if the current logged in user has rota_admin role or if the user
* is the manager of any of the spans related to the given schedule or has the rosta_manager role.
*
* @param GlideRecord schedule
*
* @return boolean
*/
rotaMgrSchedule: function( /* cmn_schedule */ current) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[rotaMgrSchedule] called for schedule: " + current.getUniqueValue() + ", user " + this._getUserID());
if (this.rotaAdminAccess())
return true;
// search through this schedule's roster spans
var gr = new GlideRecord('roster_schedule_span');
gr.addQuery('schedule', current.getUniqueValue());
gr.setWorkflow(false);
gr.query();
var timeOff = false;
while (gr.next()) {
if (gr.type + '' === 'time_off') {
// Time off entries have blank groups, deal with later
timeOff = true;
continue;
}
if (this.rotaMgrAccess(gr.group))
return true; // you can write to this schedule
}
if (!timeOff)
return false;
// this should never happen
if (this._isMgrSameGroupAs(current.name))
return true;
return false;
},
/**
* Checks if the user logged in can create a schedule based on one of these conditions:
*
* 1) Has rota_admin role
* 2) Has rota_manager role && is manager of a group
*
* @return boolean
*/
canCreateSchedule: function() {
if (gs.hasRole('rota_admin'))
return true;
return this.isManagerOfAnyGroup();
},
/**
* Gets an array of groups sys ids for the current user
*
* @return array of strings
*/
getGroups: function() {
var userSysId = this._getUserID();
var groupSysIds = {};
if (gs.hasRole('rota_manager') || gs.hasRole('rota_admin')) {
var gaGroup = this.getManagedGroups(userSysId, !gs.hasRole("rota_admin"));
while (gaGroup.next())
groupSysIds[gaGroup.getValue('sys_id')] = '';
// Add groups where current user has been delegated rota_manager.
var gaHasRole = this.getDelegatedGroups(userSysId);
while (gaHasRole.next())
groupSysIds[gaHasRole.getValue('granted_by')] = '';
}
var groupSysIdArray = Object.keys(groupSysIds);
if (this.log.atLevel(global.GSLog.DEBUG))
this.log.debug("[getGroups] groupSysIdArray: " + groupSysIdArray);
return groupSysIdArray;
},
getGroupsRefQual: function() {
var groupsArr = this.getGroups();
var managedGroupsArr = this.getManagedGroupsByPreferences();
var groupSysIds = groupsArr.concat(managedGroupsArr).join(",");
var refQual = "active=true^manager=" + this._getUserID();
if (groupSysIds !== "")
refQual += "^ORsys_idIN" + groupSysIds;
refQual += "^EQ";
if (this.log.atLevel(global.GSLog.DEBUG))
this.log.debug("[getGroupsRefQual] refQual: " + refQual);
return refQual;
},
/**
* Gets groups that are managed by userSysId or returns all groups for rota_admin's.
*
* @param userSysId String sys_user.sys_id current session's user sys_id
* @param ignoreRotaAdmin Boolean do not constrain the query by manager for rota_admin's
*
* @return gaGroup GlideAggregate
**/
getManagedGroups: function(userSysId, ignoreRotaAdmin) {
var gaGroup = new GlideAggregate("sys_user_group");
gaGroup.addAggregate("COUNT");
gaGroup.groupBy("sys_id");
gaGroup.setWorkflow(false);
gaGroup.addActiveQuery();
if (ignoreRotaAdmin)
gaGroup.addQuery("manager", userSysId);
gaGroup.query();
return gaGroup;
},
/**
* Gets sys_user_has_role records for groups that the user has been delegated the rota_manager role.
*
* @param userSysId String sys_user.sys_id current session's user sys_id
*
* @return gaHasRole GlideAggregate
**/
getDelegatedGroups: function(userSysId) {
var gaHasRole = new GlideAggregate("sys_user_has_role");
gaHasRole.addAggregate("COUNT");
gaHasRole.groupBy("granted_by");
gaHasRole.addActiveQuery();
gaHasRole.addQuery("user", userSysId);
gaHasRole.addQuery("role.name", "rota_manager");
gaHasRole.addQuery("granted_by", "!=", "not-applicable");
gaHasRole.addNotNullQuery("granted_by");
gaHasRole.query();
return gaHasRole;
},
/**
* Gets group ids for groups that the user has been delegated the manager role by group preferences.
*
* @param userSysId String sys_user.sys_id current session's user sys_id
*
* @return groupIds Array
**/
getManagedGroupsByPreferences: function(userSysId) {
var groupIds = [];
var groupSettingGr = new GlideRecord("on_call_group_preference");
groupSettingGr.query();
while (groupSettingGr.next()) {
var groupId = groupSettingGr.group + "";
if (groupId && this.isRotaManagerOfGroup(groupSettingGr, userSysId))
groupIds.push(groupId);
}
return groupIds;
},
/**
* Check if a user has access to to any Rota thats related to any Group the user is a member of.
*
* @param String userName
*
* @return boolean
*/
_isAccessSameGroupAs: function(userName) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_isAccessSameGroupAs] username: " + userName);
// do they have access to any group rota that this user with time-off has access to?
var gr = new GlideRecord('sys_user_grmember');
gr.addActiveQuery();
// (this is a temporary approach, ideally a schedule would be properly connected to a user record)
gr.addQuery('user.name', userName);
gr.setWorkflow(false);
gr.query();
while (gr.next()) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_isAccessSameGroupAs] access to: " + gr.group + "?");
if (this.rotaAccess(gr.group))
return true;
}
return false;
},
/**
* Check if a user has access to to any Rota thats related to any Group the user is a member of.
*
* @param String userName
*
* @return boolean
*/
_isMgrSameGroupAs: function(userName) {
// do they have manager level access to any group rota that this user with time-off has access to?
var gr = new GlideRecord('sys_user_grmember');
gr.addActiveQuery();
// (this is a temporary approach, ideally a schedule would be properly connected to a user record)
gr.addQuery('user.name', userName);
gr.setWorkflow(false);
gr.query();
while (gr.next()) {
if (this.rotaMgrAccess(gr.group))
return true;
}
return false;
},
/**
* Checks if the currently logged in user has the rota_admin role.
*
* @param String userId [Optional], to check the role for given userId, instead of logged user.
*
* @return boolean hasMgrAccess
*/
rotaAdminAccess: function(userId) {
var isRotaAdmin;
if (!userId) {
userId = this._getUserID();
isRotaAdmin = this._hasRole("rota_admin");
} else {
var user = GlideUser.getUserByID(userId);
isRotaAdmin = user.hasRole("rota_admin");
}
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[rotaAdminAccess] called for user " + userId + ' [' + isRotaAdmin + ']');
return (isRotaAdmin);
},
/**
* Setup a test user for testing some fo the functions in this script. Testable functions include:
* - _getUserID
* - _isMemberOf
*
* @param String sys_id user
*/
setTestUser: function(value) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[setTestUser] set to: " + value);
this.testUser = value; // "a9b0fd4dc611227601908ba719053cf6"; // Beth Anglin
},
/**
* Setup test roles for testing some fo the functions in this script. Testable functions include:
* - _hasRole
* - _hasRoleInGroup
*
* @param Array roles
*/
setTestRoles: function( /* list of roles */ value) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[setTestRoles] set to " + this.__keys(value));
this.testRoles = value;
},
/**
* Returns the sys_id of the currently logged in user or the test user if one is set
*
* @return String sys_id
*/
_getUserID: function() {
if (this.testUser)
return this.testUser;
return gs.getUserID();
},
/**
* Checks if the currently logged in user or the test user (if one is set) is a member of the given group
*
* @param string groupName
*
* @return boolean
*/
_isMemberOf: function(group) {
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_isMemberOf] group: " + group);
if (this.testUser) {
var User = GlideUser;
return User.getUserByID(this.testUser).isMemberOf(group);
}
return gs.getUser().isMemberOf(group);
},
/**
* Checks if the currently logged in user has the role specified or if the
* role specified exists in the list of test roles (if test roles are setup)
*
* @param String roleName
*
* @return boolean
*/
_hasRole: function(role) {
if (this.testRoles)
return this.testRoles[role];
return gs.hasRole(role);
},
/**
* Checks if the current user has the specified role within a specified group or if the
* role specified exists in the list of test roles (if test roles are setup).
* Returns true if all of the following conditions are met:
*
* 1. The logged-in user HAS the role in question
* 2. The "Granted by" field on the user role record is set to the specified group
* 3. The "inherited" field on the user role record is false
*
* @param String roleName
* @param GlideRecord group
*
* @return boolean
*/
_hasRoleInGroup: function(role, group) {
if (this.testRoles)
return this.testRoles[role + '_' + group];
return gs.hasRoleInGroup(role, group);
},
/**
* Returns a list of the properties and functions of an object
*
* @param Object
*
* @return Array
*/
__keys: function(object) {
var list = [];
for (var x in object)
list.push(x.toString());
return list;
},
_getGroupRecord: function(groupId) {
var gr = new GlideRecord('sys_user_group');
gr.setWorkflow(false);
if (gr.get(groupId))
return gr;
return null;
},
_getGroupSettingRecord: function(groupId) {
if (!JSUtil.nil(groupId)) {
var groupSettingGr = new GlideRecord("on_call_group_preference");
if (groupSettingGr.get("group", groupId))
return groupSettingGr;
}
return null;
},
getRotaManagersFromGroup: function(groupSettingGr) {
var query = "sys_idIN";
if (JSUtil.nil(groupSettingGr) || JSUtil.nil(groupSettingGr.getValue("group"))) {
query += -1;
return query;
}
var grpGr = new GlideRecord("sys_user_grmember");
grpGr.addQuery("group", groupSettingGr.getValue("group"));
grpGr.query();
var grpMembersPresent = false;
while (grpGr.next()) {
if (grpGr.user && grpGr.user.active) {
var userId = grpGr.getValue("user");
if (grpGr.hasNext())
query += (userId + ",");
else
query += userId;
grpMembersPresent = true;
}
}
if (!grpMembersPresent)
query += -1;
return query;
},
/**
* @param groupSettingGr, on_call_group_preference's GlideRecord.
* @param String userId [Optional], to check the role for given userId, instead of logged user.
*/
isRotaManagerOfGroup: function(groupSettingGr, userId) {
if (JSUtil.nil(groupSettingGr))
return false;
var rotaManagers = groupSettingGr.rota_managers;
if (rotaManagers.nil())
return false;
var rotaManagersList = rotaManagers.split(",");
var currentUserId = userId ? userId : this._getUserID();
if (rotaManagersList.indexOf(currentUserId) !== -1)
return true;
return false;
},
/*
* Checks if time off in approval spans should be viewable to user
*/
isTimeOffInApprovalSpanViewable: function(spanGr) {
return this.rotaMgrAccess(spanGr.group) || spanGr.schedule.document_key == gs.getUser().getID();
},
_isGroupPreferenceRotaManager: function(groupSysId) {
var groupPreferenceGr = this._getGroupSettingRecord(groupSysId);
if (!JSUtil.nil(groupPreferenceGr))
return this.isRotaManagerOfGroup(groupPreferenceGr);
return false;
},
canReadTriggerRule: function(triggerRuleGr) {
if (this.log.atLevel(global.GSLog.DEBUG))
this.log.debug("[canReadTriggerRule] userId: " + this._getUserID() + " - triggerRuleGr.sys_id: " + triggerRuleGr.getUniqueValue());
if (JSUtil.nil(triggerRuleGr))
return false;
// Access rights to create a new trigger rule is handled in the write ACL, and groups filtered
if (triggerRuleGr.isNewRecord())
return true;
if (this.log.atLevel(global.GSLog.DEBUG))
this.log.debug("[canReadTriggerRule] triggerRuleGr.isValidRecord(): " + triggerRuleGr.isValidRecord());
if (!triggerRuleGr.isValidRecord())
return false;
var groupSysId = triggerRuleGr.getValue("group");
if (this.checkManagerAccessOfGroup(groupSysId))
return true;
return this._isGroupPreferenceRotaManager(groupSysId);
},
canWriteTriggerRule: function(triggerRuleGr) {
if (triggerRuleGr.isNewRecord())
return true;
return this.checkManagerAccessOfGroup(triggerRuleGr.group);
},
canDeleteTriggerRule: function(triggerRuleGr) {
return this.canWriteTriggerRule(triggerRuleGr);
},
/*
* Check if the current user has rights to a group, either by being mangaer, rota_manager role or by being shift manager in the group preference
*/
checkManagerAccessOfGroup: function(groupSysId) {
var groupGr = this._getGroupRecord(groupSysId);
if (!JSUtil.nil(groupGr)) {
var isAccessAllowed = this._isAccessAllowed(groupGr);
if (isAccessAllowed) return true;
}
var groupSettingGr = this._getGroupSettingRecord(groupSysId);
if (!JSUtil.nil(groupSettingGr))
return this.isRotaManagerOfGroup(groupSettingGr);
return false;
},
// NOTE: Deprecating this method
checkManagerAcessOfGroup: function(groupSysId) {
return this.checkManagerAccessOfGroup(groupSysId);
},
getManagerAccessForGroups: function(groupSysIds) {
if (!groupSysIds)
groupSysIds = [];
var groupManagerAccessMap = {};
for (var i = 0; i < groupSysIds.length; i++) {
var group = groupSysIds[i];
groupManagerAccessMap[group] = false;
}
var groupGr = new GlideRecord('sys_user_group');
groupGr.setWorkflow(false);
groupGr.addQuery('sys_id', 'IN', groupSysIds.join());
groupGr.query();
while (groupGr.next()) {
var isAccessAllowed = this._isAccessAllowed(groupGr);
if (isAccessAllowed)
groupManagerAccessMap[groupGr.getUniqueValue()] = true;
}
var remainingGroups = [];
for (var j = 0; j < groupSysIds.length; j++) {
var groupId = groupSysIds[j];
if (!groupManagerAccessMap[groupId])
remainingGroups.push(groupId);
}
var currentUser = this._getUserID();
var groupSettingGr = new GlideRecord("on_call_group_preference");
groupSettingGr.addQuery("group", "IN", remainingGroups.join());
groupSettingGr.addNotNullQuery('rota_managers');
groupSettingGr.query();
while (groupSettingGr.next()) {
var isManager = this.isRotaManagerOfGroup(groupSettingGr, currentUser);
if (isManager)
groupManagerAccessMap[groupSettingGr.group + ''] = true;
}
return groupManagerAccessMap;
},
rotaMgrAccessForGroups: function(groupSysIds) {
if (!groupSysIds)
groupSysIds = [];
var rotaMgrAccessMap = {};
if (this.rotaAdminAccess()) {
for (var i = 0; i < groupSysIds.length; i++)
rotaMgrAccessMap[groupSysIds[i]] = true;
return rotaMgrAccessMap;
}
return this.getManagerAccessForGroups(groupSysIds);
},
getTriggerActionChoices: function() {
var fieldChoices = new GlideChoiceList();
fieldChoices.add("workflow", gs.getMessage("Workflow"));
if (gs.hasRole('rota_admin'))
fieldChoices.add("script", gs.getMessage("Script"));
return fieldChoices;
},
/*
* Admin users how can modify any shifts
* @return Array of user sys_ids
*/
getOnCallAdminList: function (getMap) {
var gaHasRole = new GlideRecord('sys_user_has_role');
gaHasRole.addQuery("user.active", true);
gaHasRole.addQuery("role.name", 'IN', 'admin,rota_admin');
gaHasRole.query();
var userMap = {};
while (gaHasRole.next()) {
userMap[gaHasRole.getValue('user')] = true;
}
if (getMap)
return userMap;
return Object.keys(userMap);
},
/*
* Shift managers - Direct group managers (sys_user_group.manager)
* @return [{
* groupId: '',
* userId: ''
* }]
*/
getDirectGroupManagers: function () {
var onCallGroups = new GlideAggregate('cmn_rota');
onCallGroups.addActiveQuery();
onCallGroups.groupBy('group');
onCallGroups.query();
var result = [];
while (onCallGroups.next()) {
var groupMap = {
groupId: onCallGroups.group + ''
};
groupMap.userId = onCallGroups.group.manager.toString();
result.push(groupMap);
}
return result;
},
/*
* Shift managers through preferences
* @return [{
* groupId: '',
* userIds: []
* }]
*/
getShiftManagersByPreferences: function () {
var groupSettingGr = new GlideRecord("on_call_group_preference");
groupSettingGr.query();
var result = [];
while (groupSettingGr.next()) {
var groupMap = {
groupId: groupSettingGr.group + ''
};
groupMap.userIds = groupSettingGr.rota_managers.toString().split(',');
result.push(groupMap);
}
return result;
},
/*
* Managers through delegation
* gaHasRole.granted_by -> provide group name for which user is delegated as manager.
* @return [{
* groupId: '',
* userIds: []
* }]
*/
getShiftManagersByDelegation: function () {
var gaHasRole = new GlideRecord("sys_user_has_role");
gaHasRole.addAggregate("COUNT");
gaHasRole.addActiveQuery();
gaHasRole.addQuery("role.name", "rota_manager");
gaHasRole.addQuery("granted_by", "!=", "not-applicable");
gaHasRole.addNotNullQuery("granted_by");
gaHasRole.query();
var result = [];
while (gaHasRole.next()) {
if (gaHasRole.getValue("granted_by")) {
var groupMap = this._findInArray(result, 'groupId', gaHasRole.getValue("granted_by"));
if (!groupMap) {
groupMap = {
groupId: gaHasRole.getValue("granted_by"),
userIds: []
};
result.push(groupMap);
}
groupMap.userIds.push(gaHasRole.user + '');
}
}
return result;
},
_findInArray: function (arr, key, value) {
return arr.find(function (item) {
return item[key] == value;
})
},
/**
* Checks if a user is additional manager of a group using API from Workforce Optimization plugin
*
* @param GlideRecord groupGr
* @param String userId
* @return boolean
*/
_isWfoManager: function(groupGr, userId) {
if (GlidePluginManager.isActive('com.snc.wfo') && groupGr) {
var wfoUtils = new sn_wfo.WFOUtils();
var res = wfoUtils.isGroupManagedByUser(groupGr, userId);
if (this.log.atLevel(GSLog.DEBUG))
this.log.debug("[_isWfoManager] group: " + groupGr.name + " | userId: " + userId + " | " + res);
return res;
}
return false;
},
_isAccessAllowed: function(groupGr) {
var manager = groupGr.getValue('manager');
var groupId = groupGr.getUniqueValue();
var userId = this._getUserID() + '';
return (manager && userId && manager === userId) || this._hasRoleInGroup('rota_manager', groupId) || this._isWfoManager(groupGr, userId);
},
type: 'OnCallSecurityNG'
};
Sys ID
3f8d56170a0a2c9660fe6df53700fe08