scapi
Version:
SCAPI is a restful API for use with Elsinore ScreenConnect v4
447 lines (394 loc) • 17.8 kB
JavaScript
// ----------------------------------------------------------------------------------------------
// SCAPI v1.28a
// Author: Paul Moore (Urity Group)
// URL: urity.co.uk
//
// SCREENCONNECT API
// This API allows you to connect & control ScreenConnect v4+ from an external application.
// ----------------------------------------------------------------------------------------------
// THIS IS AN ALPHA RELEASE - EXPECT BUGS.
// ----------------------------------------------------------------------------------------------
// PRE-REQs
var restify = require('restify');
var config = require('./config.js');
var util = require('util');
var fs = require('fs');
var SC;
// CONFIGURE & CREATE THE API SERVER
// We cannot pass the username/password directly to ScreenConnect - The PBKDF2 iterations make each API call too slow.
// Instead - We'll handle the API->SC auth once and store the cookie. Then, we check to see if the creds match on each API call.
var API = restify.createServer(config.SCAPI_params.API_options);
API.use(restify.fullResponse());
API.pre(restify.pre.userAgentConnection());
API.use(restify.bodyParser());
API.use(restify.gzipResponse());
API.use(restify.authorizationParser());
API.listen(config.SCAPI_params.LISTEN_PORT, function() {
SC = restify.createJsonClient({url: config.SCAPI_params.PROTOCOL + "://" + config.SCAPI_params.URL + ":" + config.SCAPI_params.PORT});
SC.basicAuth(config.SCAPI_params.API_USERNAME, config.SCAPI_params.API_PASSWORD);
SC.head('/Service.ashx/GetSessionGroups', function(err, req, res, obj)
{
// LOGIN TO SCREENCONNECT ERROR -----------
if(err)
{
console.log('CRITICAL ERROR: SCAPI could not start. [Internal API Username and/or Password was incorrect]');
process.exit(code=0);
}
// LOGIN TO SCREENCONNECT SUCCESS -----------
if(res.headers["set-cookie"])
{
SC.headers = {"Cookie" : res.headers["set-cookie"]};
console.log('%s listening at %s -- Successfully logged in to ScreenConnect.', API.name, API.url);
}
});
});
// AUTH - FRONT
API.use(function authenticate(req, res, next)
{
if(!req.is('application/json')) {return next(new restify.InvalidHeaderError("This API only speaks JSON and you haven't supplied an 'application/json' header."));}
// check the supplied username/password matches that contained within the config
if(req.username == config.SCAPI_params.API_USERNAME && req.authorization.basic.password == config.SCAPI_params.API_PASSWORD)
{
return next();
}
else
{
return next(new restify.NotAuthorizedError("API Username/Password incorrect."));
}
});
// ROUTES
// -- SESSIONS
API.get('/sessions/:group', getSessions);
API.get('/session/:id/:group', getSessionByID);
API.post('/session', createSession);
API.del('/session/:id/:group', endSession);
// -- MESSAGES
API.get('/messages/:id/:group', getMessage);
API.post('/messages/:id/:group', sendMessage);
// -- COMMANDS
API.get('/commands/:id/:group', getCommand);
API.post('/commands/:id/:group', sendCommand);
// -- NOTES
API.get('/notes/:id/:group', getNote);
API.post('/notes/:id/:group', addNote);
// -- GET TIMELINE
API.get('/timeline/:id/:group', getTimeline);
// -- GET SCREENSHOT
API.get('/screenshot/:id/:group', getScreenshot);
// -- SELF SERVICE
API.post('/selfService', selfService);
// METHODS
// -- SESSIONS
// -- -- GET SESSION(s)
function getSessions(req, res, next)
{
// run checks
var session_group = (checkGroup(req.params.group)) ? checkGroup(req.params.group) : next(new SCAPI_Message(400, "Invalid session group. Must be 'support' or 'meeting'"));
SC.post('/Service.ashx/GetHostSessionInfo',[session_group,"","",0], function(error, req1, res1, obj)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
// the request was successful, but is there any content?
if(JSON.stringify(obj) == "{}")
{
return next(new SCAPI_Message(204, "Session ID not found."));
}
res.send(200, obj.sss);
});
}
// -- -- GET SESSION BY ID
function getSessionByID(req, res, next)
{
// run checks
if(!isValidGUID(req.params.id)) {return next(new restify.InvalidArgumentError("The Session ID you provided was not a valid GUID."));}
var session_group = (checkGroup(req.params.group)) ? checkGroup(req.params.group) : next(new SCAPI_Message(400, "Invalid session group. Must be 'support' or 'meeting'"));
SC.post('/Service.ashx/GetHostSessionInfo',[session_group,"","",0], function(error, req1, res1, obj)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
// the request was successful, but is there any content?
if(JSON.stringify(obj.sss) == "[]")
{
return next(new SCAPI_Message(204, "Session ID not found."));
}
(obj.sss).forEach(function(event, index)
{
if(event.clp.s == req.params.id)
{
res.send(200, obj.sss[index]);
}
});
});
}
// -- -- CREATE SESSION
function createSession(req, res, next)
{
// run checks
if((req.params.code) && (req.params.ispublished == "true")) {return next(new SCAPI_Message(400, "You can't publish a session and define an access code. Must be one or the other."));}
if((req.params.ispublished != "false") && (req.params.ispublished != "true")) {return next(new SCAPI_Message(400, "The 'IsPublished' parameter can only be either true or false."));}
if((req.params.customparams) && ((req.params.customparams).length < 8)) {return next(new SCAPI_Message(400, "You've specified a 'Custom Parameter' but it's not an array. Needs to be an array of 8 items."));}
var session_type = (checkGroupID(req.params.type)) ? checkGroupID(req.params.type) : next(new SCAPI_Message(400, "Invalid session type. Must be 'support' or 'meeting'"));
SC.post('/Service.ashx/CreateSession',[session_type,req.params.name,req.params.ispublished,req.params.code,[req.params.customparams[0],req.params.customparams[1],req.params.customparams[2],req.params.customparams[3],req.params.customparams[4],req.params.customparams[5],req.params.customparams[6],req.params.customparams[7]]], function(error, req1, res1, obj)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
res.json(201, {code: 201, data: obj});
});
}
// -- -- END SESSION
function endSession(req, res, next)
{
// run checks
if(!isValidGUID(req.params.id)) {return next(new restify.InvalidArgumentError("The Session ID you provided was not a valid GUID."));}
var session_group = (checkGroup(req.params.group)) ? checkGroup(req.params.group) : next(new SCAPI_Message(400, "Invalid session group. Must be 'support' or 'meeting'"));
SC.post('/Service.ashx/AddEventToSessions',[session_group,[req.params.id],21,""], function(error, req, res, obj)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
return next(new SCAPI_Message(204, "Session deleted successfully."));
});
}
// -- -- GET MESSAGE
function getMessage(req, res, next)
{
// run checks
if(!isValidGUID(req.params.id)) {return next(new restify.InvalidArgumentError("The Session ID you provided was not a valid GUID."));}
var session_group = (checkGroup(req.params.group)) ? checkGroup(req.params.group) : next(new SCAPI_Message(400, "Invalid session group. Must be 'support' or 'meeting'"));
var messages = [];
SC.post('/Service.ashx/GetSessionDetails',[session_group,req.params.id], function(error, req1, res1, obj)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
// the request was successful, but is there any content?
if(JSON.stringify(obj) == "{}")
{
return next(new SCAPI_Message(204, "Session ID not found."));
}
(obj.es).forEach(function(event, index)
{
if(event.et == 45)
{
messages.push(obj.es[index]);
}
});
res.send(200, messages);
});
}
// -- -- SEND MESSAGE
function sendMessage(req, res, next)
{
// run checks
if(!isValidGUID(req.params.id)) {return next(new restify.InvalidArgumentError("The Session ID you provided was not a valid GUID."));}
var session_group = (checkGroup(req.params.group)) ? checkGroup(req.params.group) : next(new SCAPI_Message(400, "Invalid session group. Must be 'support' or 'meeting'"));
SC.post('/Service.ashx/AddEventToSessions',[session_group,[req.params.id],45,req.params.text], function(error, req, res, obj)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
return next(new SCAPI_Message(200, "Message sent successfully."));
});
}
// -- -- GET COMMAND
function getCommand(req, res, next)
{
// run checks
if(!isValidGUID(req.params.id)) {return next(new restify.InvalidArgumentError("The Session ID you provided was not a valid GUID."));}
var session_group = (checkGroup(req.params.group)) ? checkGroup(req.params.group) : next(new SCAPI_Message(400, "Invalid session group. Must be 'support' or 'meeting'"));
var commands = [];
SC.post('/Service.ashx/GetSessionDetails',[session_group,req.params.id], function(error, req1, res1, sessionDetails)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
// the request was successful, but is there any content?
if(JSON.stringify(sessionDetails) == "{}")
{
return next(new SCAPI_Message(204, "Session ID not found."));
}
// SC runs "getSortedEvents" here
for (var i = 0; sessionDetails.cs[i]; i++)
for (var j = 0; sessionDetails.cs[i].es[j]; j++)
commands.push({ eventID: sessionDetails.cs[i].es[j].id, eventType: sessionDetails.cs[i].es[j].et, time: addDateSeconds(new Date(parseInt(sessionDetails.bt.substr(6))), -sessionDetails.cs[i].es[j].t).getTime(), data: sessionDetails.cs[i].es[j].d, who: sessionDetails.cs[i].pn });
for (var i = 0; sessionDetails.es[i]; i++)
commands.push({ eventID: sessionDetails.es[i].id, eventType: sessionDetails.es[i].et, time: addDateSeconds(new Date(parseInt(sessionDetails.bt.substr(6))), -sessionDetails.es[i].t).getTime(), data: sessionDetails.es[i].d, who: sessionDetails.es[i].h });
commands.sort(function (e1, e2) { return e1.time - e2.time; });
res.send(200, commands);
});
}
// -- -- SEND COMMAND
function sendCommand(req, res, next)
{
// run checks
if(!isValidGUID(req.params.id)) {return next(new restify.InvalidArgumentError("The Session ID you provided was not a valid GUID."));}
var session_group = (checkGroup(req.params.group)) ? checkGroup(req.params.group) : next(new SCAPI_Message(400, "Invalid session group. Must be 'support' or 'meeting'"));
SC.post('/Service.ashx/AddEventToSessions',[session_group,[req.params.id],44,req.params.command], function(error, req, res, obj)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
return next(new SCAPI_Message(200, "Command sent successfully."));
});
}
function getNote(req, res, next)
{
// run checks
if(!isValidGUID(req.params.id)) {return next(new restify.InvalidArgumentError("The Session ID you provided was not a valid GUID."));}
var session_group = (checkGroup(req.params.group)) ? checkGroup(req.params.group) : next(new SCAPI_Message(400, "Invalid session group. Must be 'support' or 'meeting'"));
var notes = [];
SC.post('/Service.ashx/GetSessionDetails',[session_group,req.params.id], function(error, req1, res1, obj)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
// the request was successful, but is there any content?
if(JSON.stringify(obj) == "{}")
{
return next(new SCAPI_Message(204, "Session ID not found."));
}
(obj.es).forEach(function(event, index)
{
if(event.et == 32)
{
notes.push(obj.es[index]);
}
});
res.send(200, notes);
});
}
// -- -- ADD NOTE
function addNote(req, res, next)
{
// run checks
if(!isValidGUID(req.params.id)) {return next(new restify.InvalidArgumentError("The Session ID you provided was not a valid GUID."));}
var session_group = (checkGroup(req.params.group)) ? checkGroup(req.params.group) : next(new SCAPI_Message(400, "Invalid session group. Must be 'support' or 'meeting'"));
SC.post('/Service.ashx/AddEventToSessions',[session_group,[req.params.id],32,req.params.text], function(error, req, res, obj)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
return next(new SCAPI_Message(201, "Note added successfully."));
});
}
function getTimeline(req, res, next)
{
// run checks
if(!isValidGUID(req.params.id)) {return next(new restify.InvalidArgumentError("The Session ID you provided was not a valid GUID."));}
var session_group = (checkGroup(req.params.group)) ? checkGroup(req.params.group) : next(new SCAPI_Message(400, "Invalid session group. Must be 'support' or 'meeting'"));
SC.post('/Service.ashx/GetSessionDetails',[session_group,req.params.id], function(error, req1, res1, obj)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
// the request was successful, but is there any content?
if(JSON.stringify(obj) == "{}")
{
return next(new SCAPI_Message(204, "Session ID not found."));
}
res.send(200, obj.es);
});
}
function getScreenshot(req, res, next)
{
// run checks
if(!isValidGUID(req.params.id)) {return next(new restify.InvalidArgumentError("The Session ID you provided was not a valid GUID."));}
var session_group = (checkGroup(req.params.group)) ? checkGroup(req.params.group) : next(new SCAPI_Message(400, "Invalid session group. Must be 'support' or 'meeting'"));
SC.post('/Service.ashx/CreateSession',["All Sessions","test",false,"test",[]], function(error, req1, res1, obj)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
// the request was successful, but is there any content?
if(JSON.stringify(obj) == "{}")
{
return next(new SCAPI_Message(204, "Session ID not found."));
}
res.json(200, {code: 200, data: obj.sc});
});
}
function selfService(req, res, next)
{
// allow POST to this endpoint without BASIC creds.
// run checks
var session_type = (checkGroupID(req.params.type)) ? checkGroupID(req.params.type) : next(new SCAPI_Message(400, "Invalid session type. Must be 'support' or 'meeting'"));
var session_group = (session_type == "0") ? "All Sessions" : "All Meetings";
var code = getRandomStringFromMask("AA####");
var name = "[SS] - ["+code+"] - "+req.params.personName;
var description = req.params.personName + " has initiated a self service session and needs assistance with : " + req.params.description;
SC.post('/Service.ashx/CreateSession',[session_type,name,false,code,[]], function(error, req1, res1, obj)
{
// Did the request fail?
if (error) next(new SCAPI_Message(error.statusCode, error.message));
SC.post('/Service.ashx/AddEventToSessions',[session_group,[obj],32,description], function(error2, req2, res2, obj2)
{
// Did the request fail?
if (error2) next(new SCAPI_Message(error2.statusCode, error2.message));
});
res.json(201, {code: 201, data: {"session_code":code,"session_id":obj}});
});
}
// -------------------------------
// THE FOLLOWING ARE HELPER FUNCTIONS, HELP GUIDES AND DEFINITIONS USED BY THE API.
// -------------------------------
// VALIDATION
var isValidGUID = function(value) {
var validGuid = /^(\{|\()?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(\}|\))?$/;
var emptyGuid = /^(\{|\()?0{8}-(0{4}-){3}0{12}(\}|\))?$/;
return validGuid.test(value) && !emptyGuid.test(value);
}
var addDateMilliseconds = function (date, milliseconds) {
var originalTime = date.getTime();
return new Date(originalTime + milliseconds);
}
var addDateSeconds = function (date, seconds) {
return addDateMilliseconds(date, seconds * 1000);
}
var addDateDays = function (date, days) {
return addDateSeconds(date, days * 86400000);
}
var checkGroup = function(group) {
switch(group.toLowerCase())
{
case "support":
return "All Sessions";
break;
case "meeting":
return "All Meetings";
break;
default:
return false;
}
}
var checkGroupID = function(group) {
switch(group.toLowerCase())
{
case "support":
return "0";
break;
case "meeting":
return "1";
break;
default:
return false;
}
}
var getRandomStringFromMask = function (mask) {
var string = "";
for (var i = 0; mask[i]; i++) {
var maskChar = mask.charAt(i);
if (maskChar == "#")
string += getRandomChar(48, 58);
else if (maskChar == "A")
string += getRandomChar(65, 91);
else
string += maskChar;
}
return string;
}
var getRandomChar = function (minCharCode, maxCharCode) {
var charCode = minCharCode + Math.floor(Math.random() * (maxCharCode - minCharCode));
return String.fromCharCode(charCode);
}
// DEFINE CUSTOM ERRORS
var SCAPI_Message = function(code,message) {
restify.RestError.call(this, {
restCode: 'SCAPI_Message',
statusCode: code,
message: message,
constructorOpt: SCAPI_Message
});
this.name = 'SCAPI_Message';
};
util.inherits(SCAPI_Message, restify.RestError);