Skip to main content

Hey there everyone!

One of our clients is deploying the Centrify suite across multiple agencies throughout New York City. The most immediately recognized overwhelming task is adding all of the domain controllers and member servers into the Centrify Cloud Tenant. To make things even more fun, each agency has its own personal Tenant that’s siloed off from all of the others. So, essentially, each agency is its own full deployment. Now within each agency are a bunch of servers that need to be added as individual Resources in the Tenant for the administrators to be able to control. The use case here is that we have thousands of servers that we need to add, then add the local administrator account of each server to the Resource as a controlled account with a vaulted password for break-glass functionality. From here, each server needs to be added to a Set (or Collection to use API terms) and assign a specific group to allow access to control the local administrator account via permissions. To do all of this manually would take someone years to get these all added into all the various Tenants. Centrify does offer a bulk import tool ability to take a CSV and import all the servers as resources into the Tenant, but that’s it. It’s not going to vault and rotate the passwords of the local admin accounts, assign permission, or anything else that we need to do.

Enter the Centrify API and a tiny node.js app.

Here is the Centrify documentation reference:

  • https://developer.centrify.com/docs/introduction– This one is the readable version with examples and descriptions of all the calls. Just a note, a lot of the examples don’t work exactly as typed out there. I’ve found a lot of typos / missing quotes etc that cause copy / paste not to work.
  • https://developer.centrify.com/reference– This one is the JavaDoc, if you will, of the Centrify REST API. Here you can find all the nitty gritty details of all the calls.

Why node? Because I’ve been playing around with it recently with some other serverless application development for some AWS Lambda stuff and I really like the simplicity and asynchronousity of it all.

The Centrify developer guides are pretty thorough with regards to explaining what to do for the different REST calls, but there’s a bunch of typos and a lot left to figure out if it’s your first time working with their API (as it was for me). So I’m going to break down how I wrote a little app to tackle this problem and explain how it all goes together.

Please keep in mind that I’m not bragging about how awesome my code is. I know it’s jenky. This is more along the lines of “hack it together and make it work” kind of code =)

Overall, my example here should help save you a ton of time figuring out a lot of this bass ackwordness of the Centrify API for REST calls using MFA and let you go and write more elegant and functional code then mine.

The general outline of the API is this:

  1. Start Authentication into the Tenant
  2. Supply Password
  3. Call for MFA
  4. Execute MFA
  5. Post response to MFA Request
  6. Get a list of all existing servers
  7. Get a list of all existing accounts
  8. Add the servers as a controlled Resource
  9. Add the server to a Set / Collection
  10. Add the Admin account to the server
  11. Add the permissions to the Admin account
  12. Close up shop

The trickiest part about this entire setup is just establishing an authorized connection to the Tenant for subsequent calls. That’s really the whole point of this blog post, is to show how to get through this. Once you have the bearer token and know how to connect, then calling  the subsequent API calls is pretty trivial and you can do whatever you want from there.

Let’s jump into the code!

FYI, at the end, I’ll have the code typed out for copy / paste monkeys =)

 

<img src=”https://images.squarespace-cdn.com/content/v1/5b46aaa78ab7226e32a31180/1574349120681-DMTLE1XO6N39KN0GR9HF/image-asset.png” alt=”” />

 

To start out, we’re going to initialize a bunch of stuff that we’re going to need.

The important parts here are all the constants:

  • tenantId = The prefix in your tenant URL. Normally it’s [TenantID].my.centrify.com
  • userId = The user Id to login to the Tenant with
  • password = password to login with
  • serverSetGUID = The GUID of the Set you want to add the servers to
  • groupAccntGUID = The GUID of the Group you want to grant permissions to the local Admin account for
  • groupAccntName = The Name of the Group
  • serversListFile = the JSON file that has all the servers you want to add into the Tenant
  • groupRights = The permissions you want to grant to the Group over the local Admin account that’s being added to the Resource. Your options are:

