UNPKG

@appsensorlike/appsensorlike

Version:

A port of OWASP AppSensor reference implementation

367 lines (366 loc) 15.2 kB
import fs from 'fs'; import EventEmitter from "events"; import assert from 'assert'; import { fileURLToPath } from 'url'; import { dirname, resolve } from 'path'; import Ajv from "ajv"; import formatsPlugin from "ajv-formats"; import { AppSensorEvent, Attack, DetectionPoint, DetectionSystem, Interval, IPAddress, KeyValuePair, Resource, Response, Threshold, User } from '../core/core.js'; import { GeoLocation } from '../core/geolocation/geolocation.js'; import { Clause, Expression, Rule } from '../core/rule/rule.js'; import { ServerConfiguration } from '../core/configuration/server/server_configuration.js'; class ValidationError extends Error { constructor(errObj, message) { super(message); this.name = this.constructor.name; this.keyword = errObj.keyword; this.instancePath = errObj.instancePath; this.schemaPath = errObj.schemaPath; this.params = errObj.params; this.propertyName = errObj.propertyName; this.schema = errObj.schema; this.parentSchema = errObj.parentSchema; this.data = errObj.data; } } class JSONConfigManager { constructor(configReader, configFile = null, configSchemaFile = null, watchConfigFile = false) { this.eventEmitter = new EventEmitter(); this.configFile = null; this.configSchemaFile = null; this.watchConfigFile = false; this.currentConfig = null; this.configReader = configReader; this.configFile = configFile; this.configSchemaFile = configSchemaFile; this.watchConfigFile = watchConfigFile; if (watchConfigFile) { let fileToWatch = this.configReader.getDefaultConfigFile(); if (this.configFile) { fileToWatch = this.configFile; } this.watch(fileToWatch); } } getConfiguration() { if (!this.currentConfig) { this.currentConfig = this.configReader.read(this.configFile, this.configSchemaFile); } return this.currentConfig; } reloadConfiguration() { const config = this.configReader.read(this.configFile, this.configSchemaFile, true); if (config) { try { assert.deepStrictEqual(config, this.currentConfig); } catch (error) { if (error instanceof assert.AssertionError) { ServerConfiguration.clientApplicationCache.clear(); this.currentConfig = config; this.eventEmitter.emit(JSONConfigManager.CONFIGURATION_CHANGED_EVENT, this.currentConfig); } } } } listenForConfigurationChange(cb) { this.eventEmitter.addListener(JSONConfigManager.CONFIGURATION_CHANGED_EVENT, cb); } watch(file) { if (this.watchConfigFile) { const me = this; fs.watchFile(file, { persistent: false }, function (curr, prev) { // console.log(prev); // console.log(curr); me.reloadConfiguration(); }); } } unwatch(file) { if (this.watchConfigFile) { fs.unwatchFile(file); } } } JSONConfigManager.CONFIGURATION_CHANGED_EVENT = 'CONFIGURATION_CHANGED'; class JSONConfigReadValidate { constructor(defaultRelativeTo, defaultConfigFile, defaultConfigSchemaFile = null, prototypeOfConfigObj) { this.defaultConfigFile = Utils.resolvePath(defaultRelativeTo, defaultConfigFile); this.defaultConfigSchemaFile = defaultConfigSchemaFile ? Utils.resolvePath(defaultRelativeTo, defaultConfigSchemaFile) : null; this.prototypeOfConfigObj = prototypeOfConfigObj; } getDefaultConfigFile() { return this.defaultConfigFile; } getDefaultConfigSchemaFile() { return this.defaultConfigSchemaFile; } getConfigLocation(configLocation = null) { if (!configLocation) { configLocation = this.defaultConfigFile; } else { if (!fs.existsSync(configLocation)) { configLocation = this.defaultConfigFile; } } return configLocation; } getValidatorLocation(validatorLocation = null) { if (!validatorLocation) { validatorLocation = this.defaultConfigSchemaFile; } else { if (!fs.existsSync(validatorLocation)) { validatorLocation = this.defaultConfigSchemaFile; } } return validatorLocation; } /** * Read configuration file and validate. * If a configuration file is not provided, default is taken * If a validator(schema) is not provided, default is taken. * If default schema is not set, loaded configuration is not validated * @param configLocation a configuration file path relative to the working directory or an absolute path * @param validatorLocation a validator(schema) * @param reload true when configuration file has been changed since loaded in memory * @returns a configuration object */ read(configLocation = null, validatorLocation = null, reload = false) { let config = null; configLocation = this.getConfigLocation(configLocation); validatorLocation = this.getValidatorLocation(validatorLocation); try { config = JSON.parse(fs.readFileSync(configLocation, 'utf8')); } catch (error) { if (!reload) { throw error; } else { //don't log here because of circular dependencies console.error(error); } } if (config && validatorLocation !== null) { // console.log('Validating config...'); const valid = this.validateConfig(config, validatorLocation, reload); if (!valid) { //There is(are) validation error(s) reported by validateConfig config = null; } } if (config && this.prototypeOfConfigObj !== undefined) { Object.setPrototypeOf(config, this.prototypeOfConfigObj); } if (Utils.isIValidateInitialize(config)) { config.checkValidInitialize(); } return config; } /** * Read configuration from a string and validate. * @param configAsString a string containing the configuration in JSON format * @param validatorLocation a validator(schema); if it is null, doesn't validate * @returns a configuration object */ readFromString(configAsString, validatorLocation = null) { let config = JSON.parse(configAsString); if (config && validatorLocation !== null) { // console.log('Validating config...'); const valid = this.validateConfig(config, validatorLocation, false); if (!valid) { //There is(are) validation error(s) reported by validateConfig config = null; } } if (config && this.prototypeOfConfigObj !== undefined) { Object.setPrototypeOf(config, this.prototypeOfConfigObj); } if (Utils.isIValidateInitialize(config)) { config.checkValidInitialize(); } return config; } validateConfig(config, validatorLocation, reload) { const schema = JSON.parse(fs.readFileSync(validatorLocation, 'utf8')); const ajv = new Ajv.default({ strict: true, allowUnionTypes: true, allErrors: true }); formatsPlugin.default(ajv); const validate = ajv.compile(schema); validate(config); let valid = true; const validationResult = validate.errors; if (validationResult) { //There is(are) validation error(s) valid = false; //don't log here because of circular dependencies if (!reload) { //since this is the initial start of the applicat just throw if (validationResult.length > 0) { console.log(validationResult); throw new ValidationError(validationResult[0], `Configuration validation failed!`); } else { throw new Error('Configuration validation failed!'); } } else { //do not disturb running application with errors //just log to the console //who change the configuration should be aware console.error('Configuration validation failed!'); for (let e = 0; e < validationResult.length; e++) { console.error(validationResult[e]); } } } return valid; } } class Utils { static resolvePath(relativeToFileURL, fileLocation) { let __dirname = process.cwd(); if (relativeToFileURL.length > 0) { const __filename = fileURLToPath(relativeToFileURL); __dirname = dirname(__filename); } const resolvedPath = resolve(__dirname, fileLocation); // console.log(resolvedPath); return resolvedPath; } static sleep(timeOutInMilis) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(null); }, timeOutInMilis); }); } static setPrototypeInDepth(target, source) { const sourceProto = Object.getPrototypeOf(source); Object.setPrototypeOf(target, sourceProto); const entries = Object.entries(target); for (let i = 0; i < entries.length; i++) { const element = entries[i]; const sourcePropDescr = Object.getOwnPropertyDescriptor(source, element[0]); if (sourcePropDescr) { if (element[1] instanceof Array) { if (element[1].length > 0 && element[1][0] instanceof Object) { if (sourcePropDescr.value instanceof Array) { for (let a = 0; a < element[1].length; a++) { const arEl = element[1][a]; this.setPrototypeInDepth(arEl, sourcePropDescr.value[0]); if (Utils.isIValidateInitialize(arEl)) { arEl.checkValidInitialize(); } } } } } else if (element[1] instanceof Object) { this.setPrototypeInDepth(element[1], sourcePropDescr.value); if (Utils.isIValidateInitialize(element[1])) { element[1].checkValidInitialize(); } } } } if (Utils.isIValidateInitialize(target)) { target.checkValidInitialize(); } } static isIValidateInitialize(obj) { return obj.checkValidInitialize !== undefined; } static setTimestampFromJSONParsedObject(target, obj) { const propDescr = Object.getOwnPropertyDescriptor(obj, "timestamp"); if (propDescr) { target.setTimestamp(new Date(propDescr.value)); } } static setPrototypeInDepthByClassName(target, className) { switch (className) { case 'AppSensorEvent': { Utils.setPrototypeInDepth(target, Utils.appSensorEventPrototypeSample); Utils.setTimestampFromJSONParsedObject(target, target); break; } case 'Attack': { Utils.setPrototypeInDepth(target, Utils.attackPrototypeSample); Utils.setTimestampFromJSONParsedObject(target, target); break; } case 'Response': { Utils.setPrototypeInDepth(target, Utils.responsePrototypeSample); Utils.setTimestampFromJSONParsedObject(target, target); break; } } } static copyPropertyValues(srcObj, trgObj) { const trgPropNames = Object.getOwnPropertyNames(trgObj); for (let index = 0; index < trgPropNames.length; index++) { const trgPropName = trgPropNames[index]; const trgPropDecr = Object.getOwnPropertyDescriptor(trgObj, trgPropName); const srcPropDecr = Object.getOwnPropertyDescriptor(srcObj, trgPropName); if (!srcPropDecr) { throw new Error(`Cannot copy the value of property ${trgPropName} because doesn't exist in source object!`); } trgObj[trgPropName] = srcObj[trgPropName]; } } } (() => { const interval = new Interval(); const ipAddress = new IPAddress(); ipAddress.setGeoLocation(new GeoLocation(0, 0)); const user = new User(); user.setIPAddress(ipAddress); const detectionSystem = new DetectionSystem('', ipAddress); const resource = new Resource(); const metadata = [new KeyValuePair()]; const response = new Response(); response.setUser(user); response.setInterval(interval); response.setDetectionSystem(detectionSystem); response.setMetadata(metadata); const threshold = new Threshold(); threshold.setInterval(interval); const detPoint = new DetectionPoint("something", "something"); detPoint.setThreshold(threshold); detPoint.setResponses([response]); const clause = new Clause(); clause.setMonitorPoints([detPoint]); const expre = new Expression(); expre.setClauses([clause]); expre.setWindow(interval); const rule = new Rule(); rule.setWindow(interval); rule.setExpressions([expre]); rule.setResponses([response]); Utils.ipAddressSample = ipAddress; Utils.detectionPointSample = detPoint; Utils.ruleSample = rule; Utils.appSensorEventPrototypeSample = new AppSensorEvent(); Utils.appSensorEventPrototypeSample.setUser(user); Utils.appSensorEventPrototypeSample.setDetectionPoint(detPoint); Utils.appSensorEventPrototypeSample.setDetectionSystem(detectionSystem); Utils.appSensorEventPrototypeSample.setResource(resource); Utils.appSensorEventPrototypeSample.setMetadata(metadata); Utils.attackPrototypeSample = new Attack(); Utils.attackPrototypeSample.setUser(user); Utils.attackPrototypeSample.setDetectionPoint(detPoint); Utils.attackPrototypeSample.setDetectionSystem(detectionSystem); Utils.attackPrototypeSample.setResource(resource); Utils.attackPrototypeSample.setMetadata(metadata); Utils.attackPrototypeSample.setRule(rule); Utils.responsePrototypeSample = new Response(); Utils.responsePrototypeSample.setUser(user); Utils.responsePrototypeSample.setInterval(interval); Utils.responsePrototypeSample.setDetectionSystem(detectionSystem); Utils.responsePrototypeSample.setMetadata(metadata); Utils.responsePrototypeSample.setDetectionPoint(detPoint); Utils.responsePrototypeSample.setRule(rule); })(); export { JSONConfigManager, JSONConfigReadValidate, Utils };