UNPKG

scapi

Version:

SCAPI is a restful API for use with Elsinore ScreenConnect v4

447 lines (394 loc) 17.8 kB
// ---------------------------------------------------------------------------------------------- // 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);