Owner,Manage,Delete,View,Login,Naked,UpdatePassword,UserPortalLogin,RotatePassword

Real quick, I’ll show you the contents of the servers.json file so you know what we’re pulling from:

 

Screenshot 2019-11-21 10.13.15.png
<img src=”https://images.squarespace-cdn.com/content/v1/5b46aaa78ab7226e32a31180/1574349218960-TRYP4Q014RR4EZZ0O6MO/Screenshot+2019-11-21+10.13.15.png” alt=”Screenshot 2019-11-21 10.13.15.png” />

 

Key notes here are that the ComputerClass is either “Windows” or “Linux” and the SessionType is either “Rdp” or “Ssh”. Both are case sensitive. Yeah….

So back to the code…

 

<img src=”https://images.squarespace-cdn.com/content/v1/5b46aaa78ab7226e32a31180/1574349289876-PQ01483R8OOF4TWK312Q/image-asset.png” alt=”” />

 

You’ll notice that this is the typical layout for each call to the REST API via node that I do. It’s all done in a async.waterfall, so not the most elegant. Again. This was a “get it work, and we’ll make it pretty later” attack =)

There’s nothing really crazy going on here, so I’ll just point out the important stuff. The post_options have headers that are very important and very specific in every call. So make sure you copy these exactly. The “path” variable is the REST endpoint that you’re calling. In this case, we’re calling the /Security/StartAuthentication endpoint. In the response from this POST will be all the info for the next call.

Here is the JSON response that we get back. I’ll highlight the important parts:

{ “success”: true, “Result”: { “ClientHints”: { “PersistDefault”: false, “AllowPersist”: true, “AllowForgotPassword”: false }, “Version”: “1.0”, “SessionId”: “9MXuV91gI070n7uZ1rarvjlJEO2NpuZBt_ZtDPS-cFI1”, “AllowLoginMfaCache”: false, “Challenges”: [ { “Mechanisms”: [ { “AnswerType”: “Text”, “Name”: “UP”, “PromptMechChosen”: “Enter Password”, “PromptSelectMech”: “Password”, “MechanismId”: “NzsOnFGnFsYvfKMzAkYlaN6jNgla5IVHveTkZtN1ws01” } ] }, { “Mechanisms”: [ { “AnswerType”: “StartTextOob”, “Name”: “OTP”, “PromptMechChosen”: “Sent Mobile Authenticator request to your device. Please follow the instructions to proceed with authentication or enter verification code here.”, “PromptSelectMech”: “Mobile Authenticator”, “MechanismId”: “NncwU-hGJEuWNXAxOCFSwFpdx0JAqqPPgzsTBYp147k1” }, { “AnswerType”: “StartTextOob”, “Name”: “EMAIL”, “PromptMechChosen”: “Email sent to [email protected]. Click the link or manually enter the code to authenticate.”, “PromptSelectMech”: “Email… @domain.com”, “PartialAddress”: “domain.com”, “MechanismId”: “sZ5XglEjbku29pykRmf0edab62oG5mugiZ-Gq564J6Y1” } ] } ], “Summary”: “NewPackage”, “TenantId”: “AAZ0123” }, “Message”: null, “MessageID”: null, “Exception”: null, “ErrorID”: null, “ErrorCode”: null, “IsSoftError”: false, “InnerExceptions”: null}

For the subsequent posts, you’ll see that we add the SessionId and MechanismId to the body of our post. The MechanismId is telling the next call what type of action we’re performing. The first one will be the Password mechanism, because we need to authenticate first.

 

<img src=”https://images.squarespace-cdn.com/content/v1/5b46aaa78ab7226e32a31180/1574349427385-72AA7KPW5KBO8PHA4ZFN/image-asset.png” alt=”” />

 

