UNPKG

@appsensorlike/appsensorlike

Version:

A port of OWASP AppSensor reference implementation

1,496 lines 53.9 kB
import ipaddrlib from 'ipaddr.js'; import { Action } from './accesscontrol/accesscontrol.js'; /** * ObjectValidationError is thrown when validation of an object fails. */ class ObjectValidationError extends Error { /** * @param message the error message * @param invalidObj reference to the checked object */ constructor(message, invalidObj) { super(message); this.invalidObj = invalidObj; } } // /** // * // */ // interface IAppsensorEntity extends IEquals, IValidateInitialize { // getId(): string | undefined; // setId(id: string): void; // } /** * Base class for the most of the following classes */ class AppsensorEntity { getId() { return this.id; } setId(id) { this.id = id; } equals(obj) { if (obj === null || obj === undefined) return false; if (this.constructor.name !== obj.constructor.name) return false; return true; } checkValidInitialize() { } } /** * Represent a (key, value) pair. * It could store application specific information. */ class KeyValuePair extends AppsensorEntity { constructor(key = '', value = '') { super(); this.key = key; this.value = value; } getKey() { return this.key; } setKey(key) { this.key = key; } getValue() { return this.value; } setValue(value) { this.value = value; } equals(other) { if (!super.equals(other)) return false; if (this === other) return true; let otherPair = other; return this.key === otherPair.key && this.value === otherPair.value; } checkValidInitialize() { if (this.key.trim().length === 0) { throw new ObjectValidationError("key cannot be empty string", this); } if (this.value.trim().length === 0) { throw new ObjectValidationError("value cannot be empty string", this); } } } /** * Interval units */ var INTERVAL_UNITS; (function (INTERVAL_UNITS) { INTERVAL_UNITS["MILLISECONDS"] = "milliseconds"; INTERVAL_UNITS["SECONDS"] = "seconds"; INTERVAL_UNITS["MINUTES"] = "minutes"; INTERVAL_UNITS["HOURS"] = "hours"; INTERVAL_UNITS["DAYS"] = "days"; })(INTERVAL_UNITS || (INTERVAL_UNITS = {})); /** * The Interval represents a span of time. * * <ul> * <li>duration (example: 15)</li> * <li>unit: (example: minutes)</li> * </ul> * */ class Interval extends AppsensorEntity { constructor(duration = 0, unit = INTERVAL_UNITS.MINUTES) { super(); /** * Duration portion of interval, ie. '3' if you wanted * to represent an interval of '3 minutes' */ this.duration = 0; /** * Unit portion of interval, ie. 'minutes' if you wanted * to represent an interval of '3 minutes'. * Constants are provided in the Interval class for the * units supported by the reference implementation, ie. * SECONDS, MINUTES, HOURS, DAYS. */ this.unit = INTERVAL_UNITS.MINUTES; if (duration === 0) { // console.warn("Interval's duration is 0"); } this.setDuration(duration); this.setUnit(unit); } getDuration() { return this.duration; } setDuration(duration) { this.duration = duration; return this; } getUnit() { return this.unit; } setUnit(unit) { this.unit = unit; return this; } toMillis() { let millis = 0; if (INTERVAL_UNITS.MILLISECONDS === this.getUnit()) { millis = this.getDuration(); } else if (INTERVAL_UNITS.SECONDS === this.getUnit()) { millis = 1000 * this.getDuration(); } else if (INTERVAL_UNITS.MINUTES === this.getUnit()) { millis = 1000 * 60 * this.getDuration(); } else if (INTERVAL_UNITS.HOURS === this.getUnit()) { millis = 1000 * 60 * 60 * this.getDuration(); } else if (INTERVAL_UNITS.DAYS === this.getUnit()) { millis = 1000 * 60 * 60 * 24 * this.getDuration(); } return millis; } equals(obj) { if (!super.equals(obj)) return false; if (this === obj) return true; const other = obj; return this.duration === other.getDuration() && this.unit === other.getUnit(); } checkValidInitialize() { if (this.duration < 0) { throw new ObjectValidationError("duration cannot be negative", this); } const units = Object.values(INTERVAL_UNITS); if (units.indexOf(this.unit) === -1) { throw new ObjectValidationError("unit is not of INTERVAL_UNITS", this); } } } /** * The Threshold represents a number of occurrences over a span of time. * * <ul> * <li>count: (example: 12)</li> * <li>interval: (example: 15 minutes)</li> * </ul> * */ class Threshold extends AppsensorEntity { constructor(count = 0, interval = null) { super(); /** The count at which this threshold is triggered. */ this.count = 0; /** * The time frame within which 'count' number of actions has to be detected in order to * trigger this threshold. */ this.interval = null; this.setCount(count); this.setInterval(interval); } getCount() { return this.count; } setCount(count) { this.count = count; return this; } getInterval() { return this.interval; } setInterval(interval) { this.interval = interval; return this; } equals(obj) { if (!super.equals(obj)) return false; if (this === obj) return true; const other = obj; return this.count === other.getCount() && Utils.equalsEntitys(this.interval, other.getInterval()); } checkValidInitialize() { if (this.count < 0) { throw new ObjectValidationError("count cannot be negative", this); } if (this.interval) { this.interval.checkValidInitialize(); } } } /** * After an {@link Attack} has been determined to have occurred, a Response * is executed. The Response configuration is done on the server-side, not * the client application. */ class Response extends AppsensorEntity { constructor(user = undefined, action = '', timestamp = undefined, detectionSystem = undefined, interval = undefined) { super(); /** String representing response action name */ this.action = ''; /** ADDITION TO THE ORIGINAL CODE TO TRACE WHAT CAUSED THIS RESPONSE * ESSENTIAL FOR REPORTING * {@link DetectionPoint} that was triggered */ this.detectionPoint = null; /** ADDITION TO THE ORIGINAL CODE TO TRACE WHAT CAUSED THIS RESPONSE * ESSENTIAL FOR REPORTING * {@link Rule} that was triggered */ this.rule = null; if (user !== undefined) { this.user = user; } this.action = action; if (timestamp !== undefined) { this.timestamp = timestamp; } if (detectionSystem !== undefined) { this.detectionSystem = detectionSystem; } if (interval !== undefined) { this.interval = interval; } } getUser() { return this.user; } setUser(user) { this.user = user; return this; } getTimestamp() { return this.timestamp; } setTimestamp(timestamp) { if (timestamp) { this.timestamp = new Date(timestamp); } else { this.timestamp = new Date(); } return this; } getAction() { return this.action; } setAction(action) { this.action = action; return this; } getInterval() { return this.interval; } setInterval(interval) { this.interval = interval; return this; } getDetectionSystem() { return this.detectionSystem; } setDetectionSystem(detectionSystem) { this.detectionSystem = detectionSystem; return this; } getMetadata() { return this.metadata; } setMetadata(metadata) { this.metadata = metadata; } isActive() { // if there is no interval, the response is executed immediately and hence does not have active/inactive state if (this.interval === null || this.interval === undefined || this.timestamp === undefined) { return false; } let localActive = false; const responseStartTime = this.timestamp; const responseEndTime = new Date(responseStartTime.getTime() + this.interval.toMillis()); const now = Date.now(); // only active if current time between response start and end time if (responseStartTime.getTime() < now && responseEndTime.getTime() > now) { localActive = true; } this.active = localActive; return this.active; } getDetectionPoint() { return this.detectionPoint; } setDetectionPoint(detectionPoint) { this.detectionPoint = detectionPoint; return this; } getRule() { return this.rule; } setRule(rule) { this.rule = rule; return this; } equals(obj) { if (!super.equals(obj)) { return false; } const other = obj; const otherTimestamp = other.getTimestamp(); return Utils.equalsEntitys(this.user, other.getUser()) && ((this.timestamp === null && otherTimestamp === null) || (this.timestamp === undefined && otherTimestamp === undefined) || (this.timestamp instanceof Date && otherTimestamp instanceof Date && this.timestamp.getTime() === otherTimestamp.getTime())) && this.action === other.getAction() && Utils.equalsEntitys(this.interval, other.getInterval()) && Utils.equalsEntitys(this.detectionSystem, other.getDetectionSystem()) && Utils.equalsArrayEntitys(this.metadata, other.getMetadata()) && Utils.equalsEntitys(this.detectionPoint, other.getDetectionPoint()) && Utils.equalsEntitys(this.rule, other.getRule()); } checkValidInitialize() { if (this.action.trim().length === 0) { throw new ObjectValidationError("action cannot be empty string", this); } if (this.user) { this.user.checkValidInitialize(); } if (this.detectionSystem) { this.detectionSystem.checkValidInitialize(); } if (this.interval) { this.interval.checkValidInitialize(); } if (this.metadata) { this.metadata.forEach(element => { element.checkValidInitialize(); }); } if (this.detectionPoint === undefined) { this.detectionPoint = null; } if (this.rule === undefined) { this.rule = null; } } } /** * Detection point category */ class Category { } Category.REQUEST = "Request"; Category.AUTHENTICATION = "Authentication"; Category.SESSION_MANAGEMENT = "Session Management"; Category.ACCESS_CONTROL = "Access Control"; Category.INPUT_VALIDATION = "Input Validation"; Category.OUTPUT_ENCODING = "Output Encoding"; Category.COMMAND_INJECTION = "Command Injection"; Category.FILE_IO = "File IO"; Category.HONEY_TRAP = "Honey Trap"; Category.USER_TREND = "User Trend"; Category.SYSTEM_TREND = "System Trend"; Category.REPUTATION = "Reputation"; /** * The detection point represents the unique sensor concept in the code. * * A list of project detection points are maintained at https://www.owasp.org/index.php/AppSensor_DetectionPoints * * @see <a href="https://www.owasp.org/index.php/AppSensor_DetectionPoints">https://www.owasp.org/index.php/AppSensor_DetectionPoints</a> */ class DetectionPoint extends AppsensorEntity { constructor(category = '', label = undefined, threshold = null, responses = [], guid = undefined) { super(); /** * Category identifier for the detection point. (ex. "Request", "AccessControl", "SessionManagement") */ this.category = ''; /** * {@link Threshold} for determining whether given detection point (associated {@link AppSensorEvent}) * should be considered an {@link Attack}. */ this.threshold = null; /** * Set of {@link Response}s associated with given detection point. */ this.responses = []; this.category = category; this.label = label; //here we set id as well since it hasn't been set so far this.id = this.label; this.threshold = threshold; this.responses = responses; if (guid !== undefined) { this.guid = guid; } } getCategory() { return this.category; } getLabel() { return this.label; } setLabel(label) { this.label = label; return this; } getGuid() { return this.guid; } setGuid(guid) { this.guid = guid; } getThreshold() { return this.threshold; } setThreshold(threshold) { this.threshold = threshold; return this; } getResponses() { return this.responses; } setResponses(responses) { this.responses = responses; return this; } typeMatches(other) { if (other == null) { throw new Error("other must be non-null"); } let matches = true; matches && (matches = (this.category !== null) ? this.category === other.getCategory() : true); matches && (matches = (this.label) ? this.label === other.getLabel() : true); return matches; } typeAndThresholdMatches(other) { if (other == null) { throw new Error("other must be non-null"); } let matches = true; matches && (matches = (this.category !== null) ? this.category === other.getCategory() : true); matches && (matches = (this.label) ? this.label === other.getLabel() : true); matches && (matches = (this.threshold !== null) ? Utils.equalsEntitys(this.threshold, other.getThreshold()) : true); return matches; } equals(obj) { if (!super.equals(obj)) return false; if (this === obj) return true; const other = obj; return this.category === other.getCategory() && this.label === other.getLabel() && Utils.equalsEntitys(this.threshold, other.getThreshold()) && Utils.equalsArrayEntitys(this.responses, other.getResponses()) && this.guid === other.getGuid(); } checkValidInitialize() { if (this.category.trim().length === 0) { throw new ObjectValidationError('category cannot be empty string!', this); } //label can be empty when it is read from a configuration //but in this case it is set to be equal to id //for more details about that change in the original java code //see https://github.com/jtmelton/appsensor/issues/18 if (this.label && this.label.trim().length === 0) { throw new ObjectValidationError('label cannot be empty string!', this); } if (this.guid && this.guid.trim().length === 0) { throw new ObjectValidationError('guid, when defined, cannot be empty string', this); } if (this.threshold) { this.threshold.checkValidInitialize(); } } } /** * The IP Address for the user, optionally provided by the client application. */ class IPAddress extends AppsensorEntity { constructor(address = '', geoLocation = null) { super(); this.address = ''; this.geoLocation = null; this.address = address.trim(); this.geoLocation = geoLocation; this.address = IPAddress.localhostToAddress(this.address); } static localhostToAddress(address) { if (address.trim().toLowerCase() === "localhost") { return "127.0.0.1"; } return address; } static async fromString(ipString, geoLocator = null) { ipString = IPAddress.localhostToAddress(ipString); if (!ipaddrlib.isValid(ipString)) { throw new Error("IP Address string is invalid: " + ipString); } let geoLocation = null; if (geoLocator) { geoLocation = await geoLocator.readLocation(ipString); } return new IPAddress(ipString, geoLocation); } setAddress(address) { this.address = address; return this; } getAddress() { return this.address; } getGeoLocation() { return this.geoLocation; } setGeoLocation(geoLocation) { this.geoLocation = geoLocation; return this; } equalAddress(otherAddress) { let equal = false; if (this.address.trim().length === 0 && otherAddress.trim().length === 0) { equal = true; } else { otherAddress = otherAddress.trim(); otherAddress = IPAddress.localhostToAddress(otherAddress); try { const _address = ipaddrlib.process(this.address); const _otherAddress = ipaddrlib.process(otherAddress); if (_address instanceof ipaddrlib.IPv4 && _otherAddress instanceof ipaddrlib.IPv4) { equal = _address.toString() === _otherAddress.toString(); } else if (_address instanceof ipaddrlib.IPv6 && _otherAddress instanceof ipaddrlib.IPv6) { equal = _address.toNormalizedString() === _otherAddress.toNormalizedString(); } } catch (error) { console.error(`Cannot compare for equality addresses: '${this.address}' and '${otherAddress}'`); console.error(`${error.message}`); } } return equal; } equals(obj) { if (!super.equals(obj)) return false; if (this === obj) return true; const other = obj; return this.equalAddress(other.getAddress()) && Utils.equalsEntitys(this.geoLocation, other.getGeoLocation()); } checkValidInitialize() { this.address = IPAddress.localhostToAddress(this.address); if (!ipaddrlib.isValid(this.address)) { throw new ObjectValidationError("IP Address string is invalid: " + this.address, this); } if (this.geoLocation) { this.geoLocation.checkValidInitialize(); } } } /** * Identifier label for the system that detected the event. * This will be either the client application, or possibly an external * detection system, such as syslog, a WAF, network IDS, etc. */ class DetectionSystem extends AppsensorEntity { constructor(detectionSystemId = '', ipAddress = null) { super(); this.detectionSystemId = ''; this.ipAddress = null; this.setIPAddress(ipAddress); this.setDetectionSystemId(detectionSystemId); } getDetectionSystemId() { return this.detectionSystemId; } setDetectionSystemId(detectionSystemId) { this.detectionSystemId = detectionSystemId; // This logic is outside now, allowing asynchronous resolution of location // // // if IP is used as system id, setup IP address w/ geolocation // if (this.ipAddress === null && this.locator != null && ipaddrlib.isValid(detectionSystemId)) { //InetAddresses.isInetAddress(detectionSystemId)) { // this.ipAddress = this.locator.fromString(detectionSystemId); // } return this; } getIPAddress() { return this.ipAddress; } setIPAddress(ipAddress) { this.ipAddress = ipAddress; return this; } equals(obj) { if (!super.equals(obj)) return false; if (this === obj) return true; const other = obj; return this.detectionSystemId === other.getDetectionSystemId(); } checkValidInitialize() { if (this.ipAddress) { this.ipAddress.checkValidInitialize(); } } } /** * The standard User object. This represents the end user in the system, * NOT the client application. * * The base implementation assumes the username is provided by the client application. * * It is up to the client application to manage the username. * The username could be anything, an actual username, an IP address, * or any other identifier desired. The core notion is that any desired * correlation on the user is done by comparing the username. */ class User extends AppsensorEntity { constructor(username = User.ANONYMOUS_USER, ipAddress = null) { super(); this.username = User.ANONYMOUS_USER; this.ipAddress = null; //set ip first so the setUsername call to geolocate won't run if it's already explicitly set this.setIPAddress(ipAddress); this.setUsername(username); } getUsername() { return this.username; } setUsername(username) { this.username = username; // This logic is outside now, allowing asynchronous resolution of location // // // if IP is used as username, setup IP address w/ geolocation // if (this.locator != null && this.ipAddress == null && ipaddrlib.isValid(username)) { // this.ipAddress = this.locator.fromString(username); // } return this; } getIPAddress() { return this.ipAddress; } setIPAddress(ipAddress) { this.ipAddress = ipAddress; return this; } equals(obj) { if (!super.equals(obj)) return false; if (this === obj) return true; const other = obj; return this.username === other.getUsername(); } checkValidInitialize() { if (this.ipAddress) { this.ipAddress.checkValidInitialize(); } } } //not part of the original code User.ANONYMOUS_USER = 'ANONYMOUS'; //not part of the original code User.UNDEFINED_USER = '<<UNDEFINED>>'; /** * Resource represents a generic component of an application. In many cases, * it would represent a URL, but it could also presumably be used for something * else, such as a specific object, function, or even a subsection of an application, etc. */ class Resource extends AppsensorEntity { constructor(location = '', method = '') { super(); /** * The resource being requested when a given event/attack was triggered, which can be used * later to block requests to a given function. In this implementation, * the current request URI is used. */ this.location = ''; /** * The method used to request the resource. In terms of HTTP this would be GET/POST/PUT/etc. * In the case, in which the resources specifies an object this could be the invoked object method. */ this.method = ''; this.location = location; this.method = method; } getLocation() { return this.location; } setLocation(location) { this.location = location; } getMethod() { return this.method; } setMethod(method) { this.method = method; } equals(obj) { if (!super.equals(obj)) return false; if (this === obj) return true; const other = obj; return this.location === other.getLocation() && this.method === other.getMethod(); } checkValidInitialize() { if (this.location.trim().length === 0) { throw new ObjectValidationError('location cannot be empty string!', this); } if (this.method.trim().length === 0) { throw new ObjectValidationError('method cannot be empty string!', this); } } } /** * Event is a specific instance that a sensor has detected that * represents a suspicious activity. * * The key difference between an AppSensorEvent and an Attack is that an AppSensorEvent * is "suspicous" whereas an Attack has been determined to be "malicious" by some analysis. * * The name of this class in the original Java code is Event. * Since Javascript has own native Event class, this class has been renamed to AppSensorEvent (not to burden with the name all the time). */ class AppSensorEvent extends AppsensorEntity { constructor(user = null, detectionPoint = null, detectionSystem = null, timestamp = null) { super(); /** {@link User} who triggered the event, could be anonymous user */ this.user = null; /** {@link DetectionPoint} that was triggered */ this.detectionPoint = null; /** When the event occurred */ this.timestamp = new Date(); /** * Identifier label for the system that detected the event. * This will be either the client application, or possibly an external * detection system, such as syslog, a WAF, network IDS, etc. */ this.detectionSystem = null; /** * The resource being requested when the event was triggered, which can be used * later to block requests to a given function. */ this.resource = null; /** Represent extra metadata, anything client wants to send */ this.metadata = []; this.setUser(user); this.setDetectionPoint(detectionPoint); this.setTimestamp(timestamp); this.setDetectionSystem(detectionSystem); } getUser() { return this.user; } setUser(user) { this.user = user; return this; } getDetectionPoint() { return this.detectionPoint; } setDetectionPoint(detectionPoint) { this.detectionPoint = detectionPoint; return this; } getTimestamp() { return this.timestamp; } setTimestamp(timestamp) { if (timestamp) { this.timestamp = new Date(timestamp); } else { this.timestamp = new Date(); } return this; } getDetectionSystem() { return this.detectionSystem; } setDetectionSystem(detectionSystem) { this.detectionSystem = detectionSystem; return this; } getResource() { return this.resource; } setResource(resource) { this.resource = resource; return this; } getMetadata() { return this.metadata; } setMetadata(metadata) { this.metadata = metadata; } static getTimeAscendingComparator(e1, e2) { if (e1 === null || e2 === null) { throw new Error('e1 and e2 cannot be null'); } if (e1.getTimestamp().getTime() < e2.getTimestamp().getTime()) { return -1; } else if (e1.getTimestamp().getTime() > e2.getTimestamp().getTime()) { return 1; } else { return 0; } } equals(obj) { if (!super.equals(obj)) return false; if (this === obj) return true; const other = obj; return Utils.equalsEntitys(this.user, other.getUser()) && Utils.equalsEntitys(this.detectionPoint, other.getDetectionPoint()) && this.timestamp.getTime() === other.getTimestamp().getTime() && Utils.equalsEntitys(this.detectionSystem, other.getDetectionSystem()) && Utils.equalsEntitys(this.resource, other.getResource()) && Utils.equalsArrayEntitys(this.metadata, other.getMetadata()); } checkValidInitialize() { if (this.user) { this.user.checkValidInitialize(); } if (this.detectionSystem) { this.detectionSystem.checkValidInitialize(); } if (this.detectionPoint) { this.detectionPoint.checkValidInitialize(); } if (this.resource) { this.resource.checkValidInitialize(); } if (this.metadata) { this.metadata.forEach(element => { element.checkValidInitialize(); }); } else { this.metadata = []; } } } /** * An attack can be added to the system in one of two ways: * <ol> * <li>Analysis is performed by the event analysis engine and determines an attack has occurred</li> * <li>Analysis is performed by an external system (ie. WAF) and added to the system.</li> * </ol> * * The key difference between an AppSensorEvent and an Attack is that an AppSensorEvent * is "suspicous" whereas an Attack has been determined to be "malicious" by some analysis. */ class Attack extends AppsensorEntity { constructor(event = null, user = null, detectionPoint = null, timestamp = null, detectionSystem = null, resource = null) { super(); /** {@link User} who triggered the attack, could be anonymous user */ this.user = null; /** {@link DetectionPoint} that was triggered */ this.detectionPoint = null; /** When the attack occurred */ this.timestamp = new Date(); /** * Identifier label for the system that detected the attack. * This will be either the client application, or possibly an external * detection system, such as syslog, a WAF, network IDS, etc. */ this.detectionSystem = null; /** * The resource being requested when the attack was triggered, which can be used * later to block requests to a given function. */ this.resource = null; /** Rule that was triggered */ this.rule = null; /** Represent extra metadata, anything client wants to send */ this.metadata = []; if (event) { this.setUser(event.getUser()); this.setDetectionPoint(event.getDetectionPoint()); this.setTimestamp(event.getTimestamp()); this.setDetectionSystem(event.getDetectionSystem()); this.setResource(event.getResource()); } else { this.setUser(user); this.setDetectionPoint(detectionPoint); this.setTimestamp(timestamp); this.setDetectionSystem(detectionSystem); this.setResource(resource); } } getUser() { return this.user; } setUser(user) { this.user = user; return this; } getDetectionPoint() { return this.detectionPoint; } setDetectionPoint(detectionPoint) { this.detectionPoint = detectionPoint; return this; } getTimestamp() { return this.timestamp; } setTimestamp(timestamp) { if (timestamp) { this.timestamp = new Date(timestamp); } else { this.timestamp = new Date(); } return this; } getDetectionSystem() { return this.detectionSystem; } setDetectionSystem(detectionSystem) { this.detectionSystem = detectionSystem; return this; } getResource() { return this.resource; } setResource(resource) { this.resource = resource; return this; } getRule() { return this.rule; } setRule(rule) { this.rule = rule; return this; } getMetadata() { return this.metadata; } setMetadata(metadata) { this.metadata = metadata; } getName() { let name = ""; if (this.rule === null) { name = Utils.getDetectionPointLabel(this.detectionPoint); } else if (this.rule) { name = this.rule.getName() == null ? this.rule.getGuid() : this.rule.getName(); } return name; } equals(obj) { if (!super.equals(obj)) return false; if (this === obj) return true; const other = obj; return Utils.equalsEntitys(this.user, other.getUser()) && Utils.equalsEntitys(this.detectionPoint, other.getDetectionPoint()) && this.timestamp.getTime() === other.getTimestamp().getTime() && Utils.equalsEntitys(this.detectionSystem, other.getDetectionSystem()) && Utils.equalsEntitys(this.resource, other.getResource()) && Utils.equalsEntitys(this.rule, other.getRule()) && Utils.equalsArrayEntitys(this.metadata, other.getMetadata()); } checkValidInitialize() { if (this.user) { this.user.checkValidInitialize(); } if (this.detectionSystem) { this.detectionSystem.checkValidInitialize(); } if (this.detectionPoint) { this.detectionPoint.checkValidInitialize(); } if (this.resource) { this.resource.checkValidInitialize(); } if (this.rule) { this.rule.checkValidInitialize(); } if (this.metadata) { this.metadata.forEach(element => { element.checkValidInitialize(); }); } else { this.metadata = []; } } } /** * The ClientApplication object represents a consumer of the AppSensor * services in any of the client-server style setups. */ class ClientApplication { constructor(name = '', roles = []) { /** The name of the client application */ this.name = ''; /** The collection of Roles associated with this client application */ this.roles = []; this.name = name; this.roles = roles; } getName() { return this.name; } setName(name) { this.name = name; return this; } getRoles() { return this.roles; } getIPAddresses() { return this.ipAddresses; } setIPAddresses(ipAddresses) { this.ipAddresses = ipAddresses; return this; } addIPAddress(ipAddress) { if (this.ipAddresses == undefined) { this.ipAddresses = []; } this.ipAddresses.push(ipAddress); return this; } isIPAddressAllowed(ip) { let result = false; if (this.ipAddresses && this.ipAddresses.length > 0) { for (const configClientIP of this.ipAddresses) { if (configClientIP.equalAddress(ip)) { result = true; break; } } } else { //no restrictions when ip addresses are not specified result = true; } return result; } equals(obj) { if (this === obj) return true; if (obj === null) return false; if (this.constructor.name !== obj.constructor.name) return false; const other = obj; let equals = this.name === other.getName() && Utils.equalsArrayEntitys(this.ipAddresses, other.getIPAddresses()); if (equals) { const _roles = this.roles; _roles.sort(); const _otherRoles = other.getRoles(); _otherRoles.sort(); if (_roles.length === _otherRoles.length) { for (let i = 0; i < _roles.length; i++) { if (_roles[i] !== _otherRoles[i]) { return false; } } } return true; } else { return false; } } } /** * AppSensor core class for accessing client-side components. * However, the configuration portions are setup in * the appsensor-client-config.json file. */ class AppSensorClient { constructor() { this.configuration = null; this.eventManager = null; this.responseHandler = null; this.userManager = null; } getConfiguration() { return this.configuration; } setConfiguration(updatedConfiguration) { this.configuration = updatedConfiguration; } getEventManager() { return this.eventManager; } setEventManager(eventManager) { this.eventManager = eventManager; } getResponseHandler() { return this.responseHandler; } setResponseHandler(responseHandler) { this.responseHandler = responseHandler; } getUserManager() { return this.userManager; } setUserManager(userManager) { this.userManager = userManager; } } /** * AppSensor core class for accessing server-side components. * However, the configuration portions are setup in * the appsensor-server-config.json file. */ class AppSensorServer { constructor() { this.configuration = null; this.eventStore = null; this.attackStore = null; this.responseStore = null; this.eventAnalysisEngines = []; this.attackAnalysisEngines = []; this.responseAnalysisEngines = []; this.accessController = null; } getConfiguration() { return this.configuration; } setConfiguration(updatedConfiguration) { this.configuration = updatedConfiguration; } getEventStore() { return this.eventStore; } getAttackStore() { return this.attackStore; } getResponseStore() { return this.responseStore; } getEventAnalysisEngines() { return this.eventAnalysisEngines; } getAttackAnalysisEngines() { return this.attackAnalysisEngines; } getResponseAnalysisEngines() { return this.responseAnalysisEngines; } getAccessController() { return this.accessController; } setEventStore(eventStore) { this.eventStore = eventStore; } setAttackStore(attackStore) { this.attackStore = attackStore; } setResponseStore(responseStore) { this.responseStore = responseStore; } setEventAnalysisEngines(eventAnalysisEngines) { this.eventAnalysisEngines = eventAnalysisEngines; } setAttackAnalysisEngines(attackAnalysisEngines) { this.attackAnalysisEngines = attackAnalysisEngines; } setResponseAnalysisEngines(responseAnalysisEngines) { this.responseAnalysisEngines = responseAnalysisEngines; } setAccessController(accessController) { this.accessController = accessController; } } /** * Utility methods */ class Utils { /** Tests wether two objects are equal */ static equalsEntitys(ent1, ent2) { return (ent1 === ent2) || (ent1 !== null && ent1 !== undefined && ent1.equals(ent2)); } /** Tests wether two arrays of objects are equal */ static equalsArrayEntitys(ent1, ent2) { if (ent1 === ent2) { return true; } else if (ent1 !== null && ent2 !== null && ent1 !== undefined && ent2 !== undefined && ent1.length === ent2.length) { //consider the arrays are sorted for (let i = 0; i < ent1.length; i++) { if (!ent1[i].equals(ent2[i])) { return false; } } return true; } else { return false; } } /** Tests wether two objects are equal according to specified properties returned by a function */ static equalsOnProperties(obj1, obj2, propMap, propertyNamesFunc) { if (obj1 === obj2) { return true; } if (!(obj1 !== null && obj1 !== undefined && obj2 !== null && obj2 !== undefined)) { return false; } if (obj1.constructor.name !== obj2.constructor.name) { return false; } const properties = propertyNamesFunc(obj1, propMap); let equal = true; for (let index = 0; index < properties.length; index++) { const propName = properties[index]; const propDescr1 = Object.getOwnPropertyDescriptor(obj1, propName); const propDescr2 = Object.getOwnPropertyDescriptor(obj2, propName); if (!propDescr1 && !propDescr2) { continue; } if ((propDescr1 && !propDescr2) || (!propDescr1 && propDescr2)) { equal = false; const val1 = propDescr1 ? propDescr1.value : undefined; const val2 = propDescr2 ? propDescr2.value : undefined; // console.debug(`equalsOnProperties: class: ${obj1.constructor.name}, property: ${propName} '${val1}' <> '${val2}'`); break; } if (propDescr1.value instanceof Array) { equal = Utils.equalsArrayEntitysOnProperties(propDescr1.value, propDescr2.value, propMap, propertyNamesFunc); } else if (propDescr1.value instanceof Object) { equal = Utils.equalsOnProperties(propDescr1.value, propDescr2.value, propMap, propertyNamesFunc); } else { equal = propDescr1.value === propDescr2.value; } if (!equal) { // console.debug(`equalsOnProperties: class: ${obj1.constructor.name}, property: ${propName} '${propDescr1!.value}' <> '${propDescr2!.value}'`); break; } } return equal; } /** Tests wether two arrays of objects are equal according to specified properties returned by a function */ static equalsArrayEntitysOnProperties(obj1, obj2, propMap, propertyNamesFunc) { if (obj1 === obj2) { return true; } else if (obj1 !== null && obj2 !== null && obj1 !== undefined && obj2 !== undefined && obj1.length === obj2.length) { //consider the arrays are sorted for (let i = 0; i < obj1.length; i++) { if (!Utils.equalsOnProperties(obj1[i], obj2[i], propMap, propertyNamesFunc)) { return false; } } return true; } else { return false; } } /** * Function returning the object's own properties or specified properties defined in a map. * The map's key is the object's constructor name. */ static allOrOnlySpecifiedProperties(obj, onlyPropertiesToCompareMap) { let propertiesToCompare = []; if (obj) { propertiesToCompare = Object.getOwnPropertyNames(obj); let onlyPropertiesToCompare = onlyPropertiesToCompareMap.get(obj.constructor.name); if (!onlyPropertiesToCompare) { onlyPropertiesToCompare = onlyPropertiesToCompareMap.get('*'); } if (onlyPropertiesToCompare) { propertiesToCompare = onlyPropertiesToCompare; } } return propertiesToCompare; } /** * Function returning the object's own properties or own properties without the properties defined in a map. * The map's key is the object's constructor name. */ static allOrWithoutExcludedProperties(obj, excludedPropertiesFromCompareMap) { let propertiesToCompare = []; if (obj) { propertiesToCompare = Object.getOwnPropertyNames(obj); let propertiesToIgnore = excludedPropertiesFromCompareMap.get(obj.constructor.name); if (!propertiesToIgnore) { propertiesToIgnore = excludedPropertiesFromCompareMap.get('*'); } if (propertiesToIgnore) { propertiesToCompare = propertiesToCompare.filter(el => propertiesToIgnore.indexOf(el) === -1); } } return propertiesToCompare; } static getUserName(user) { let userName = User.UNDEFINED_USER; if (user) { userName = user.getUsername(); } return userName; } static getDetectionPointLabel(detPoint) { let detPointLabel = undefined; if (detPoint) { detPointLabel = detPoint.getLabel(); } return detPointLabel; } /** Access control {@link Action} to {@link RequestHandler} method name mapping*/ static getMethodFromAction(action) { let method = "unknown"; switch (action) { case Action.ADD_ATTACK: { method = "addAttack"; break; } case Action.ADD_EVENT: { method = "addEvent"; break; } case Action.GET_EVENTS: { method = "getEvents"; break; } case Action.GET_ATTACKS: { method = "getAttacks"; break; } case Action.GET_RESPONSES: { method = "getResponses"; break; } } return method; } /** {@link RequestHandler} method name to access control {@link Action} mapping*/ static getActionFromMethod(method) { let action = Action.UNKNOWN; switch (method) { case "addAttack": { action = Action.ADD_ATTACK; break; } case "addEvent": { action = Action.ADD_EVENT; break; } case "getEvents": { action = Action.GET_EVENTS; break; } case "getAttacks": { action = Action.GET_ATTACKS; break; } case "getResponses": { action = Action.GET_RESPONSES; break; } } return action; } /** * Finder for attacks in the AttackStore. * * This method was moved here out of AttackStore in order the same logic to be * utilized in other places as well * * @param criteria the SearchCriteria object to search by * @param attack the Attack object to match on * @return true or false depending on the matching of the search criteria to the attack */ static isMatchingAttack(criteria, attack) { let match = false; const user = criteria.getUser(); const detectionPoint = criteria.getDetectionPoint(); const detectionSystemIds = criteria.getDetectionSystemIds(); const earliest = criteria.getEarliest(); const rule = criteria.getRule(); // check user match if user specified const userMatch = (user != null) ? user.equals(attack.getUser()) : true; //check detection system match if detection systems specified let detectionSystemMatch = true; const attDetSystemId = attack.getDetectionSystem(); if (detectionSystemIds != null && detectionSystemIds.length > 0 && attDetSystemId !== null) { detectionSystemMatch = detectionSystemIds.indexOf(attDetSystemId.getDetectionSystemId()) > -1; } //check detection point match if detection point specified let detectionPointMatch = true; if (detectionPoint !== null) { const attDetoint = attack.getDetectionPoint(); detectionPointMatch = (attDetoint !== null) ? detectionPoint.typeAndThresholdMatches(attDetoint) : false; } //check rule match if rule specified let ruleMatch = true; if (rule !== null) { const attRule = attack.getRule(); ruleMatch = (attRule !== null) ? rule.guidMatches(attRule) : false; } let earliestMatch = true; if (earliest !== null) { const attackTimestampMillis = attack.getTimestamp().getTime(); const earliestMillis = earliest.getTime(); earliestMatch = (earliestMillis < attackTimestampMillis || earliestMillis === attackTimestampMillis); } if (userMatch && detectionSystemMatch && detectionPointMatch && ruleMatch && earliestMatch) { match = true; } return match; } /** * A finder for Event objects in the EventStore * * This method was moved here out of EventStore in order the same logic to be * utilized in other places as well * * @param criteria the SearchCriteria object to search by * @param event the AppSensorEvent object to match on * @return true or false depending on the matching of the search criteria to the event */ static isMatchingEvent(criteria, event) { let match = false; const user = criteria.getUser(); const detectionPoint = criteria.getDetectionPoint(); const detectionSystemIds = criteria.getDetectionSystemIds(); const earliest = criteria.getEarliest(); const rule = criteria.getRule(); // check user match if user specified const userMatch = (user != null) ? user.equals(event.getUser()) : true; //check detection system match if detection systems specified let detectionSystemMatch = true; const eventDetSystemId = event.getDetectionSystem(); if (detectionSystemIds != null && detectionSystemIds.length > 0 && eventDetSystemId !== null) { detectionSystemMatch = detectionSystemIds.indexOf(eventDetSystemId.getDetectionSystemId()) > -1; } //check detection point match if detection point specified let detectionPointMatch = true; if (detecti