NetSuite
This page outlines the process of configuring and using the NetSuite API through Zilla to sync Users, and roles.
Note: Netsuite does not provide an out of the box REST API. A REST API can be created by installing a small piece of software called “RESTlet” in your NetSuite installation. This RESTlet will create the API endpoint required for the API integration. Zilla has built a RESTlet capable of extracting accounts and roles in Netsuite. Please reach out to your Zilla Security contact for additional details.
Summary
Pre-Install
Zilla supports optionally syncing granular permissions within roles. Below are 2 RESTLet scripts customer can choose accordingly. Save one of the script into a file and name it ZillaUAR.js -
Sync roles only, not granular permissions within a role -
/**
* @NApiVersion 2.1
* @NModuleScope Public
* @NScriptType Restlet
*
*/
define(["N/search"],
function (search) {
/**
* getEmployees - gets a list of all active employees with log in access
* @returns JSON payload of employees, attributes, and roles
*
*/
function getEmployees() {
var _employeeSearchObj = search.create({
type: "employee",
filters:
[
["giveaccess","is","T"],
"AND",
["isinactive","is","F"]
],
columns:
[
search.createColumn({name: "internalid", label: "Internal ID"}),
search.createColumn({name: "externalid", label: "External ID"}),
search.createColumn({
name: "entityid",
sort: search.Sort.ASC,
label: "Name"
}),
search.createColumn({name: "email", label: "Email"}),
search.createColumn({name: "firstname", label: "First Name"}),
search.createColumn({name: "lastname", label: "Last Name"})
]
});
// var searchResultCount = _employeeSearchObj.runPaged().count;
// log.debug("_employeeSearchObj result count",searchResultCount);
var _employeeArray = [];
_employeeSearchObj.run().each(function(result){
// .run().each has a limit of 4,000 results
_employeeArray.push({
"id": result.getValue({name: "internalid"}),
"externalid": result.getValue({name: "externalid"}),
"status": "Active",
"email": result.getValue({name: "email"}),
"firstname": result.getValue({name: "firstname"}),
"lastname": result.getValue({name: "lastname"}),
"roles": getRolesByEmployee(result.getValue({name: "internalid"}))
})
return true;
});
return _employeeArray;
}
/**
* getRolesByEmployee - gets all roles assigned to a specific employee
* @param _employeeID - internal ID of the employee record
* @returns array of roles associated with the employee
*/
function getRolesByEmployee(_employeeID) {
var _employeeSearchObj = search.create({
type: "employee",
filters:
[
["internalid", "anyof", _employeeID]
],
columns:
[
search.createColumn({name: "role", label: "Role"})
]
});
// var searchResultCount = _employeeSearchObj.runPaged().count;
// log.debug("_employeeSearchObj result count",searchResultCount);
var _roleArray = [];
_employeeSearchObj.run().each(function(result){
// .run().each has a limit of 4,000 results
_roleArray.push(result.getText("role"));
return true;
});
return _roleArray;
}
return {
/* Restlet Entry Point */
get:
/**
* <function description>
* @param context
* @returns ???
*
*/
function _get(context) {
return getEmployees();
}
};
}
);
Sync both roles and granular permissions within a role -
/**
* @NApiVersion 2.1
* @NModuleScope Public
* @NScriptType Restlet
*
*/
define(["N/search"],
function (search) {
/**
* Retrieves a list of all active employees who have access to the system, along with their details and roles.
*
* This function performs a search to find employees who are active and have access enabled. It returns an
* array of employee objects, including their internal ID, external ID, email, first name, last name, and the
* roles assigned to each employee with corresponding permissions.
*
* @function getEmployees
* @returns {Array<Object>} An array of employee objects where each object contains:
* - `id` {string}: The internal ID of the employee.
* - `externalid` {string}: The external ID of the employee (if available).
* - `status` {string}: The employee's status, always set to "Active" in this function.
* - `email` {string}: The email address of the employee.
* - `firstname` {string}: The first name of the employee.
* - `lastname` {string}: The last name of the employee.
* - `roles` {Array<Object>}: An array of role-permission objects, as fetched by `getAccessInfoByEmployeeId`.
*
* Example:
* [
* {
* id: "123",
* externalid: "ext-001",
* status: "Active",
* email: "john.doe@example.com",
* firstname: "John",
* lastname: "Doe",
* roles: [
* { role: "Admin", permissions: [ { permission: "edit_users", level: "1" } ] }
* ]
* },
* {
* id: "124",
* externalid: "",
* status: "Active",
* email: "jane.smith@example.com",
* firstname: "Jane",
* lastname: "Smith",
* roles: [
* { role: "User", permissions: [ { permission: "view_dashboard", level: "3" } ] }
* ]
* }
* ]
*/
function getEmployees() {
var employeeSearchObj = search.create({
type: search.Type.EMPLOYEE,
filters:
[
["giveaccess", "is", "T"],
"AND",
["isinactive", "is", "F"]
],
columns:
[
search.createColumn({name: "internalid", label: "Internal ID"}),
search.createColumn({name: "externalid", label: "External ID"}),
search.createColumn({
name: "entityid",
sort: search.Sort.ASC,
label: "Name"
}),
search.createColumn({name: "email", label: "Email"}),
search.createColumn({name: "firstname", label: "First Name"}),
search.createColumn({name: "lastname", label: "Last Name"})
]
});
// var searchResultCount = employeeSearchObj.runPaged().count;
// log.debug("employeeSearchObj result count",searchResultCount);
var employeeArray = [];
var employeeSearchResult = employeeSearchObj.run();
employeeSearchResult.each(function (result) {
// .run().each has a limit of 4,000 results
employeeArray.push({
"id": result.getValue({name: "internalid"}),
"externalid": result.getValue({name: "externalid"}),
"status": "Active",
"email": result.getValue({name: "email"}),
"firstname": result.getValue({name: "firstname"}),
"lastname": result.getValue({name: "lastname"}),
"roles": getAccessInfoByEmployeeId(result.getValue({name: "internalid"}))
})
return true;
});
// log.debug(employeeArray[0]);
// log.debug(employeeArray);
return employeeArray;
}
/**
* Retrieves all roles assigned to a specific employee and their corresponding permissions.
*
* This function fetches the roles associated with the given employee ID and then gathers
* the permissions and access levels for each role. The result is returned as an array of
* role-permission objects.
*
* @function getAccessInfoByEmployeeId
* @param {string} employeeID - The internal ID of the employee record.
* @returns {Array<Object>} An array of objects, where each object contains:
* - `role` {string}: The name of the role.
* - `permissions` {Array<Object>}: An array of permission objects with corresponding access levels.
*
* Example:
* [
* {
* role: "Admin",
* permissions: [
* { permission: "view_reports", level: "1" },
* { permission: "edit_users", level: "2" }
* ]
* },
* {
* role: "User",
* permissions: [
* { permission: "view_dashboard", level: "3" }
* ]
* }
* ]
*/
function getAccessInfoByEmployeeId(employeeID) {
// log.debug('Fetching roles');
var roles = getRolesByEmployeeId(employeeID)
var rolePermission = []
// log.debug('Fetching permissions under roles');
for (var i = 0; i < roles.length; i++) {
rolePermission.push({
role: roles[i].role,
permissions: getPermissionsAndLevelByRoleName(roles[i].role)
});
}
// log.debug('Fetched permissions under all roles');
// log.debug(rolePermission);
return rolePermission
}
/**
* Retrieves all roles associated with a given employee by their internal ID.
*
* This function performs a search query on the employee record to find roles assigned to the
* employee with the specified ID. It returns an array of role objects, where each object contains
* the name of a role.
*
* @function getRolesByEmployeeId
* @param {string} employeeID - The internal ID of the employee record.
* @returns {Array<Object>} An array of objects where each object contains:
* - `role` {string}: The name of the role assigned to the employee.
*
* Example:
* [
* { role: "Admin" },
* { role: "User" }
* ]
*/
function getRolesByEmployeeId(employeeID) {
var employeeRoleSearchObj = search.create({
type: search.Type.EMPLOYEE,
filters:
[
["internalid", "anyof", employeeID]
],
columns:
[
search.createColumn({name: "role", label: "Role"})
]
});
var roleArray = [];
employeeRoleSearchObj.run().each(function(result){
roleArray.push({
"role": result.getText("role")
});
return true;
});
// log.debug(roleArray);
// log.debug('Fetched all roles');
return roleArray;
}
/**
* Retrieves the permissions and their associated access levels for a given role.
*
* This function searches for permissions assigned to the specified role name and returns an
* array of objects. Each object contains a permission and its corresponding access level.
*
* @function getPermissionsAndLevelByRoleName
* @param {string} roleName - The name of the role for which permissions are being fetched.
* @returns {Array<Object>} An array of objects where each object contains:
* - `permission` {string}: The name of the permission assigned to the role.
* - `level` {string}: The access level of the permission (e.g., "1", "3").
*
* Example:
* [
* { permission: "edit_users", level: "1" },
* { permission: "view_reports", level: "3" }
* ]
*/
function getPermissionsAndLevelByRoleName(roleName) {
var permissionObject = search.create({
type: search.Type.ROLE,
filters:
[
["name", "is", roleName]
],
columns:
[
search.createColumn({name: "permission", label: "Permission"}),
search.createColumn({name: "level", label: "Level"})
]
});
var permissionArray = [];
permissionObject.run().each(function(result) {
permissionArray.push({
'permission': result.getValue("permission"),
'level': result.getValue("level")
});
return true;
});
// log.debug(permissionArray);
// log.debug('Fetched all permission');
return permissionArray;
}
return {
/* Restlet Entry Point */
get:
/**
* <function description>
* @param context
* @returns ???
*
*/
function _get(context) {
return getEmployees();
}
};
}
);
Setup in Netsuite
1. Create a Role
Go to Setup > Users/Roles > Manage Roles > New
Assign the role scopes:
Lists -> Employees
Lists -> Employees Records
Lists -> Subsidiaries
Lists -> Integration Applications
Setup -> REST Web Services
Setup -> Login Using Access Tokens
Setup -> Bulk Manage Roles
Setup -> View Login Audit Trail
Give the role access to all subsidiaries
2. Create a New User
Go to Lists->Employees->Employees->New
Enter a Name and Email
Check GIVE ACCESS in the Access subtab and assign a password to the account
Assign the Role made in the previous step to this user
3. Create an Integration in NetSuite
Go to setup->integration->manage integrations->new create a new Zilla integration
Check 'token based authentication' and 'RESTLets', uncheck 'TBA Authorization Flow' and 'authorization code grant', then save
Copy Client ID and Secret for later use
4. Create an Access Token
Go to Setup -> Users/Roles -> Access Tokens -> New
Generate a Consumer Token assigned to the user you created previously
Store the access secret and token for later use
5. Upload Script
Upload the script (Customization->Scripting->Scripts->New).
Note: Make sure the script name and name of the script deployment are the same as the name (ZillaUAR.js) of the file (include the .js).
Make the created user an owner of the script
Select Deploy Script
Add the Zilla integration Role to script deployment. Make status 'released'
Add your subsidiary to the script
Copy the External URL that appears once you save it for later use
Set up NetSuite Application Integration on Zilla:
Visit the Zilla application and login using your admin credentials and then click
Add Application
in the top right.
A window with a search bar appears, type in
netsuite
in the search bar. NetSuite application entry will appear at the top of the list, clickAdd to Applications
button to the right.
Fill in the form with appropriate details and then click
Add to Applications
button.
A detailed view of NetSuite application appears. Click
Sync now
or thegear icon
in top right corner to configure your application.
Enable API Integration option from the dialog.
Add the configuration details, check the information below this screenshot.
Enter all of your information into the corresponding fields in Zilla:
Consumer Key: Take from “3. Create an Integration in NetSuite” Section above
Consumer Secret: Take from “3. Create an Integration in NetSuite” Section above
Signature Method: value will be:
HMAC-SHA256
Realm: AccountID value found in the EXTERNAL URL value. For example, if the URL is https://123.restlets.api.netsuite.com … then the Realm value is 123.
If you’re using sandbox environment, the Realm value needs to be 123_SB (with an underscore, not a hyphen). As an example, if my external URL is https://123-sb1.restlets.api.netsuite.com… then this value needs to be 123-SB1
Users Endpoint: Taken from the Script Deployment record’s EXTERNAL URL value.
Access Token: Token ID from Create a New Access Token Section
Token Secret: Token Secret from Create a New Access Token Section
Sync permission associated to roles ? : Default is No. If set to No, it will not synchronise permissions associated with roles. If set to Yes, it will synchronise the permissions of all roles..
Once the configuration is complete click
Next/Sync Now
.
Click Next and it will begin the sync. The Click
Done
on the next pop-up that appears. After reviewing the sync summary pop-up, clickClose
.
Once the sync has been completed all accounts should appear under the
Accounts
tab.