You’ll see that in our next step we grab the SessionId and MechanismId (or the Password Mechanism) and then pass them along in the body. Additionally, we add “Action” with the value of “Answer” and “Answer” with the value of the user password. This performs the basic login.

Here is the response:

Before posting this all though, I wrote a little loop to grab the mechanism ID of the Email MFA type since that’s the only one I want to work with. You could obviously employ your own logic here to allow users to select what they want or whatever.

 

<img src=”https://images.squarespace-cdn.com/content/v1/5b46aaa78ab7226e32a31180/1574349464637-GX2NMY68M6FF1P0SVD99/image-asset.png” alt=”” />

 

What I want to point out here is that we’re now passing the sessionId and mfa_mechId (of the MFA type) variables to the subsequent functions along the waterfall to continue the session after the post along with the response JSON.

 

<img src=”https://images.squarespace-cdn.com/content/v1/5b46aaa78ab7226e32a31180/1574349499386-TWJB5S8YZF1EY7K5XIQV/image-asset.png” alt=”” />

 

Next we are going to start the MFA process by triggering the Mechanism Id that we pulled earlier. In the post_data, we set the “Action” to “StartOOB” and remove the “Answer” property. If you pass it (or other properties that are not specifically called for, for the function you’re POSTing to, it’ll 500 error on you. We also change the path of the post_options to “/Security/AdvanceAuthentication”

 

Screenshot 2019-11-21 10.40.02.png
<img src=”https://images.squarespace-cdn.com/content/v1/5b46aaa78ab7226e32a31180/1574350818833-5X17HFU4LMAEQAIN4GOZ/Screenshot+2019-11-21+10.40.02.png” alt=”Screenshot 2019-11-21 10.40.02.png” />

 

The response will be this:

At this point, Centrify has now sent out the MFA token to the email address on file. You’ll need to prompt the user to enter in the token to capture it, and POST it back to Centrify to complete the MFA process.

 

Screenshot 2019-11-21 10.41.03.png
<img src=”https://images.squarespace-cdn.com/content/v1/5b46aaa78ab7226e32a31180/1574350879489-VBMAPRIQUWIBNQB968PC/Screenshot+2019-11-21+10.41.03.png” alt=”Screenshot 2019-11-21 10.41.03.png” />

 

To have this asynchronous process stop to prompt the user for the MFA code, I’ve made this an “async function” and the “ans” variable is calling an “await askQuestion” function to wait for the response of the code before continuing. To pass the answer to the next function in the waterfall, we don’t call “next” here, but just “return ans”.

Now that we have the MFA token, we need to plug it in and POST it back to Centrify to complete the MFA process.

 

<img src=”https://images.squarespace-cdn.com/content/v1/5b46aaa78ab7226e32a31180/1574350923895-T7LD6AEEE3JXGYP1VIOG/image-asset.png” alt=”” />

 

Just like posting the password earlier, we now post the MFA answer as the “Answer” with the “Action” updated to “Answer” and the “MechanismId” is still the same mechanism ID from the Email MFA from the initial request. Once posted, you’ll receive a full JSON response about the user session including the bearer token which is what we need to make all subsequent API calls from here on.

Here is a sample response:

{“success”:true,”Result”:{“AuthLevel”:”High”,”DisplayName”:“John Doe”,”Auth”:”EF1C3BFB9926425A5FA0530C7DD3115389C303893889B83020714D0CED37573BE6FC764DED8213D18792F1CAE6D86F580201AC2BD9C7D9E5151979D0DEECBE62CF4EEE82F02AA69F4FBCB46E4E8077CBB2E21CE0C7D8C5F092789DAA29E98B0DB990982068A0616B4450FA0ECE42D554F4942BF6ED875410C869A7767F30B2EAF45AE7D4C03F7690691FE23711D7A878C1E55A37D4F2FCACD8EEBCB9483399FFF8455BD91D3E4A3B9B4BC839E0ED348A4DC65FA42F6DEE5D4F7D37E0DA37E2EFABF20EE73D63215859082F320A879724A3646F692B53D2EF187F33C15C3C7478B0C71E0E0754AA0C87C85F5223EEDB3C286DCD579ED1934CC7226CA741635E39087A57BCD2ECDFA39654F066A73647075AF0CEA4C416F75B47FB2820A0764F8E38B94A29BF4EF2A6CEC175E9EDD18CDFBE432921D022E8D5A2D41177BF40AF8EEA940ACEF9B7E2A9A0D8BF5FBF1AF0F1CEB24803″,”UserId”:”a18869e8-1c21-47aa-ad55-4a0ec507560f”,”EmailAddress”:“[email protected]“,”UserDirectory”:”ADS”,”PodFqdn”:”tenantid.my.centrify.com”,”User”:“[email protected]”,”CustomerID”:”AAZ0123″,”SystemID”:”AAZ0123″,”SourceDsType”:”ADS”,”Summary”:”LoginSuccess”},”Message”:null,”MessageID”:null,”Exception”:null,”ErrorID”:null,”ErrorCode”:null,”IsSoftError”:false,”InnerExceptions”:null}

The “Auth” value is the bearer token that we need. So grab this and store it into a variable.

 

<img src=”https://images.squarespace-cdn.com/content/v1/5b46aaa78ab7226e32a31180/1574350986322-ENGQTOYR0MB1M790UCUN/image-asset.png” alt=”” />

 

Now that we have our bearer token, we can make as many calls into the Centrify REST API as we want to get data, make stuff, whatever. I’m not going to go through each function as that would be a bit tedious, but I’ll go through the first one, and then you can scour through the code posted at the end as you see fit.

I chose this function to demonstrate because it’s calling /Redrock/Query which is just a SQL call into the Centrify Tenant database for data of any kind. The body of the post is SQL script you want to call.

In the post_options, you need to format it just like I have it here. the “jar: true” is so that you post the cookies that were received from the previous calls. There’s a really important one to maintain your session called .ASPXAUTH. Without this, you’re requests will fail. Additionally, in the headers we ow add “‘Authorization’ : ‘bearer ‘ + authToken”. This is where we add the bearer token for authentication / authorization for all our API calls. It must be exactly like this.

Here is an example response to this call:

That’s pretty much it. From here you can make any call you want to get whatever data you need or update the Tenant as you need. I hope this helps save some people a lot of time trying to figure out how to use the Centrify REST API =)

 

