@appsensorlike/appsensorlike
Version:
A port of OWASP AppSensor reference implementation
367 lines (366 loc) • 15.2 kB
JavaScript
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 };