…thoughts on ServiceNow and digital transformation

Post

ServiceNow HRSD COE Security Policy Audit Functionality


It’s a good idea to have regular audits/reviews of COE security policies and HR groups to ensure that the permissions are setup as intended. This custom functionality provides a mechanism of performing audits/reviews with an HR case.

The basic idea of this functionality is

  • An HR case is created to perform the audit
  • HR Tasks to review group membership are created for the managers of each group that is contained in an COE Security Policy.
  • HR Tasks to review the CEO Security Policies are created.

Here’s how to set it up:

HR Service

Create an HR Service called HRSD Audit in the HRIT Operations COE

Script Include

Create a script include HRSDAuditUtils as below

var HRSDAuditUtils = Class.create();
HRSDAuditUtils.prototype = {
    initialize: function() {},
    getHRSDGroupsInCOESecurityPolicies: function() {
        //returns and array of sys_ids of groups used in active COE Security Policies

        var groups = [];
        var polgroupGR = new GlideRecord("sn_hr_core_m2m_security_policy_group");
        polgroupGR.addEncodedQuery('security_policy.active=true');
        polgroupGR.query();
        while (polgroupGR.next()) {
            var groupSysId = polgroupGR.group.sys_id.toString();
            if (groups.indexOf(groupSysId) == -1) {
                groups.push(groupSysId);

            }
        }
        return groups;

    },
    getGroupMembersString: function(groupGR) {
        //takes a group GlideRecord as input and returns the members of the group as a string with line breaks
        var result = '';
        var memberGR = new GlideRecord('sys_user_grmember');
        memberGR.addQuery('group', groupGR.getUniqueValue());
        memberGR.query();
        while (memberGR.next()) {
            result += memberGR.user.email.getDisplayValue() + '<br/>';
        }
        return result;
    },
    previewGroupsForMembershipAudit: function() {
        //displays the groups that will be audited in an info message.  Called from a UI Action	

        var groups = this.getHRSDGroupsInCOESecurityPolicies();
        var htmlResult = 'Groups for membership audit: <br/>';
        var groupGR = new GlideRecord('sys_user_group');
        groupGR.addQuery('sys_id', 'IN', groups);
        groupGR.orderBy('name');
        groupGR.query();
        while (groupGR.next()) {
            htmlResult += groupGR.getDisplayValue() + ': ' + groupGR.manager.getDisplayValue() + '<br/>';
        }
        gs.addInfoMessage(htmlResult);
    },

    createAuditTaskforGroup: function(groupGR, parentCaseGR) {
        var taskGR = new GlideRecord('sn_hr_core_task');
        taskGR.initialize();
        taskGR.setValue('template', '46e00eff87f6d210f7efc8460cbb354c'); //HRSD Audit Task Template
        taskGR.setValue('short_description', 'Audit membership of group ' + groupGR.getDisplayValue());
        taskGR.setValue('state', 10);
        taskGR.setValue('hr_task_type', 'mark_when_complete');
        taskGR.setValue('parent', parentCaseGR.getUniqueValue());
        taskGR.setValue('assignment_group', parentCaseGR.getValue('assignment_group'));

        var description = 'Members of group ' + groupGR.getDisplayValue() + ' as of ' + new GlideDateTime() + ' UTC <br/>';
        description += this.getGroupMembersString(groupGR);

        taskGR.setValue('rich_description', description);
        taskGR.insert();

    },
    createGroupAuditTasks: function(parentCaseGR) {

        //create the audit tasks for the active groups
        var groups = this.getHRSDGroupsInCOESecurityPolicies();
        var groupGR = new GlideRecord('sys_user_group');
        groupGR.addQuery('sys_id', 'IN', groups);
        groupGR.query();
        while (groupGR.next()) {

            var inputs = {};
            inputs['group_gliderecord'] = groupGR; // GlideRecord of table: sys_user_group 
            inputs['case_gliderecord'] = parentCaseGR; // GlideRecord of table: sn_hr_core_case_operations 

            sn_fd.FlowAPI.getRunner().subflow('sn_hr_core.hrsd_audit__create_group_audit_task').inBackground().withInputs(inputs).run();

        }

    },

    createCOESecurityPolicyAuditTasks: function(parentCaseGR) {
        //Creates an audit task for each active COE Security Policy
        var coePolicyGR = new GlideRecord("sn_hr_core_coe_security_policy");
        coePolicyGR.addEncodedQuery("active=true");
        coePolicyGR.query();
        while (coePolicyGR.next()) {
            var taskGR = new GlideRecord('sn_hr_core_task');
            taskGR.initialize();
            taskGR.setValue('template', '166367bb87bad21077df42e50cbb35c8'); //HRSD Audit COE Security
            taskGR.setValue('short_description', 'HRSD Audit Task: Audit COE Sec Policy ' + coePolicyGR.policy_name.getDisplayValue());
            taskGR.setValue('state', 10);
            taskGR.setValue('hr_task_type', 'mark_when_complete');
            taskGR.setValue('parent', parentCaseGR.getUniqueValue());
            taskGR.setValue('assignment_group', parentCaseGR.getValue('assignment_group'));
            taskGR.setValue('correlation_id', coePolicyGR.getUniqueValue());

            var description = 'Go to the COE Security Policy record, verify the configuration and then click on Close Audit Task for this Policy related link <br/> <a href="/nav_to.do?uri=sn_hr_core_coe_security_policy.do?sys_id=' + coePolicyGR.getUniqueValue() + '" target="_blank">' + coePolicyGR.policy_name.getDisplayValue() + '</a>';
            taskGR.setValue('rich_description', description);
            taskGR.insert();
        }
    },
    getCOESecurityPolicySnapshot: function(policyGR) {
        //takes a snapshot of the current values of a given COE Security Policy
        var snapshot = '';
        snapshot += 'Policy: ' + policyGR.policy_name.getDisplayValue() + '\n';
        snapshot += 'Snapshot Date: ' + new GlideDateTime() + ' UTC \n';
        snapshot += 'COE: ' + policyGR.coe.getDisplayValue() + '\n';
        snapshot += 'Applies to all child COEs: ' + policyGR.applies_to_all_child_coes.getDisplayValue() + '\n';
        snapshot += 'Applies to all services: ' + policyGR.all_services.getDisplayValue() + '\n';
        snapshot += 'Services: ' + policyGR.services.getDisplayValue() + '\n';
        snapshot += 'Short description: ' + policyGR.short_description.getDisplayValue() + '\n';
        snapshot += 'Applies when ' + policyGR.applies_when.getDisplayValue() + '\n';
        snapshot += 'Groups: \n';

        var groupsGR = new GlideRecord('sn_hr_core_m2m_security_policy_group');
        groupsGR.addQuery('security_policy', policyGR.getUniqueValue());
        groupsGR.query();
        while (groupsGR.next()) {
            snapshot += '     ' + groupsGR.group.name.getDisplayValue() + '\n';
        }
        return snapshot;
    },
    closeCOESecurityPolicyAuditTask: function(policyGR) {
        //takes a snapshot and closes any open audit tasks for the given COE security policy
        var hrTaskGR = new GlideRecord('sn_hr_core_task');
        hrTaskGR.addQuery('correlation_id', policyGR.getUniqueValue());
        hrTaskGR.addActiveQuery();
        hrTaskGR.query();
        gs.addInfoMessage('Open Audit Tasks to be closed: ' + hrTaskGR.getRowCount());
        while (hrTaskGR.next()) {

            var snapshot = this.getCOESecurityPolicySnapshot(policyGR);
            var currentNotes = hrTaskGR.close_notes.getDisplayValue() + '\n';
            currentNotes += '------------------------------------\n';
            currentNotes += 'Snapshot of COE Sec Policy \n';
            currentNotes += '------------------------------------\n';

            hrTaskGR.setValue('close_notes', currentNotes + snapshot);
            hrTaskGR.setValue('u_audit_task_result', 'pass');
            hrTaskGR.setValue('state', 3);
            hrTaskGR.update();

        }
    },


    type: 'HRSDAuditUtils'
};