The Full Code:

const http = require(‘https’);
const async = require(‘async’);
const fs = require(‘fs’);

const tenantId = ‘TENANT ID’;
const userId = ‘YOUR TENANT LOGIN ID’;
const password = ‘YOUR TENANT LOGIN PASSWORD’;
const serverSetGUID = ‘SERVER SET GUID’;
const groupAccntGUID = ‘GROUP GUID’;
const groupAccntName = ‘GROUP NAME’;
const serversListFile = “servers.json”;

// Available Permissions: Owner,Manage,Delete,View,Login,Naked,UpdatePassword,UserPortalLogin,RotatePassword
// // Checkout = Manage?
const groupRights = ‘Owner,Manage,Delete,View,Login,Naked,UpdatePassword,UserPortalLogin,RotatePassword’;
//let groupRights = ‘None’;
const host = tenantId + ‘.my.centrify.com’;

let sessionId;
let pw_mechId;
let mfa_mechId;
let resp;
let post_data;
let post_options;
let authToken;

function addPermissionsToAccount(authCookie,authToken,accountID){
let userAccnt_post_data = JSON.stringify({
“PVID”: accountID,
“Grants”:
[
{
“Rights”: groupRights,
“Principal”: groupAccntName,
“PrincipalId”: groupAccntGUID,
“PType”: “Group”
}
] });

let userAccnt_post_options = {
host: host,
port: ‘443’,
path: “/ServerManage/SetAccountPermissions”,
method: ‘POST’,
jar: true,
headers: {
‘Content-Type’: ‘application/json’,
‘X-CENTRIFY-NATIVE-CLIENT’: ‘true’,
‘Authorization’: ‘bearer ‘ + authToken,
‘Cookie’: authCookie,
‘Content-Length’: userAccnt_post_data.length
}
};

// Add the permissions to the user account
let userAccnt_post_auth = http.request(userAccnt_post_options, function (res) {
res.setEncoding(‘utf8’);
res.on(‘data’, function (chunk) {
//console.log(‘Response: ‘ + JSON.stringify(JSON.parse(chunk), null, 4));
//next(null);
console.log(“Account Permissions Created for: ” + accountID);
return true;
});
res.on(‘error’, function (e) {
console.log(“Got error: ” + e.message);
return false;
});
});

userAccnt_post_auth.write(userAccnt_post_data);
userAccnt_post_auth.end();
}

