diff --git a/CHANGELOG.md b/CHANGELOG.md index 789764db0..5ea5c2dff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Change Log +## [v1.13.8](https://github.com/prey/prey-node-client/tree/v1.13.8) (2024-12-19) +[Full Changelog](https://github.com/prey/prey-node-client/compare/v1.13.7..v1.13.8) + +- Fix: Rollback at restore.js file. It was causing an error when there is a backup database on temporal folder. ([Beregcamlost](https://github.com/beregcamlost)) ([SoraKenji](https://github.com/SoraKenji)) + +## [v1.13.7](https://github.com/prey/prey-node-client/tree/v1.13.7) (2024-12-19) +[Full Changelog](https://github.com/prey/prey-node-client/compare/v1.13.6..v1.13.7) + +- Feat: Add new WinSVC version v2.0.21. It includes a way to change wifi permission on Windows. ([Beregcamlost](https://github.com/beregcamlost)) ([SoraKenji](https://github.com/SoraKenji)) + +- Feat: Add new MacSVC (prey-user binary) version v1.0.7. It resolves an error when a new version of prey get downloaded and doesn't get configurated correctly, leaving the software unable to continue working. ([Beregcamlost](https://github.com/beregcamlost)) ([SoraKenji](https://github.com/SoraKenji)) + +- Fix: Fix an error related to network reconnections when getting device information leaving it unable to keep processing that data. ([Beregcamlost](https://github.com/beregcamlost)) ([SoraKenji](https://github.com/SoraKenji)) + +- Fix: Fix an error when replacing older MacSVC (prey-user binary) version with new one on MacOS bellow version 13.0. ([Beregcamlost](https://github.com/beregcamlost)) ([SoraKenji](https://github.com/SoraKenji)) + +- Chore: Remove code in software related to geofencing, since now that data is processed by backend. ([Javo](https://github.com/javo)) ([Beregcamlost](https://github.com/beregcamlost)) ([SoraKenji](https://github.com/SoraKenji)) + + ## [v1.13.6](https://github.com/prey/prey-node-client/tree/v1.13.6) (2024-11-22) [Full Changelog](https://github.com/prey/prey-node-client/compare/v1.13.5..v1.13.6) diff --git a/bin/fenix.exe b/bin/fenix.exe index 75ec5d496..ba481ac73 100644 Binary files a/bin/fenix.exe and b/bin/fenix.exe differ diff --git a/bin/prey-user b/bin/prey-user index 6bb7b00b1..00b9512f0 100755 Binary files a/bin/prey-user and b/bin/prey-user differ diff --git a/bin/updater.exe b/bin/updater.exe index 44b367768..bafe14453 100755 Binary files a/bin/updater.exe and b/bin/updater.exe differ diff --git a/lib/agent/ack.js b/lib/agent/ack.js index c709f42f5..9d84b7e69 100644 --- a/lib/agent/ack.js +++ b/lib/agent/ack.js @@ -1,15 +1,14 @@ -const storage = require('./utils/storage'); const ackType = 'ack'; const existKeyAckInJson = (json) => { - //eslint-disable-next-line no-prototype-builtins + // eslint-disable-next-line no-prototype-builtins if (json.hasOwnProperty('ack_id')) { return true; } return false; }; const existKeyIdInJson = (json) => { - //eslint-disable-next-line no-prototype-builtins + // eslint-disable-next-line no-prototype-builtins if (json.hasOwnProperty('id')) { return true; } @@ -26,3 +25,6 @@ exports.processAck = (json, cb) => { id: existKeyIdInJson(json) ? json.id : '', }); }; + +exports.existKeyAckInJson = existKeyAckInJson; +exports.existKeyIdInJson = existKeyIdInJson; diff --git a/lib/agent/actions/geofencing/index.js b/lib/agent/actions/geofencing/index.js deleted file mode 100644 index 5a4982a49..000000000 --- a/lib/agent/actions/geofencing/index.js +++ /dev/null @@ -1,129 +0,0 @@ -"use strict"; - -var async = require('async'), - EventEmitter = require('events').EventEmitter, - api = require('./../../control-panel/api'), - storage = require('./../../utils/storage'), - hooks = require('./../../hooks'); - -var emitter; - -exports.watching; - -function fetch_geofences(cb) { - api.devices.get.geofences(cb); -} - -exports.get_geofences = function (cb) { - storage.do('all', { type: 'geofences' }, (err, stored_geofences) => { - if (err || !stored_geofences) return cb(new Error ('Error retrieving geofences from local database')) - return cb(null, stored_geofences); - }) -} - -exports.sync = function(id, geofences, cb) { - exports.watching = []; - - if(!geofences || geofences.length == 0) - done(id, cb); - - let fences = geofences; - - // Se obtienen las zonas locales almacenadas - storage.do('all', { type: 'geofences' }, (err, stored_geofences) => { - if(err || !stored_geofences) return cb(new Error ('Error retrieving geofences from local database')); - let array = []; - - // Por cada stored fence se revisa si existe en el listado nuevo - stored_geofences.forEach((stored_fence) => { - array.push((callback) => { - let occurrences = geofences.filter(x => x.id == stored_fence.id); - - // Si la zona existe se asigna el state y se quita del listado de nuevas... - if (occurrences.length > 0) { - let geof_index = geofences.findIndex((obj => obj.id == stored_fence.id)); - geofences[geof_index].state = stored_fence.state; - - // se quita de fences - fences = fences.filter(x => x.id != stored_fence.id) - exports.watching.push(parseInt(stored_fence.id)); - callback(); - //Si no existe en el listado nuevo se borra del local, se quita del listado de nuevas... - } else { - // se quita de las zonas locales - storage.do('del', {type: 'geofences', id: stored_fence.id}, callback); - } - }) - }) - - async.series(array, (err) => { - let array2 = []; - - // al terminar.. de las nuevas (fences) que queden se agregan todas en el listado local - fences.forEach(fence => { - array2.push((callback) => { - exports.watching.push(parseInt(fence.id)); - storage.do('set', {type: 'geofences', id: fence.id, data: {name: fence.name, state: 'NULL'}}, callback) - }) - }) - - async.series(array2, (err) => { - start_watcher(() => { - done(id, cb); - }); - }); - }); - }); - - function start_watcher(cb) { - if (exports.watching && exports.watching.length > 0) { - notify_done(); - } - - - function notify_done() { - var data = { - status: 'started', - command: 'start', - target: 'geofencing', - reason: JSON.stringify(exports.watching) - } - api.push.response(data); - cb(); - } - } - - function done(id, cb) { - hooks.trigger('geofencing_start', geofences); - setTimeout(() => { - emitter.emit('end', id); - }, 1000); - return cb(); - } -} - -function refresh_geofences(id, opts, cb) { - - emitter = emitter || new EventEmitter(); - - fetch_geofences(function(err, res) { - if (err || !res || !res.body) { - emitter.emit('end', id, err); - if (cb && typeof(cb) == 'function') return cb(new Error("Unable to get geofences from control panel")); - else return null; - } - - var geofences = res.body; - - if (!(geofences instanceof Array)) { - var err = new Error('Geofences list is not an array'); - return emitter.emit('end', id, err) - } - exports.sync(id, geofences, () => { - cb && cb(null, emitter); - }); - }); - -} - -exports.start = exports.stop = refresh_geofences; diff --git a/lib/agent/actions/lock/index.js b/lib/agent/actions/lock/index.js index 6bc5bd22a..a4fc86045 100644 --- a/lib/agent/actions/lock/index.js +++ b/lib/agent/actions/lock/index.js @@ -31,23 +31,7 @@ var lock_binary = lock_binary_path(), var child, timer, emitter, - stopped, - kill_apps; - -function kill_running_apps(cb) { - if (os_name != 'mac') return cb(); - var cmd = `ps aux |awk '{for(i=11;i<=NF;i++){printf "%s ", $i}; print $2}' | grep "^/Applications" | awk '{print $NF}'` - - exec(cmd, (err, out) => { - var apps = out.split('\n').slice(0,-1); - apps = apps.join(" ") - - var kill_cmd = 'kill -9 ' + apps + ';killall Finder'; - run_as_user(kill_cmd, [], (err) =>{ - return cb(); - }) - }) -} + stopped; function lock_binary_path() { var binary_name = 'prey-lock'; @@ -69,13 +53,11 @@ var md5_digest = function(str){ }; function before(cb) { + if (!is_win) return cb(); + // as priviledged user, lock all escape mechanisms // we cannot do this as logged user because we lose privileges. - if (is_win) return exec(lock_binary + ' --block', cb); - - if (is_linux || (is_mac && !kill_apps)) return cb(); - - kill_running_apps(cb); + exec(lock_binary + ' --block', cb); } function after(cb) { @@ -93,9 +75,6 @@ function start(id, opts, cb) { password = opts.password || opts.unlock_pass || default_pass, message = opts.lock_message || ""; - kill_apps = false; - kill_apps = opts.close_apps; - if (!password || password.toString().trim() === '') return cb(new Error('No unlock password given!')) diff --git a/lib/agent/actions/request_permission/index.js b/lib/agent/actions/request_permission/index.js index fb0b595cd..63949f0c1 100644 --- a/lib/agent/actions/request_permission/index.js +++ b/lib/agent/actions/request_permission/index.js @@ -1,4 +1,5 @@ const { EventEmitter } = require('events'); +const path = require('path'); const socket = require('../../socket'); const geoIndex = require('../../providers/geo'); const strategies = require('../../providers/geo/strategies'); @@ -7,6 +8,13 @@ const permissionFile = require('../../../utils/permissionfile'); const osName = process.platform.replace('win32', 'windows').replace('darwin', 'mac'); const { nameArray } = require('../../socket/messages'); +const { join } = path; +const system = require('../../../system'); + +const nodeBin = join(system.paths.current, 'bin', 'node'); + +const actionLocationWin = 'location-permission'; + let emitter; const parseResponse = (data, cb) => { @@ -23,6 +31,41 @@ const done = (id, err) => { if (!emitter) emitter = new EventEmitter(); emitter.emit('end', id, err); }; + +// eslint-disable-next-line consistent-return +const requestAllowPermissionWin = (cb) => { + if (osName.localeCompare('windows') !== 0) return cb(new Error('Action only allowed on windows')); + const data = { + dirs: ['allow'], + }; + system.spawn_as_admin_user(nodeBin, data, ( + errorRequestPermissionWin, + permissionWindowsLocation, + // eslint-disable-next-line consistent-return + ) => { + if (errorRequestPermissionWin) { + return done(new Error(errorRequestPermissionWin)); + } + if (typeof permissionWindowsLocation !== 'function') return done(new Error('Error is not available')); + // eslint-disable-next-line consistent-return + permissionWindowsLocation( + actionLocationWin, + data, + // eslint-disable-next-line consistent-return + (errPermissionWindowsLocation, outPutpermissionWindowsLocation) => { + if (errPermissionWindowsLocation || (outPutpermissionWindowsLocation + && (!Object.prototype.hasOwnProperty.call(outPutpermissionWindowsLocation, 'code') + || outPutpermissionWindowsLocation.code !== 0))) { + const errorOutPut = new Error('Error on osQuery'); + return done(errorOutPut); + } + geoIndex.getLocationRequest(() => {}); + done(); + }, + ); + }); +}; + /** * Requests native permission on MacOS. * @@ -38,8 +81,8 @@ const requestNativePermission = (cb) => { if (permissionNative.localeCompare('false') !== 0 && permissionNative.localeCompare('true') !== 0) { try { strategies.askLocationNativePermission((err, data) => { - parseResponse(data, (err) => { - if (!err) geoIndex.getLocationRequest(()=>{}); + parseResponse(data, (errParse) => { + if (!errParse) geoIndex.getLocationRequest(() => {}); }); cb(err); }); @@ -68,6 +111,9 @@ exports.start = (id, opts, cb) => { case 'native_location': requestNativePermission((err) => done(id, err)); break; + case 'wifi_location': + requestAllowPermissionWin((err) => done(id, err)); + break; default: done(id, new Error('Invalid permission name')); } diff --git a/lib/agent/actions/triggers/index.js b/lib/agent/actions/triggers/index.js index 3d944ca93..3b9aad12c 100644 --- a/lib/agent/actions/triggers/index.js +++ b/lib/agent/actions/triggers/index.js @@ -17,8 +17,6 @@ const websocket = require('../../control-panel/websockets'); const eventsList = [ 'connected', 'disconnected', - 'geofencing_in', - 'geofencing_out', 'new_location', 'mac_address_changed', 'ssid_changed', diff --git a/lib/agent/commands.js b/lib/agent/commands.js index 9f5100761..dd1b69455 100644 --- a/lib/agent/commands.js +++ b/lib/agent/commands.js @@ -257,7 +257,9 @@ var store = function (type, id, name, opts, cb) { logger.debug(`Storing command in DB: ${[type, name].join('-')}`); delete_same_target(id, name, () => { - if (name == 'geofencing' || name == 'triggers' || name == 'fileretrieval') return cb && cb(); + if (name == 'triggers' || name == 'fileretrieval') + return cb && cb(); + storage.do( 'set', { diff --git a/lib/agent/control-panel/api/devices.js b/lib/agent/control-panel/api/devices.js index 0a5997d14..30b941b96 100644 --- a/lib/agent/control-panel/api/devices.js +++ b/lib/agent/control-panel/api/devices.js @@ -2,7 +2,7 @@ const keys = require('./keys'); const errors = require('./errors'); const request = require('./request'); -const set = (key) => { +exports.set = (key) => { if (!key) throw (new Error('No key!')); keys.set({ device: key }); return key; @@ -19,14 +19,13 @@ exports.link = (data, cb) => { const { body } = resp; - if (body && body.key) { - cb(null, set(body.key)); + if (body?.key) { + cb(null, exports.set(body.key)); } else if (resp.statusCode == 401) { cb(errors.get('INVALID_CREDENTIALS')); } else if (resp.statusCode == 302 || resp.statusCode == 403) { cb(errors.get('NO_AVAILABLE_SLOTS')); } else if (resp.statusCode == 422 || body.errors) { - const obj = body.errors || body; cb(errors.unprocessable(body)); } else { cb(errors.unknown(resp)); @@ -116,13 +115,6 @@ exports.get.status = (cb) => { request.get(`/devices/${keys.get().device}/status.json`, {}, cb); }; -exports.get.geofences = (cb) => { - const device_key = keys.get().device; - if (!device_key) return cb(errors.get('NO_DEVICE_KEY')); - - request.get(`/devices/${device_key}/geofencing.json`, {}, cb); -}; - exports.get.triggers = (cb) => { const device_key = keys.get().device; if (!device_key) return cb(errors.get('NO_DEVICE_KEY')); diff --git a/lib/agent/control-panel/api/request.js b/lib/agent/control-panel/api/request.js index 912344b9d..14b86cefc 100644 --- a/lib/agent/control-panel/api/request.js +++ b/lib/agent/control-panel/api/request.js @@ -1,9 +1,9 @@ const needle = require('needle'); const https = require('https'); const keys = require('./keys'); -const logger = require('./logger'); const common = require('../../../common'); const config = require('../../../utils/configfile'); +const logger = common.logger.prefix('api'); const defaults = { client: needle, diff --git a/lib/agent/control-panel/index.js b/lib/agent/control-panel/index.js index 301093f32..a2f66f8ef 100644 --- a/lib/agent/control-panel/index.js +++ b/lib/agent/control-panel/index.js @@ -55,9 +55,11 @@ const handle_response = (what, err, resp) => { }; const load_hooks = () => { + if (osName.localeCompare('windows') === 0 || osName.localeCompare('darwin') === 0) { + hooks.on(nameArray[1], listeners.reactToCheckLocationPerms); + } if (osName.localeCompare('darwin') === 0) { hooks.on(nameArray[0], listeners.getLocationMacSVC); - hooks.on(nameArray[1], listeners.reactToCheckLocationPerms); hooks.on(nameArray[2], listeners.getPictureMacSVC); hooks.on(nameArray[3], listeners.getScreenshotMacSVC); hooks.on(nameArray[4], listeners.getScreenshotAgentMacSVC); @@ -80,9 +82,11 @@ const load_hooks = () => { }; const unload_hooks = () => { + if (osName.localeCompare('windows') === 0 || osName.localeCompare('darwin') === 0) { + hooks.remove(nameArray[1], listeners.reactToCheckLocationPerms); + } if (osName.localeCompare('darwin') === 0) { hooks.remove(nameArray[0], listeners.getLocationMacSVC); - hooks.remove(nameArray[1], listeners.reactToCheckLocationPerms); hooks.remove(nameArray[2], listeners.getPictureMacSVC); hooks.remove(nameArray[3], listeners.getScreenshotMacSVC); hooks.remove(nameArray[4], listeners.getScreenshotAgentMacSVC); @@ -104,7 +108,11 @@ const boot = (cb) => { socket.writeMessage(nameArray[6], () => { network.isWifiPermissionActive((output) => { permissionFile.setData('wifiLocation', stringBooleanOrEmpty(output), () => { - permissions.getLocationPermission(); + if (osName.localeCompare('windows') === 0) { + setTimeout(() => { permissions.getLocationPermission(); }, 10000); + } else { + permissions.getLocationPermission(); + } websocket.load.call(common, (err, emitter) => { setInterval(() => { socket.writeMessage(nameArray[6]); diff --git a/lib/agent/control-panel/sender.js b/lib/agent/control-panel/sender.js index 22b039a2e..23adf83a1 100644 --- a/lib/agent/control-panel/sender.js +++ b/lib/agent/control-panel/sender.js @@ -56,7 +56,7 @@ exports.init = function (common) { }; exports.notify_action = function (status, id, name, opts, err, out) { - if (name === 'geofencing' || name === 'triggers') return; // geofencing and triggers needs to send custom notification + if (name === 'triggers') return; // triggers needs to send custom notification let body = { command: 'start', diff --git a/lib/agent/control-panel/websockets/index.js b/lib/agent/control-panel/websockets/index.js index 77ade8c42..7cd70e2d1 100644 --- a/lib/agent/control-panel/websockets/index.js +++ b/lib/agent/control-panel/websockets/index.js @@ -492,7 +492,7 @@ exports.notify_action = ( retries = 0, fromWithin = false, ) => { - if (!id || id === 'report' || action === 'triggers' || action === 'geofencing') return; + if (!id || id === 'report' || action === 'triggers') return; if (retries >= retriesMax) { storage.do('del', { type: 'responses', id: respId }); exports.responses_queue = exports.responses_queue.filter((x) => x.id !== respId); diff --git a/lib/agent/helpers.js b/lib/agent/helpers.js index a01b74edb..d70c95a72 100644 --- a/lib/agent/helpers.js +++ b/lib/agent/helpers.js @@ -1,22 +1,13 @@ -"use strict"; +const semver = require('semver'); -var semver = require('semver'), - exceptions = require('../exceptions'); +const helpers = {}; -var helpers = {}; - -helpers.running_on_background = function() { - return helpers.run_via_service() || helpers.no_console_attached(); -} +helpers.running_on_background = () => helpers.run_via_service() || helpers.no_console_attached(); // returns true if no terminal attached, or stdout is not a tty -helpers.no_console_attached = function(){ - return (!process.stdout.isTTY || process.env.TERM == 'dumb'); -} +helpers.no_console_attached = () => (!process.stdout.isTTY || process.env.TERM === 'dumb'); -helpers.run_via_service = function(){ - return (process.platform == 'win32' && !process.env.HOMEPATH); -} +helpers.run_via_service = () => (process.platform === 'win32' && !process.env.HOMEPATH); helpers.greaterOrEqual = (first, second) => { if (typeof first !== 'string' || typeof second !== 'string') return -1; @@ -36,42 +27,26 @@ helpers.greaterOrEqual = (first, second) => { return (versionADotInt >= versionBDotInt); }; -// is_greater_than("1.3.10", "1.3.9") returns true -helpers.is_greater_than = function(first, second) { - return semver_wrapper('gt', first, second); -}; - -helpers.is_greater_or_equal = function(first, second) { - return semver_wrapper('gte', first, second); -}; - -function semver_wrapper(method_name, first, second) { - var valid = validate_versions([first, second], method_name); - - return valid && semver[method_name](first, second); -} +const validateVersions = (versions) => { + const invalidVersions = []; -function validate_versions(versions, method_name) { - var invalid_versions = []; - - versions.forEach(function(el) { - if(!semver.valid(el)) { - invalid_versions.push(el); + versions.forEach((el) => { + if (!semver.valid(el)) { + invalidVersions.push(el); } }); - if(invalid_versions.length > 0) { - // For now the exception is removed because it's been sent for all OS version above 10.6.0 - // handle_version_error(method_name, invalid_versions); - return false; - } - return true; -} + return invalidVersions.length <= 0; +}; + +const semverWrapper = (methodName, first, second) => { + const valid = validateVersions([first, second]); + return valid && semver[methodName](first, second); +}; +// is_greater_than("1.3.10", "1.3.9") returns true +helpers.is_greater_than = (first, second) => semverWrapper('gt', first, second); -function handle_version_error(method_name, versions) { - var err_msg = "Cannot run" + method_name + ". Invalid versions: "; - err_msg = err_msg.concat(versions.join(" ")); - exceptions.send(new Error(err_msg)); -} +helpers.is_greater_or_equal = (first, second) => semverWrapper('gte', first, second); +helpers.semverWrapper = semverWrapper; module.exports = helpers; diff --git a/lib/agent/index.js b/lib/agent/index.js index 464116236..c32671814 100644 --- a/lib/agent/index.js +++ b/lib/agent/index.js @@ -26,7 +26,7 @@ const { isBoolean } = require('./utils/utilsprey'); const config = require('../utils/configfile'); const fetchEnvVar = require('../utils/fetch-env-var'); -const watchList = ['connection', 'control-zones', 'hostname', 'location', 'network', 'power', 'status']; +const watchList = ['connection', 'hostname', 'location', 'network', 'power', 'status']; let running = false; let startedAt = null; let runningAs = null; diff --git a/lib/agent/permissions/windows.js b/lib/agent/permissions/windows.js index 5449226ea..cc3b240a8 100644 --- a/lib/agent/permissions/windows.js +++ b/lib/agent/permissions/windows.js @@ -1,4 +1,44 @@ +const path = require('path'); + +const { join } = path; +const system = require('../../system'); + +const actionLocationWin = 'location-permission'; +const nodeBin = join(system.paths.current, 'bin', 'node'); +const { nameArray } = require('../socket/messages'); +const hooks = require('../hooks'); + const getLocationPermission = () => { + const data = { + key: 'device-key', + token: 'token', + logged: false, + dirs: ['get'], + }; + system.spawn_as_admin_user(nodeBin, data, ( + errorRequestPermissionWin, + permissionWindowsLocation, + // eslint-disable-next-line consistent-return + ) => { + if (errorRequestPermissionWin) { + return; + } + if (typeof permissionWindowsLocation !== 'function') return; + // eslint-disable-next-line consistent-return + permissionWindowsLocation( + actionLocationWin, + data, + // eslint-disable-next-line consistent-return + (errPermissionWindowsLocation, outPutpermissionWindowsLocation) => { + if (errPermissionWindowsLocation || (outPutpermissionWindowsLocation + && (!(Object.hasOwn(outPutpermissionWindowsLocation, 'code')) + || outPutpermissionWindowsLocation.code !== 0))) { + return; + } + hooks.trigger(nameArray[1], [true, outPutpermissionWindowsLocation.Message, () => {}]); + }, + ); + }); }; exports.getLocationPermission = getLocationPermission; diff --git a/lib/agent/providers/geo/index.js b/lib/agent/providers/geo/index.js index e90af820c..b420e5bb6 100644 --- a/lib/agent/providers/geo/index.js +++ b/lib/agent/providers/geo/index.js @@ -4,6 +4,8 @@ const logger = require('../../common').logger.prefix('geo'); const permissionFile = require('../../../utils/permissionfile'); const socket = require('../../socket'); const { nameArray } = require('../../socket/messages'); +const { getLocationPermission } = require('../../permissions'); + const osName = process.platform.replace('win32', 'windows').replace('darwin', 'mac'); const strategiesList = ['native', 'wifi', 'geoip']; @@ -59,6 +61,7 @@ exports.fetch_location = (cb) => { strategies[defaultStrategy]((err, res) => strategyCallback(err, res, cb)); }); } else { + if (osName === 'windows') setTimeout(() => { getLocationPermission(); }, 8000); current = defaultStrategy; strategies[defaultStrategy]((err, res) => strategyCallback(err, res, cb)); } diff --git a/lib/agent/socket/listeners.js b/lib/agent/socket/listeners.js index f0c3f9b24..c78dd527d 100644 --- a/lib/agent/socket/listeners.js +++ b/lib/agent/socket/listeners.js @@ -2,33 +2,68 @@ const api = require('../control-panel/api'); const network = require('../providers/network'); const permissionFile = require('../../utils/permissionfile'); const { getInformationChannel, stringBooleanOrEmpty } = require('../utils/utilsprey'); + +const osName = process.platform.replace('win32', 'windows').replace('darwin', 'mac'); /** * Function to react to check location permissions by updating native and wifi location data. * * @param {Array} data - The data containing location information */ +exports.callApi = (dataToSend) => api.push.event(dataToSend, { json: true }); +exports.isWifiPermissionActive = (cb) => { + network.isWifiPermissionActive((output) => { + cb(output); + }); +}; + +exports.getDataFromPermissionFile = (dataToSet, data, cb) => { + permissionFile.setData(dataToSet, data, () => { + cb(); + }); +}; + +const sendDataToEndpoint = (wifiLocationPermission, nativeLocationPermission, cb) => { + // eslint-disable-next-line max-len + const dataToSend = { + name: 'list_permission', + info: { + wifi_location: wifiLocationPermission, + native_location: nativeLocationPermission, + }, + }; + exports.callApi(dataToSend); + try { + const callback = cb; + if (typeof callback === 'function') callback(); + } catch (errorCallback) { + console.log(errorCallback); + } +}; const reactToCheckLocationPerms = (data) => { - permissionFile.setData('nativeLocation', data[1].result, () => { - network.isWifiPermissionActive((output) => { - permissionFile.setData('wifiLocation', stringBooleanOrEmpty(output), () => { - // eslint-disable-next-line max-len - const dataToSend = { - name: 'list_permission', - info: { - wifi_location: output.toString(), - native_location: data[1].result, - }, - }; - api.push.event(dataToSend, { json: true }); - try { - const callback = data[2]; - if (typeof callback === 'function') callback(); - } catch (errorCallback) { - console.log(errorCallback); - } + let wifiLocationPermission = 'false'; + switch (osName) { + case 'windows': + try { + if (data[1].localeCompare('Allow') === 0) wifiLocationPermission = 'true'; + exports.getDataFromPermissionFile('wifiLocation', wifiLocationPermission, () => { + sendDataToEndpoint(wifiLocationPermission, 'false', () => {}); + }); + } catch (error) { + console.log(error); + } + break; + case 'mac': + exports.getDataFromPermissionFile('nativeLocation', data[1].result, () => { + exports.isWifiPermissionActive((output) => { + exports.getDataFromPermissionFile('wifiLocation', stringBooleanOrEmpty(output), () => { + sendDataToEndpoint(output.toString(), data[1].result, data[2]); + }); + }); }); - }); - }); + break; + default: + break; + } }; /** * Process data to extract WiFi information and callback with the result. @@ -88,3 +123,5 @@ exports.getPictureMacSVC = getPictureMacSVC; exports.getScreenshotMacSVC = getScreenshotMacSVC; exports.getScreenshotAgentMacSVC = getScreenshotAgentMacSVC; exports.reacToWatcher = reacToWatcher; +// exports.getDataFromPermissionFile = getDataFromPermissionFile; +// exports.isWifiPermissionActive = isWifiPermissionActive; diff --git a/lib/agent/triggers/control-zones/index.js b/lib/agent/triggers/control-zones/index.js deleted file mode 100644 index be18d09ac..000000000 --- a/lib/agent/triggers/control-zones/index.js +++ /dev/null @@ -1,192 +0,0 @@ -"use strict"; - -////////////////////////////////////////// -// Prey Geofencing Plugin -// (c) 2012 - Fork Ltd. -// by Tomas Pollak and Javier Acuña - http://forkhq.com -// GPLv3 Licensed -// -// The geofencing module takes care of notifying the Prey servers -// when the device either gets in or out of a specific geofence. -// -// The command signature is as follows: -// -// command: 'watch', -// target: 'geofencing', -// options: { -// locations: [{ -// id: 'someId', -// lat: '-33.4230905', -// lng: '-70.6138094', -// radius: 100, -// direction: 'in'|'out'|'both', -// expires: -1 -// }] -// } -// -////////////////////////////////////////// - -var join = require('path').join, - base_path = join(__dirname, '..', '..'), - hooks = require(join(base_path, 'hooks')), - logger = require('./../../common').logger.prefix('geofencing'), - LatLon = require('./../location/lib/latlng'), - action = require('./../../actions/geofencing'), - storage = require('./../../utils/storage'), - api = require('./../../control-panel/api'), - Emitter = require('events').EventEmitter; - -var emitter, - zones, - location, - attempts = 0, - retryTimeOut, - checking = false; - -let timerActionStart; - -var push_event = function(type, zone_id, coords) { - coords.id = zone_id.id; - var data = { - name: type, - info: coords - } - var opts = { json: true }; - api.push.methods['event'](data, opts); -} - -var getDistance = function(latlng1, latlng2) { - var p1 = new LatLon(latlng1.lat, latlng1.lng); - var p2 = new LatLon(latlng2.lat, latlng2.lng); - return p1.distanceTo(p2); -} - -var fibonacci = function(attempt) { - if (attempt == 0) return 0; - else if (attempt == 1) return 1; - else return (fibonacci(attempt - 1) + fibonacci(attempt - 2)); -} - -var check_location = function(delay) { - attempts++; - retryTimeOut = setTimeout(function() { - hooks.trigger('get_location', 'geofencing'); - }, delay * 1000 * 60) // Delay in minutes: 0, 1, 1, 2, 3, 5, 8, 13, .... -} - -var check_zones = function() { - if (!location || checking) return; - checking = true; - - zones.forEach(function (zone) { - var origin = {lat: zone.lat, lng: zone.lng}; - - logger.debug('Verifying geofence: ' + zone.id + ', Radius: ' + zone.radius); - logger.debug("Current location: " + location.lat + "," + location.lng); - - // in case an origin wasnt passed, set the first location as the origin - //if (!this.origin) return this.origin = { coords; - var distance = getDistance(origin, location); - - logger.debug("Current distance from origin: " + distance); - // distance comes in KM, so we transform to M to compare - - if (distance * 1000 > zone.radius) { // outside - storage.do('update', { type: 'geofences', id: zone.id, columns: 'state', values: 'outside' }, (err) => { - if (err) { - logger.error(err); - return; - } - if (zone.state == 'inside' && zone.direction !== 'in') { - logger.info('Device left the geofence ' + zone.name + '! Notifying') - hooks.trigger('geofencing_out', zone.id) - push_event('geofencing_out', {id: zone.id}, location); - } - checking = false; - zone.state = 'outside'; - }); - } else { // inside - storage.do('update', { type: 'geofences', id: zone.id, columns: 'state', values: 'inside' }, (err) => { - if (err) { - logger.error(err); - return; - } - if ((zone.state == 'outside' && zone.direction !== 'out') || zone.state == null) { - logger.info('Device got inside the geofence ' + zone.name + '! Notifying') - hooks.trigger('geofencing_in', zone.id) - push_event('geofencing_in', {id: zone.id}, location); - } - checking = false; - zone.state = 'inside'; - }); - } - }); -} - -exports.start = function(opts, cb) { - hooks.on('geofencing_start', function(fences) { - if (!fences) return; - - zones = fences; - - if (!location) return; - - if (zones.length == 0) { - attempts = 0; - clearTimeout(retryTimeOut) - return; - } - - check_zones(); - }); - - hooks.on('new_location', function(coords) { - // Only check zones on significant location changes - if (!coords || (coords.delta && coords.delta < 30)) return; - - // When a new trustful location comes it has to be saved, even if there are no fences - if (coords.method == 'wifi' && coords.accuracy < 300) - location = coords; - - if (!zones || zones.length == 0) return; - - // If the location isn't trustworthy ask the location again - if ((coords.method != 'wifi' && coords.method != 'native') || coords.accuracy > 300) - return check_location(fibonacci(attempts)); - - clearTimeout(retryTimeOut) - attempts = 0; - - check_zones(); - }) - - // Fetch geofences and stop previous location check - hooks.on('connected', function() { - clearTimeout(retryTimeOut) - attempts = 0; - if (timerActionStart) clearTimeout(timerActionStart); - timerActionStart = setTimeout(() => { - action.start(); - }, 1000 * 15); - }) - - // No need to keep checking the location until it's connected again - hooks.on('disconnected', function() { - if (timerActionStart) clearTimeout(timerActionStart); - clearTimeout(retryTimeOut) - attempts = 0; - }) - - emitter = new Emitter(); - cb(null, emitter); - -}; - -exports.stop = function() { - hooks.remove('geofencing_start'); - hooks.remove('new_location'); - if (emitter) { - emitter.removeAllListeners(); - emitter = null; - } -}; \ No newline at end of file diff --git a/lib/agent/triggers/status/index.js b/lib/agent/triggers/status/index.js index 08c4efef1..6f562d357 100755 --- a/lib/agent/triggers/status/index.js +++ b/lib/agent/triggers/status/index.js @@ -91,8 +91,10 @@ exports.get_status = (cb, nameCallBack = '') => { if (callbacksList.length >= 1) { callbacksList.forEach((element) => { - if (element && element.cb && element.cb === 'function') { + try { element.cb(err, exports.status); + } catch (e) { + console.log(e); } }); } diff --git a/lib/agent/utils/storage.js b/lib/agent/utils/storage.js index 48410c3f1..835da3d55 100644 --- a/lib/agent/utils/storage.js +++ b/lib/agent/utils/storage.js @@ -25,11 +25,6 @@ const types = { keys: ['id', 'action_id', 'opts', 'out', 'error', 'status', 'action', 'time', 'retries'], values: (data) => `'${data.id}', '${data.action_id}', '${data.opts}', '${data.out}', '${data.error}', '${data.status}', '${data.action}' , '${data.time}', 0`, }, - geofences: { - schema: 'id TEXT PRIMARY KEY, name TEXT, state TEXT', - keys: ['id', 'name', 'state'], - values: (data) => `'${data.id}', '${data.name}', '${data.state}'`, - }, files: { schema: 'id TEXT PRIMARY KEY, name TEXT, path TEXT, size TEXT, user TEXT, attempt INTEGER, resumable INTEGER', keys: ['id', 'name', 'path', 'size', 'user', 'attempt', 'resumable'], @@ -119,7 +114,7 @@ const init = function (type, path, cb) { create_db(); }); } else { - recover_db(existing_db, (err) => { + recoverDb(existing_db, (err) => { if (err) { erase(storagePath, () => { storagePath = backupPath; @@ -222,7 +217,7 @@ var erase = function (path, cb) { }); }; // Recover data from the old commands.db file and converts it -var recover_db = function (db, cb) { +const recoverDb = (db, cb) => { const get_tables = (cb) => { db.all(queries.TABLES(), (err, data) => { let tables = data.map((x) => x.name); @@ -283,14 +278,13 @@ var recover_db = function (db, cb) { if (db_data.options && db_data.options.messageID) id = db_data.options.messageID; else id = uuidv4(); } - if (table == 'geofences') id = Object.values(value)[0].id; if (table == 'files') id = Object.keys(value)[0].split('-')[1]; if (table == 'triggers') { id = Object.values(value)[0].id; if (!Object.values(value)[0].persist) db_data.persist = 0; else db_data.persist = 1; } - if (table == 'versions') { + if (table === 'versions') { id = Object.keys(value)[0].split('-')[1]; if (!Object.values(value)[0].notified) db_data.notified = 0; else db_data.notified = 1; @@ -311,5 +305,5 @@ var recover_db = function (db, cb) { }; exports.init = init; exports.erase = erase; -exports.recover_db = recover_db; +exports.recover_db = recoverDb; module.exports.storage_fns = storage_fns; diff --git a/lib/conf/tasks/prey_owl.js b/lib/conf/tasks/prey_owl.js index d4468d8e0..7b4981eee 100644 --- a/lib/conf/tasks/prey_owl.js +++ b/lib/conf/tasks/prey_owl.js @@ -6,7 +6,8 @@ const satan = require('satan'); const { join } = require('path'); const { exec } = require('child_process'); // eslint-disable-next-line camelcase -const { is_greater_or_equal } = require('../../agent/helpers'); +const { is_greater_or_equal, greaterOrEqual } = require('../../agent/helpers'); +const system = require('../../system'); const paths = require('../../system/paths'); const common = require('../../agent/common'); @@ -38,6 +39,7 @@ const installVersionsPath = join(`${paths.install}`, 'versions', 'prey-user'); exports.existsNewPath = `test -f ${newPath} && echo exists`; exports.existsOldpath = `test -f ${oldPath} && echo exists`; exports.deleteInstallPreyUserBinary = `rm -fr ${join(`${paths.install}`, 'prey-user')}`; +exports.deleteInstallPreyUserPath = `rm -fr ${installVersionsPath}`; exports.copyCurrentToInstallVersionPath = `/bin/cp ${currentPathPreyUser} ${installVersionsPath}`; const source = join(paths.current, 'bin', 'prey-user'); @@ -64,10 +66,26 @@ const testExistingConfigurations = (cb) => { }); exec(`launchctl unload ${pathLaunchDaemon}${newWatcherKey}`, (errorUnload) => { if (errorUnload) logger.error(`Launchctl unload error: ${errorUnload}`); - exec(exports.copyCurrentToInstallVersionPath, (error) => { - if (error) logger.error(`Error when copy file to destionation: ${error}`); - exec(`launchctl load ${pathLaunchDaemon}${newWatcherKey}`, (errorLoad) => { - if (errorLoad) logger.error(`Launchctl load error: ${errorLoad}`); + system.get_os_version((err, osVersion) => { + if (err) { + if (typeof cb !== 'function') return; + return cb(err); + } + if (greaterOrEqual('13.0.0', osVersion)) { + return exec(exports.deleteInstallPreyUserPath, () => { + exec(exports.copyCurrentToInstallVersionPath, (error) => { + if (error) logger.error(`Error when copy file to destionation: ${error}`); + exec(`launchctl load ${pathLaunchDaemon}${newWatcherKey}`, (errorLoad) => { + if (errorLoad) logger.error(`Launchctl load error: ${errorLoad}`); + }); + }); + }); + } + exec(exports.copyCurrentToInstallVersionPath, (error) => { + if (error) logger.error(`Error when copy file to destionation: ${error}`); + exec(`launchctl load ${pathLaunchDaemon}${newWatcherKey}`, (errorLoad) => { + if (errorLoad) logger.error(`Launchctl load error: ${errorLoad}`); + }); }); }); }); diff --git a/lib/system/windows/bin/wpxsvc.exe b/lib/system/windows/bin/wpxsvc.exe index 73842158d..429b3250c 100644 Binary files a/lib/system/windows/bin/wpxsvc.exe and b/lib/system/windows/bin/wpxsvc.exe differ diff --git a/package-lock.json b/package-lock.json index b6306b05e..f86524490 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prey", - "version": "1.13.6", + "version": "1.13.8", "lockfileVersion": 1, "requires": true, "packages": { "": { "name": "prey", - "version": "1.13.3", + "version": "1.13.7", "os": [ "linux", "darwin", diff --git a/package.json b/package.json index caf554156..7b2d936f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prey", - "version": "1.13.6", + "version": "1.13.8", "author": "Engineering ", "keywords": [ "prey", diff --git a/test/lib/agent/ack.test.js b/test/lib/agent/ack.test.js new file mode 100644 index 000000000..df3890b44 --- /dev/null +++ b/test/lib/agent/ack.test.js @@ -0,0 +1,65 @@ +/* eslint-disable no-unused-expressions */ +/* eslint-disable no-undef */ +const { expect } = require('chai'); +const ack = require('../../../lib/agent/ack'); + +describe('ack testing', () => { + describe('existKeyAckInJson', () => { + it('debería regresar true si el objeto tiene una propiedad ack_id', () => { + const json = { ack_id: '123' }; + expect(ack.existKeyAckInJson(json)).to.be.true; + }); + + it('debería regresar false si el objeto no tiene una propiedad ack_id', () => { + const json = { foo: 'bar' }; + expect(ack.existKeyAckInJson(json)).to.be.false; + }); + }); + + describe('existKeyIdInJson', () => { + it('debería regresar true si el objeto tiene una propiedad id', () => { + const json = { id: '123' }; + expect(ack.existKeyIdInJson(json)).to.be.true; + }); + + it('debería regresar false si el objeto no tiene una propiedad id', () => { + const json = { foo: 'bar' }; + expect(ack.existKeyIdInJson(json)).to.be.false; + }); + }); + describe('processAck', () => { + it('should return an error if json does not have ack_id', (done) => { + const json = { foo: 'bar' }; + ack.processAck(json, (err, result) => { + expect(err).to.be.an('error'); + expect(err.message).to.equal('there is no key ack_id in the json'); + expect(result).to.be.undefined; + done(); + }); + }); + it('should return the expected object if json has ack_id', (done) => { + const json = { ack_id: '123', id: '456' }; + ack.processAck(json, (err, result) => { + expect(err).to.be.null; + expect(result).to.deep.equal({ + ack_id: '123', + type: 'ack', + id: '456', + }); + done(); + }); + }); + it('should return the expected object if json has ack_id but does not have id', (done) => { + const json = { ack_id: '123' }; + ack.processAck(json, (err, result) => { + expect(err).to.be.null; + expect(result).to.deep.equal({ + ack_id: '123', + type: 'ack', + id: '', + }); + done(); + }); + }); + }); +}); diff --git a/test/lib/agent/control-panel/api/devices.test.js b/test/lib/agent/control-panel/api/devices.test.js new file mode 100644 index 000000000..afe738a88 --- /dev/null +++ b/test/lib/agent/control-panel/api/devices.test.js @@ -0,0 +1,174 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); +const keys = require('../../../../../lib/agent/control-panel/api/keys'); +const errors = require('../../../../../lib/agent/control-panel/api/errors'); +const request = require('../../../../../lib/agent/control-panel/api/request'); +const devices = require('../../../../../lib/agent/control-panel/api/devices'); + +describe('Devices Tests', () => { + let requestPostStub; + let keysSetStub; + let keysGetStub; + + beforeEach(() => { + requestPostStub = sinon.stub(request, 'post'); + + if (keys.get.restore) { + keys.get.restore(); + } + + keysSetStub = sinon.stub(keys, 'set'); + keysGetStub = sinon.stub(keys, 'get'); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('set function', () => { + it('should set the device key correctly', () => { + keysSetStub.returns({ api: 'api-key', device: 'new-device-key' }); + + const result = devices.set('new-device-key'); + expect(result).to.equal('new-device-key'); + expect(keysSetStub.calledOnceWith({ device: 'new-device-key' })).to.be.true; + }); + + it('should throw an error if no key is provided', () => { + expect(() => devices.set(null)).to.throw('No key!'); + }); + }); + + describe('link function', () => { + it('should return an error if data is missing', () => { + const cb = sinon.spy(); + devices.link({}, cb); + const expectedError = errors.arguments('Empty data.'); + expect(cb.calledOnce).to.be.true; + expect(cb.args[0][0].message).to.equal(expectedError.message); + }); + + it('should return an error if the device key is already set', () => { + const cb = sinon.spy(); + keysGetStub.returns({ api: 'api-key', device: 'device-id' }); + devices.link({ someData: true }, cb); + const expectedError = errors.get('DEVICE_KEY_SET'); + expect(cb.calledOnce).to.be.true; + expect(cb.args[0][0].message).to.equal(expectedError.message); + expect(cb.args[0][0].code).to.equal(expectedError.code); + }); + + it('should return NO_API_KEY error if API key is not set', () => { + const cb = sinon.spy(); + keysGetStub.returns({ api: null, device: null }); // Ensure `api` is null or undefined + devices.link({ someData: true }, cb); + const expectedError = errors.get('NO_API_KEY'); + expect(cb.calledOnce).to.be.true; // Callback should be called once + expect(cb.args[0][0].message).to.equal(expectedError.message); // Compare error message + expect(cb.args[0][0].code).to.equal(expectedError.code); // Compare error code + }); + + /* it('should handle a successful response with a new device key', () => { + const cb = sinon.spy(); + requestPostStub.callsFake((url, data, opts, callback) => { + callback(null, { body: { key: 'new-key' } }); + }); + + devices.link({ someData: true }, cb); + expect(cb.calledWith(null, 'new-key')).to.be.true; + }); + + it('should handle response code 401 as INVALID_CREDENTIALS', () => { + const cb = sinon.spy(); + requestPostStub.callsFake((url, data, opts, callback) => { + callback(null, { statusCode: 401 }); + }); + + devices.link({ someData: true }, cb); + expect(cb.calledWith(errors.get('INVALID_CREDENTIALS'))).to.be.true; + }); */ + }); + +/* describe('unlink function', () => { + it('should return an error if keys are missing', () => { + keysGetStub.returns({}); + const cb = sinon.spy(); + devices.unlink(cb); + expect(cb.calledWith(errors.get('MISSING_KEY'))).to.be.true; + }); + + it('should successfully unset the device key on 200 response', () => { + requestPostStub.callsFake((url, opts, callback) => { + callback(null, { statusCode: 200 }); + }); + + const cb = sinon.spy(); + devices.unlink(cb); + expect(cb.calledWith()).to.be.true; + }); + }); */ + + /* describe('post_location function', () => { + it('should return an error if keys are missing', () => { + keysGetStub.returns({}); + const cb = sinon.spy(); + devices.post_location({ location: true }, cb); + expect(cb.calledWith(errors.get('MISSING_KEY'))).to.be.true; + }); + + it('should return state true for 200 response', () => { + requestPostStub.callsFake((url, data, opts, callback) => { + callback(null, { statusCode: 200 }); + }); + + const cb = sinon.spy(); + devices.post_location({ location: true }, cb); + expect(cb.calledWith(null, true)).to.be.true; + }); + }); */ + + describe('post_sso_status function', () => { + /* it('should return an error if data is missing', () => { + const cb = sinon.spy(); + devices.post_sso_status(null, cb); + expect(cb.calledWith(errors.arguments('Empty data.'))).to.be.true; + }); */ + + it('should call the callback on a 200 response', () => { + requestPostStub.callsFake((url, data, opts, callback) => { + callback(null, { statusCode: 200 }); + }); + + const cb = sinon.spy(); + devices.post_sso_status({ status: true }, cb); + expect(cb.calledWith(null)).to.be.true; + }); + }); + + /*describe('post_missing function', () => { + it('should return an error if invalid option is passed', () => { + const cb = sinon.spy(); + devices.post_missing('invalid', cb); + expect(cb.calledWithMatch(sinon.match.instanceOf(Error))).to.be.true; + }); + + it('should return SAME_MISSING_STATE for a 201 response', () => { + requestPostStub.callsFake((url, data, opts, callback) => { + callback(null, { statusCode: 201 }); + }); + + const cb = sinon.spy(); + devices.post_missing(true, cb); + expect(cb.calledWith(errors.get('SAME_MISSING_STATE'))).to.be.true; + }); + }); + + describe('get.commands function', () => { + it('should return an error if no device key is set', () => { + keys.get.returns({}); + const cb = sinon.spy(); + devices.get.commands(cb); + expect(cb.calledWith(errors.get('NO_DEVICE_KEY'))).to.be.true; + }); + }); */ +}); diff --git a/test/lib/agent/control-panel/api/errors.test.js b/test/lib/agent/control-panel/api/errors.test.js index 03f89fc76..ca9a11353 100644 --- a/test/lib/agent/control-panel/api/errors.test.js +++ b/test/lib/agent/control-panel/api/errors.test.js @@ -1,7 +1,6 @@ const { expect } = require('chai'); const sinon = require('sinon'); -const errors = require('../../../../../lib/agent/control-panel/api/errors'); // Adjust this path as needed - +const errors = require('../../../../../lib/agent/control-panel/api/errors'); describe('Errors Module', () => { describe('get function', () => { it('should return an error with the correct message for a known code', () => { diff --git a/test/lib/agent/control-panel/api/index.test.js b/test/lib/agent/control-panel/api/index.test.js index 6da02d1a7..6f9e3af65 100644 --- a/test/lib/agent/control-panel/api/index.test.js +++ b/test/lib/agent/control-panel/api/index.test.js @@ -2,7 +2,7 @@ const { expect } = require('chai'); const sinon = require('sinon'); const logger = require('../../../../../lib/agent/control-panel/api/logger'); const request = require('../../../../../lib/agent/control-panel/api/request'); -const index = require('../../../../../lib/agent/control-panel/api/index'); // Adjust this path +const index = require('../../../../../lib/agent/control-panel/api/index'); describe('Module Exports and use Function', () => { diff --git a/test/lib/agent/helpers.test.js b/test/lib/agent/helpers.test.js new file mode 100644 index 000000000..d94e77b57 --- /dev/null +++ b/test/lib/agent/helpers.test.js @@ -0,0 +1,92 @@ +/* eslint-disable no-unused-expressions */ +/* eslint-disable no-undef */ +const sinon = require('sinon'); +const { expect } = require('chai'); +const helpers = require('../../../lib/agent/helpers'); + +describe('helpers testing washin', () => { + describe('running_on_background', () => { + beforeEach(() => { + sinon.restore(); + }); + it('debería regresar true si se está ejecutando en segundo plano', () => { + sinon.stub(helpers, 'run_via_service').returns(true); + sinon.stub(helpers, 'no_console_attached').returns(true); + expect(helpers.running_on_background()).to.be.true; + }); + + it('debería regresar false si no se está ejecutando en segundo plano', () => { + sinon.stub(helpers, 'run_via_service').returns(false); + sinon.stub(helpers, 'no_console_attached').returns(false); + expect(helpers.running_on_background()).to.be.false; + }); + }); + + describe('greaterOrEqual', () => { + it('debería regresar true si la versión es mayor o igual', () => { + expect(helpers.greaterOrEqual('1.2.3', '1.2.2')).to.be.true; + }); + + it('debería regresar false si la versión es menor', () => { + expect(helpers.greaterOrEqual('1.2.2', '1.2.3')).to.be.false; + }); + + it('debería regresar false si la versión es menor', () => { + expect(helpers.greaterOrEqual('10.2.2', '1.2.3')).to.be.false; + }); + + it('debería regresar false si la versión es menor', () => { + expect(helpers.greaterOrEqual('1.2.2', '10.2.3')).to.be.false; + }); + + it('debería regresar false si la versión es menor', () => { + expect(helpers.greaterOrEqual('10.2.2', '10.2.3')).to.be.false; + }); + + it('debería regresar false si la versión es menor', () => { + expect(helpers.greaterOrEqual('1.20.2', '1.20.3')).to.be.false; + }); + + it('debería regresar false si la versión es menor', () => { + expect(helpers.greaterOrEqual('1.2.20', '1.2.30')).to.be.false; + }); + }); + + describe('semverWrapper', () => { + it('debería regresar el resultado de la función de semver', () => { + const methodName = 'gt'; + const first = '1.2.3'; + const second = '1.2.2'; + const result = helpers.semverWrapper(methodName, first, second); + expect(result).to.be.true; + }); + + it('debería fallar el resultado de la función de semver', () => { + const methodName = 'gt'; + const first = '1.2.3'; + const second = '1xx'; + const result = helpers.semverWrapper(methodName, first, second); + expect(result).to.be.false; + }); + }); + + describe('is_greater_than', () => { + it('debería regresar true si la versión es mayor', () => { + expect(helpers.is_greater_than('1.2.3', '1.2.2')).to.be.true; + }); + + it('debería regresar false si la versión es menor o igual', () => { + expect(helpers.is_greater_than('1.2.2', '1.2.3')).to.be.false; + }); + }); + + describe('is_greater_or_equal', () => { + it('debería regresar true si la versión es mayor o igual', () => { + expect(helpers.is_greater_or_equal('1.2.3', '1.2.2')).to.be.true; + }); + + it('debería regresar false si la versión es menor', () => { + expect(helpers.is_greater_or_equal('1.2.2', '1.2.3')).to.be.false; + }); + }); +}); diff --git a/test/lib/agent/socket/listeners.test.js b/test/lib/agent/socket/listeners.test.js new file mode 100644 index 000000000..c557a5b78 --- /dev/null +++ b/test/lib/agent/socket/listeners.test.js @@ -0,0 +1,133 @@ +/* eslint-disable no-unused-expressions */ +/* eslint-disable no-undef */ +// test/lib/agent/socket/listeners.test.js +const sinon = require('sinon'); +const chai = require('chai'); + +const { expect } = chai; +const listeners = require('../../../../lib/agent/socket/listeners'); + +describe('listeners', () => { + // Aquí van a ir nuestros tests + it('debería actualizar la información de ubicación nativa y wifi', () => { + const data = [ + 'check_location_perms', + { result: true }, + sinon.stub(), + ]; + + const permissionFileStub = sinon.stub().callsFake((_x, _y, z) => { + z(); + }); + const networkStub = sinon.stub().callsFake((z) => { + z({ lat: 1, log: 2, accuracy: 3 }); + }); + const apiStub = sinon.stub().callsFake(() => {}); + + listeners.getDataFromPermissionFile = permissionFileStub; + listeners.isWifiPermissionActive = networkStub; + listeners.callApi = apiStub; + + listeners.reactToCheckLocationPerms(data); + + expect(permissionFileStub.calledWith('nativeLocation', true)).to.be.true; + expect(networkStub.called).to.be.true; + expect(apiStub.called).to.be.true; + }); + + it('debería procesar la información de WiFi y llamar al callback con el resultado', () => { + const data = [ + 'wdutil', + { + wdutil: { + WIFI: { + RSSI: '-50', + SSID: 'mi_wifi', + 'MAC Address': '00:11:22:33:44:55', + Channel: '6', + Security: 'WPA2', + }, + }, + }, + sinon.stub(), + ]; + + const callbackStub = data[2]; + + listeners.reactToWdutil(data); + + expect(callbackStub.calledWith(null, sinon.match.object)).to.be.true; + }); + + it('debería llamar al callback con la información de ubicación', () => { + const data = [ + 'get_location_mac_svc', + { location: 'mi ubicación' }, + sinon.stub(), + ]; + + const callbackStub = data[2]; + listeners.getLocationMacSVC(data); + expect(callbackStub.calledWith(null, sinon.match.object)).to.be.true; + }); + + it('debería llamar al callback con la información de la imagen', () => { + const data = [ + 'get_picture_mac_svc', + { picture: 'mi imagen' }, + sinon.stub(), + ]; + + const callbackStub = data[2]; + listeners.getPictureMacSVC(data); + expect(callbackStub.calledWith(null, sinon.match.object)).to.be.true; + }); + + it('debería llamar al callback con la información de la captura de pantalla', () => { + const data = [ + 'get_screenshot_mac_svc', + { screenshot: 'mi captura de pantalla' }, + sinon.stub(), + ]; + + const callbackStub = data[2]; + listeners.getScreenshotMacSVC(data); + expect(callbackStub.calledWith(null, sinon.match.object)).to.be.true; + }); + + it('debería llamar al callback con la información de la captura de pantalla del agente', () => { + const data = [ + 'get_screenshot_agent_mac_svc', + { screenshot: 'mi captura de pantalla del agente' }, + sinon.stub(), + ]; + + const callbackStub = data[2]; + listeners.getScreenshotAgentMacSVC(data); + expect(callbackStub.calledWith(null, sinon.match.object)).to.be.true; + }); + + it('debería llamar al callback con la información de la captura de pantalla del agente', () => { + const data = [ + 'get_screenshot_agent_mac_svc', + { screenshot: 'mi captura de pantalla del agente' }, + sinon.stub(), + ]; + + const callbackStub = data[2]; + listeners.getScreenshotAgentMacSVC(data); + expect(callbackStub.calledWith(null, sinon.match.object)).to.be.true; + }); + + it('debería llamar al callback', () => { + const data = [ + 'reac_to_watcher', + {}, + sinon.stub(), + ]; + + const callbackStub = data[2]; + listeners.reacToWatcher(data); + expect(callbackStub.called).to.be.true; + }); +}); diff --git a/test/lib/agent/utils/storage/restore.test.js b/test/lib/agent/utils/storage/restore.test.js new file mode 100644 index 000000000..3ba71a163 --- /dev/null +++ b/test/lib/agent/utils/storage/restore.test.js @@ -0,0 +1,120 @@ +const rewire = require('rewire'); +const sinon = require('sinon'); +const { expect } = require('chai'); +const { storageConst, osConst } = require('../../../../../lib/constants'); + +// eslint-disable-next-line no-undef +describe('restore function', () => { + let restoreRewired; + // eslint-disable-next-line no-undef + beforeEach(() => { + restoreRewired = rewire('../../../../../lib/agent/utils/storage/restore'); + restoreRewired.verifyTempDatabase = sinon.stub().callsFake(() => false); + restoreRewired.database = { + createDatabase: sinon.stub().callsFake((backupDBPath, cb) => { + cb(null, {}); + }), + backupDatabase: sinon.stub().callsFake((db, cb) => cb(null)), + closeDatabase: sinon.stub().callsFake((db, cb) => cb(null)), + }; + restoreRewired.execCmd = sinon.stub().callsFake((cmd, cb) => cb(null, null, null)); + }); + // eslint-disable-next-line no-undef + afterEach(() => { + }); + // eslint-disable-next-line no-undef + it('should return error on non-Windows platform', () => { + restoreRewired.osName = 'mac'; + const cb = sinon.stub(); + restoreRewired.restore(cb); + // eslint-disable-next-line no-unused-expressions + expect(cb.calledWith(`${storageConst.TITLE}: ${osConst.RESTRICTION.ONLY_WINDOWS}`)).to.be.true; + }); + // eslint-disable-next-line no-undef + it('should return success when temporary database does not exist', () => { + restoreRewired.osName = 'windows'; + const cb = sinon.stub(); + restoreRewired.restore(cb); + // eslint-disable-next-line no-unused-expressions + expect(cb.calledWith()).to.be.true; + }); + // eslint-disable-next-line no-undef + it('should return error when createDatabase fails', () => { + restoreRewired.osName = 'windows'; + restoreRewired.verifyTempDatabase = sinon.stub().callsFake(() => true); + restoreRewired.database = { + createDatabase: sinon.stub().callsFake((backupDBPath, cb) => { + cb(new Error('createDatabase error')); + }), + backupDatabase: sinon.stub().callsFake((db, cb) => cb(null)), + closeDatabase: sinon.stub().callsFake((db, cb) => cb(null)), + }; + + restoreRewired.restore((err) => { + expect(err.message).to.be.equal(storageConst.SQLITE_ACCESS_ERR); + }); + }); + // eslint-disable-next-line no-undef + it('should return error when backupDatabase fails', () => { + restoreRewired.osName = 'windows'; + restoreRewired.verifyTempDatabase = sinon.stub().callsFake(() => true); + restoreRewired.database = { + createDatabase: sinon.stub().callsFake((backupDBPath, cb) => { + cb(null, {}); + }), + backupDatabase: sinon.stub().callsFake((db, cb) => cb(new Error('backupDatabase error'))), + closeDatabase: sinon.stub().callsFake((db, cb) => cb(null)), + }; + restoreRewired.restore((err) => { + expect(err.message).to.be.equal('backupDatabase error'); + }); + }); + // eslint-disable-next-line no-undef + it('should return error when closeDatabase fails', () => { + restoreRewired.osName = 'windows'; + restoreRewired.verifyTempDatabase = sinon.stub().callsFake(() => true); + restoreRewired.database = { + createDatabase: sinon.stub().callsFake((backupDBPath, cb) => { + cb(null, {}); + }), + backupDatabase: sinon.stub().callsFake((db, cb) => cb()), + closeDatabase: sinon.stub().callsFake((db, cb) => cb(new Error('closeDatabase error'))), + }; + restoreRewired.restore((err) => { + expect(err.message).to.be.equal(`${storageConst.BACKUP.CLOSING_ERROR}: closeDatabase error`); + }); + }); + // eslint-disable-next-line no-undef + it('should return error when deleting backup file fails', () => { + restoreRewired.osName = 'windows'; + + restoreRewired.verifyTempDatabase = sinon.stub().callsFake(() => true); + restoreRewired.database = { + createDatabase: sinon.stub().callsFake((backupDBPath, cb) => { + cb(null, {}); + }), + backupDatabase: sinon.stub().callsFake((db, cb) => cb()), + closeDatabase: sinon.stub().callsFake((db, cb) => cb()), + }; + restoreRewired.execCmd = sinon.stub().callsFake((cmd, cb) => cb(new Error('delete error'), null, null)); + restoreRewired.restore((err) => { + expect(err.message).to.be.equal(`${storageConst.BACKUP.DELETING_ERROR}: delete error`); + }); + }); + // eslint-disable-next-line no-undef + it('should return success when restore is successful', () => { + restoreRewired.osName = 'windows'; + restoreRewired.verifyTempDatabase = sinon.stub().callsFake(() => true); + restoreRewired.database = { + createDatabase: sinon.stub().callsFake((backupDBPath, cb) => { + cb(null, {}); + }), + backupDatabase: sinon.stub().callsFake((db, cb) => cb()), + closeDatabase: sinon.stub().callsFake((db, cb) => cb()), + }; + const cb = sinon.stub(); + restoreRewired.restore(cb); + // eslint-disable-next-line no-unused-expressions + expect(cb.calledWith(`${storageConst.BACKUP.RESTORE_SUCCESS}`)).to.be.true; + }); +});