HR Templates

Create HR Templates

  • HRSD Audit for on the HRIT Operations table for the case
  • HRSD Group Membership Audit Task on the HR Task Table
  • HRSD Audit Change Task on the HR Table

HR Task Table

Create a choice field called Audit Result with possible values of Pass and Fail.

Employee Form

Create an Employee Form to collect the group member confirmation from group managers.

The first question is a choice field with Yes and No as answers. The second question is a string field.

Subflow

Create a Flow which creates the HR Task for Collect Employee Input and then retrieves the results. See this article on retrieving data from Employee Forms https://incident.do/2024/11/08/servicenow-hrsd-updating-hr-tasks-or-hr-cases-with-employee-forms/

The inputs are

  • The gliderecord of the audit case (HRIT Operations)
  • The gliderecord of the group for which membership should be verified.

Step 1

short description script

return 'Confirm membership of group ' + fd_data.subflow_inputs.group_gliderecord.getDisplayValue();

description script

var html = 'Please confirm that the members of the group ' + fd_data.subflow_inputs.group_gliderecord.getDisplayValue() + ' are correct (members as of ' + new GlideDateTime() + ' UTC )<br/><br/>';
html += new HRSDAuditUtils().getGroupMembersString(fd_data.subflow_inputs.group_gliderecord);
return html;