async.waterfall([
function startLogon(next){

post_data = JSON.stringify({
TenantId: tenantId,
User: userId,
Version: ‘1.0’
});

post_options = {
host: host,
port: ‘443’,
path: “/Security/StartAuthentication”,
method: ‘POST’,
jar: true,
headers: {
‘Content-Type’:’application/json’,
‘X-CENTRIFY-NATIVE-CLIENT’:’true’,
‘Content-Length’: post_data.length
}
};

// Set up the request
let post_auth = http.request(post_options, function(res) {
res.setEncoding(‘utf8’);
res.on(‘data’, function (chunk) {
//console.log(‘startLogon Response: ‘ + JSON.stringify(JSON.parse(chunk), null, 4));
next(null, chunk);
});
res.on(‘error’, function (e) {
console.log(“Got error: ” + e.message);
});
});

post_auth.write(post_data);
post_auth.end();
},
function startAuthN(respChunk, next){
resp = JSON.parse(respChunk);
sessionId = resp.Result.SessionId;
pw_mechId = resp.Result.Challenges[0].Mechanisms[0].MechanismId;
mfa_mechId = 0

// Loop through all MFA Mechanisms and select Email
for (let m = 0; m < resp.Result.Challenges[1].Mechanisms.length; m++) {
if (resp.Result.Challenges[1].Mechanisms[m].Name === “EMAIL”) {
mfa_mechId = resp.Result.Challenges[1].Mechanisms[m].MechanismId;
console.log(resp.Result.Challenges[1].Mechanisms[m].PromptMechChosen);
}
}

post_data = JSON.stringify({
“TenantId”: tenantId,
“SessionId”: sessionId,
“MechanismId”: pw_mechId,
“Action”: “Answer”,
“Answer”: password
});

post_options = {
host: host,
port: ‘443’,
path: “/Security/AdvanceAuthentication”,
method: ‘POST’,
jar: true,
headers: {
‘Content-Type’:’application/json’,
‘X-CENTRIFY-NATIVE-CLIENT’:’true’,
‘Content-Length’: post_data.length
}
};

// Set up the request
let post_auth = http.request(post_options, function(res) {
res.setEncoding(‘utf8’);
res.on(‘data’, function (chunk) {
//console.log(‘startAuthN Response: ‘ + JSON.stringify(JSON.parse(chunk), null, 4));
next(null, chunk, sessionId, mfa_mechId);
});
res.on(‘error’, function (e) {
console.log(“Got error: ” + e.message);
});
});

if ( mfa_mechId != 0 ) {
post_auth.write(post_data);
post_auth.end();
} else {
post_auth.end();
console.log(“Couldn’t find Email as an MFA mechanism for login: ” + userId);
}

},
function startMFA(respChunk, sessionId, mfa_mechId, next){

post_data = JSON.stringify({
“TenantId”: tenantId,
“SessionId”: sessionId,
“MechanismId”: mfa_mechId,
“Action”: “StartOOB”
});

post_options = {
host: host,
port: ‘443’,
path: “/Security/AdvanceAuthentication”,
method: ‘POST’,
jar: true,
headers: {
‘Content-Type’:’application/json’,
‘X-CENTRIFY-NATIVE-CLIENT’:’true’,
‘Content-Length’: post_data.length
}
};

// Set up the request
let post_auth = http.request(post_options, function(res) {
res.setEncoding(‘utf8’);
res.on(‘data’, function (chunk) {
//onsole.log(‘startMFA Response: ‘ + JSON.stringify(JSON.parse(chunk), null, 4));
next(null, chunk, sessionId, mfa_mechId);
});
res.on(‘error’, function (e) {
console.log(“Got error: ” + e.message);
});
});

post_auth.write(post_data);
post_auth.end();

},
async function promptForMFA(respChunk, sessionId, mfa_mechId, next){
resp = JSON.parse(respChunk);

const readline = require(‘readline’);

function askQuestion(query) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

return new Promise(resolve => rl.question(query, ans => {
rl.close();
resolve(ans);
}))
}

let ans = await askQuestion(“MFA Code: “);
return ans;
},
function PostMFA(mfaAns, next){

post_data = JSON.stringify({
“TenantId”: tenantId,
“SessionId”: sessionId,
“MechanismId”: mfa_mechId,
“Action”: “Answer”,
“Answer”: mfaAns
});

post_options = {
host: host,
port: ‘443’,
path: “/Security/AdvanceAuthentication”,
method: ‘POST’,
jar: true,
headers: {
‘Content-Type’:’application/json’,
‘X-CENTRIFY-NATIVE-CLIENT’:’true’,
‘Content-Length’: post_data.length
}
};

// Set up the request
let post_auth = http.request(post_options, function(res) {
res.setEncoding(‘utf8’);
res.on(‘data’, function (chunk) {
//console.log(‘PostMFA Response: ‘ + JSON.stringify(JSON.parse(chunk), null, 4));
resp = JSON.parse(chunk);
authToken = resp.Result.Auth;

let authCookie = res.headers[“set-cookie”][0];

next(null, authToken, authCookie);
});
res.on(‘error’, function (e) {
console.log(“Got error: ” + e.message);
});
});

post_auth.write(post_data);
post_auth.end();

},

// Get existing servers via Redrock Query
function GetExistingServers(authToken, authCookie, next){

post_data = JSON.stringify({
“Script”: “SELECT SERVER.ID,SERVER.Name,SERVER.FQDN FROM SERVER”,
});

post_options = {
host: host,
port: ‘443’,
path: “/Redrock/Query”,
method: ‘POST’,
jar: true,
headers: {
‘Content-Type’: ‘application/json’,
‘X-CENTRIFY-NATIVE-CLIENT’: ‘true’,
‘Authorization’: ‘bearer ‘ + authToken,
‘Cookie’: authCookie,
‘Content-Length’: post_data.length
}
};

// Set up the request
let post_auth = http.request(post_options, function(res) {
res.setEncoding(‘utf8’);
res.on(‘data’, function (chunk) {
//console.log(‘Redrock Existing Servers Response: ‘ + JSON.stringify(JSON.parse(chunk), null, 4));
resp = JSON.parse(chunk);
serverList = resp.Result.Results;

next(null, authToken, authCookie, serverList);
});
res.on(‘error’, function (e) {
console.log(“Got error: ” + e.message);
});
});

post_auth.write(post_data);
post_auth.end();

},

async function GetExistingAccounts(authToken, authCookie, serverList, next){
console.log(“Getting existsing accounts for all servers”);

function getAccountsForServer(iterateServerGUID) {
return new Promise(function (resolve, reject) {
//console.log(“Getting accounts for: ” + iterateServerGUID);
let accountList = ”;

post_data = JSON.stringify({
“Script”: “SELECT VAULTACCOUNT.ID,VAULTACCOUNT.USER FROM VAULTACCOUNT WHERE VAULTACCOUNT.HOST='” + iterateServerGUID + “‘”,
});

post_options = {
host: host,
port: ‘443’,
path: “/Redrock/Query”,
method: ‘POST’,
jar: true,
headers: {
‘Content-Type’: ‘application/json’,
‘X-CENTRIFY-NATIVE-CLIENT’: ‘true’,
‘Authorization’: ‘bearer ‘ + authToken,
‘Cookie’: authCookie,
‘Content-Length’: post_data.length
}
};

// Set up the request
let post_auth = http.request(post_options, function (res) {
res.setEncoding(‘utf8’);
res.on(‘data’, function (chunk) {
//console.log(‘Redrock Response: ‘ + JSON.stringify(JSON.parse(chunk), null, 4));
resp = JSON.parse(chunk);
accountList = resp.Result.Results;
resolve(accountList);
});
res.on(‘error’, function (e) {
console.log(“Got error: ” + e.message);
reject(e.message);
});
});

post_auth.write(post_data);
post_auth.end();

return accountList;
});
}

for (let i = 0; i < serverList.length; i++) {
let accountsOnServer = {};
accountsOnServer = await getAccountsForServer(serverList[i].Row.ID);

serverList[i].Row.Accounts = accountsOnServer;
}
return [authToken,authCookie,serverList];
},

function AddResource(details, next){
authToken = details[0];
authCookie = details[1];
existingServers = details[2];

let resources = JSON.parse(fs.readFileSync(serversListFile).toString());

console.log(“Servers found to process: ” + resources.Servers.length);

// Loop through all the servers to create
for (let i = 0; i < resources.Servers.length; i++) {

let serverExists = 0;

let thisResource = resources.Servers[i];

let resourceUsername = thisResource.Username;
let resourcePassword = thisResource.Password;
let resourceAccountDesc = thisResource.UserDesc;
let resourceGUID = thisResource.GUID;
let resourceFQDN = thisResource.FQDN;

// Check to see if server with same FQDN already exists
for (let i = 0; i < existingServers.length; i++) {
existingResource = existingServers[i].Row;
//resourceName = existingResource.Name;
//resourceFQDN = existingResource.FQDN;
//resourceID = existingResource.ID;

if (existingResource.FQDN == thisResource.FQDN) {
resourceGUID = existingResource.ID;
serverExists = 1;
console.log(“Server with FQDN ” + thisResource.FQDN + ” already exists with GUID ” + resourceGUID);
}
}

post_data = JSON.stringify({
“Name”: thisResource.Name,
“FQDN”: thisResource.FQDN,
“ComputerClass”: thisResource.ComputerClass,
“SessionType”: thisResource.SessionType,
“Description”: thisResource.Description
});

post_options = {
host: host,
port: ‘443’,
path: “/ServerManage/AddResource”,
method: ‘POST’,
jar: true,
headers: {
‘Content-Type’: ‘application/json’,
‘X-CENTRIFY-NATIVE-CLIENT’: ‘true’,
‘Authorization’: ‘bearer ‘ + authToken,
‘Cookie’: authCookie,
‘Content-Length’: post_data.length
}
};

// Add the server to the Tenant
let post_auth = http.request(post_options, function (res) {
res.setEncoding(‘utf8’);
res.on(‘data’, function (chunk) {
resp = JSON.parse(chunk);
let serverID = resp.Result;

if ( serverID === null && resourceGUID !== “”) {
serverID = resourceGUID;
console.log(“Server already exitsts: ” + serverID);
} else {
console.log(“Server created: ” + serverID);
}

//console.log(‘Response: ‘ + JSON.stringify(JSON.parse(chunk), null, 4));

//next(null, authToken, authCookie, serverID);

console.log(“Adding Server (” + serverID + “) to Set ID ” + serverSetGUID);
// Add the server to a set
let serverSet_post_data = JSON.stringify({
“id”: serverSetGUID,
“add”:
[
{
“MemberType”:”Row”,
“Table”:”Server”,
“Key”:serverID
}
] });

let serverSet_post_options = {
host: host,
port: ‘443’,
path: “/Collection/UpdateMembersCollection”,
method: ‘POST’,
jar: true,
headers: {
‘Content-Type’: ‘application/json’,
‘X-CENTRIFY-NATIVE-CLIENT’: ‘true’,
‘Authorization’: ‘bearer ‘ + authToken,
‘Cookie’: authCookie,
‘Content-Length’: serverSet_post_data.length
}
};

// Add the server to the Set
let serverSet_post_auth = http.request(serverSet_post_options, function (res) {
res.setEncoding(‘utf8’);
res.on(‘data’, function (chunk) {
console.log(“Server added to set.”);
//console.log(‘Response: ‘ + JSON.stringify(JSON.parse(chunk), null, 4));
//next(null);

let accountGUID = ”;
let accountExists = 0;

// See what accounts are already attached to the server
if (serverExists === 1) {
// Get the user accounts from this server
console.log(“Checking existing server for existing accounts to match”);
for (let s = 0; s < existingServers.length; s++) {
existingResource = existingServers[s].Row;
//resourceName = existingResource.Name;
//resourceFQDN = existingResource.FQDN;
//resourceID = existingResource.ID;

if (existingResource.ID === serverID) {

for (let z = 0; z < existingResource.Accounts.length; z++) {
if (existingResource.Accounts[z].Row.USER === resourceUsername && existingResource.FQDN === resourceFQDN) {
accountGUID = existingResource.Accounts[z].Row.ID;
accountExists = 1;
console.log(“Account already exists with GUID ” + accountGUID);
}
}
}
}
}

if (accountExists === 1) {
addPermissionsToAccount(authCookie,authToken,accountGUID);
} else {

let user_post_data = JSON.stringify({
“User”: resourceUsername,
“Password”: resourcePassword,
“IsManaged”: true,
“UseWheel”: false,
“Description”: resourceAccountDesc,
“Host”: serverID
});

//console.log(“TO POST: ” + JSON.stringify(user_post_data));

let user_post_options = {
host: host,
port: ‘443’,
path: “/ServerManage/AddAccount”,
method: ‘POST’,
jar: true,
headers: {
‘Content-Type’: ‘application/json’,
‘X-CENTRIFY-NATIVE-CLIENT’: ‘true’,
‘Authorization’: ‘bearer ‘ + authToken,
‘Cookie’: authCookie,
‘Content-Length’: user_post_data.length
}
};

// Add admin user to the server
let user_post_auth = http.request(user_post_options, function (res) {
res.setEncoding(‘utf8’);
res.on(‘data’, function (chunk) {
//console.log(‘Response: ‘ + JSON.stringify(JSON.parse(chunk), null, 4));
//next(null);
resp = JSON.parse(chunk);
accountGUID = resp.Result;
console.log(“Account created: ” + accountGUID);

addPermissionsToAccount(authCookie,authToken,accountGUID);

});
res.on(‘error’, function (e) {
console.log(“Got error: ” + e.message);
});
});

user_post_auth.write(user_post_data);
user_post_auth.end();
}
// End if account already exists
});
res.on(‘error’, function (e) {
console.log(“Got error: ” + e.message);
});
});

serverSet_post_auth.write(serverSet_post_data);
serverSet_post_auth.end();

});
res.on(‘error’, function (e) {
console.log(“Got error: ” + e.message);
});
});

post_auth.write(post_data);
post_auth.end();

} // End For Loop

},
], function (err) {
if (err) {
console.error(
‘Unable to Register: ‘ + err
);
} else {
console.log(
‘Successfully registered’
);
}

//callback(null, “message”);
});

Cheers!