Scripted REST API Permissions

AmolJ
Tera Guru

I am on Zurich.

I have instance "A" and on this instance I have a user account marked as both "machine" and Internal Integration User. This user account is linked to a role which is linked to a REST API's "execute" ACL.

This ACL is linked to a scripted REST API with 2 resources. One of the resources is a POST resource used by another ServiceNow instance (instance B) to create incidents into this instance.

Neither this user through a directly assigned role, nor through a role contained by the ACL role can this user create incidents.

Yet when from instance B I post, it creates incident on instance A.

I have used GlideRecordSecure on instance A in the POST resource.

Any hints?

1 ACCEPTED SOLUTION

AmolJ
Tera Guru

The only way I could stop it is by creating another custom role and scripting it in the POST resource script to check if the user has this role or not. This role is not associated with Incident table through ACL. It's just sitting there on its own and this machine account is associated with this role. That's the whole setup now.

 

The default Deny-Unless ACL and Allow-If (can open your own incidents) ACL allowed this account to have write permissions even though I didn't specifically give it this permission.

Hence GlideRecordSecure() wasn't really helpful as these two ACLs were passing in the Access Analyzer.

ServiceNow needs to give us some other solid way to handle this.

View solution in original post

7 REPLIES 7

Hi Ankit, 

This is my script on the POST resource. I have added lots of comments so it's easier to understand. I have also checked which user creates the incidents and it happens to be the "machine" user I created. So it's interesting.. Code is as below:

(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {

    // implement resource here
    try {
        // 1. Parse the incoming JSON payload from the third-party app
        var requestBody = request.body.data;
       
        // Extract required parameters
        var callerSysId = requestBody.sys_id;
        var shortDescription = requestBody.short_description;

        // 2. Input validation: Ensure both fields are provided
        if (!callerSysId || !shortDescription) {
            response.setStatus(400); // Bad Request
            return {
                status: "error",
                message: "Missing required parameters: 'sys_id' and 'short_description' must be provided."
            };
        }

        // 3. Optional Security Check: Verify the user actually exists in ServiceNow
        var userCheck = new GlideRecord('sys_user');
        if (!userCheck.get(callerSysId)) {
            response.setStatus(404); // Not Found
            return {
                status: "error",
                message: "The provided sys_id does not match any user in the system."
            };
        }

        // 4. Create the Incident record
        var incidentGr = new GlideRecordSecure('incident');
        incidentGr.initialize();
       
        incidentGr.caller_id = callerSysId;
        incidentGr.short_description = shortDescription;
        incidentGr.contact_type = 'integration'; // Identifies the source
       
        // Insert the record and capture the new sys_id
        var newIncidentSysId = incidentGr.insert();

        // 5. Build and send the successful response back to the third party
        if (newIncidentSysId) {
            response.setStatus(201); // 201 Created
           
            // Re-query to get the human-readable Incident Number (e.g., INC0010001)
            var finalIncident = new GlideRecord('incident');
            finalIncident.get(newIncidentSysId);

            return {
                status: "success",
                message: "Incident created successfully.",
                data: {
                    sys_id: newIncidentSysId,
                    number: finalIncident.getValue('number')
                }
            };
        } else {
            // Handle unexpected database failures
            response.setStatus(500);
            return {
                status: "error",
                message: "Failed to insert incident record due to an internal database error."
            };
        }

    } catch (ex) {
        // 6. Exception handling for script crashes
        gs.error("Error in Custom Scripted REST API: " + ex.message);
        response.setStatus(500); // Internal Server Error
        return {
            status: "error",
            message: "An unexpected script error occurred.",
            detail: ex.message
        };
    }

})(request, response);

Hi @AmolJ 

 

Thanks for sharing the script.

 

The 1 thing I noticeed is:

var incidentGr = new GlideRecordSecure('incident');
incidentGr.initialize();
...
var newIncidentSysId = incidentGr.insert();

Since you've already confirmed that the authenticated user is the machine user, this appears to rule out an authentication issue.

One thing I'd test is whether the platform believes the user has create access at runtime:

gs.info('Can Create Incident: ' + incidentGr.canCreate());

Add that immediately before the insert() and check the logs.

Can you check whether canCreate() returns true or false? That will help us determine if the problem is related to ACL evaluation or the way server-side inserts from a Scripted REST API work.

 

If you found this useful, feel free to mark it as Accept as Solution and Helpful. It makes my day (and helps others too ðŸ˜‰).

Regards,
- Ankit
LinkedIn: https://www.linkedin.com/in/sharmaankith/

AmolJ
Tera Guru

The only way I could stop it is by creating another custom role and scripting it in the POST resource script to check if the user has this role or not. This role is not associated with Incident table through ACL. It's just sitting there on its own and this machine account is associated with this role. That's the whole setup now.

 

The default Deny-Unless ACL and Allow-If (can open your own incidents) ACL allowed this account to have write permissions even though I didn't specifically give it this permission.

Hence GlideRecordSecure() wasn't really helpful as these two ACLs were passing in the Access Analyzer.

ServiceNow needs to give us some other solid way to handle this.