Step 2

Step 3 and 4 – If the manager confirmed the members of the group, update the HR Task Audit Result field to Pass

Step 5 and 6 – If the manager requested changes to the group membership then update the HR Task Audit Result to Fail

Step 7 and 8 – Get the string entered by the manager on the Employee Form and create a new HR Task which contains the string

Store the group’s sys_id in the Correlation ID field of the new HR Task so that we can send another group membership audit task for the group after we made the modifications.

UI Actions

Preview Groups for Membership Audit

Create a UI Action to preview the groups contained in COE Security Policies. This allows the person working the audit case to look at the groups and the managers before sending the group membership audit tasks

action.setRedirectURL(current);
new HRSDAuditUtils().previewGroupsForMembershipAudit();

Create Group Membership Audit Tasks

Create another UI Action to call the Subflow created above

action.setRedirectURL(current);
new HRSDAuditUtils().createGroupAuditTasks(current);
gs.addInfoMessage('HRSD Group Audit Tasks created.  Refresh the page to see all tasks');

Create HR Grp Membership Audit Task

This UI Action is on the HR Task table, it is used when a manager requests changes to group membership. Once the changes are complete, this UI Action creates another Group Membership Audit Task for the manager to complete.

		//launches the Create HRSD Audit Task subflow
		action.setRedirectURL(current);
		var groupGR = new GlideRecord('sys_user_group');
		groupGR.get(current.getValue('correlation_id')); //group sys_id is stored in the correlation_id field.

				var caseGR = new GlideRecord('sn_hr_core_case_operations');
		caseGR.get(current.getValue('parent'));

		if (groupGR.isValidRecord() && caseGR.isValidRecord()) {
		    createTask();
		}

		function createTask() {

		    gs.addInfoMessage('A new HRSD Audit Task will be created for ' + groupGR.getDisplayValue());
		    var inputs = {};
		    inputs['group_gliderecord'] = groupGR; // GlideRecord of table: sys_user_group 
		    inputs['case_gliderecord'] = caseGR; // GlideRecord of table: sn_hr_core_case_operations 

		    // Execute Synchronously: Run in foreground. Code snippet has access to outputs.
		    sn_fd.FlowAPI.getRunner().subflow('sn_hr_core.hrsd_audit__create_group_audit_task').inForeground().withInputs(inputs).run();

		}

Create COE Security Policy Audit Tasks

This UI Policy creates an audit task for each active COE Security Policy

action.setRedirectURL(current);
new HRSDAuditUtils().createCOESecurityPolicyAuditTasks(current);
gs.addInfoMessage('HRSD COE Security Policy Audit Tasks created');

Close Audit Task for this Policy

This is an action on the COE Security Policy table to close the audit task. It takes a snapshot of the current configuration of the policy and writes it to the Close Notes of the HR Task.

action.setRedirectURL(current);
new HRSDAuditUtils().closeCOESecurityPolicyAuditTask(current);