@appsensorlike/appsensorlike
Version:
A port of OWASP AppSensor reference implementation
1,496 lines • 53.9 kB
JavaScript
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