UNPKG

@appsensorlike/appsensorlike

Version:

A port of OWASP AppSensor reference implementation

323 lines (322 loc) 10.8 kB
import { AppsensorEntity, Interval, DetectionPoint, Utils, INTERVAL_UNITS, ObjectValidationError } from '../core.js'; /** * A Notification represents the {@link Interval} of time between a series * of {@link AppSensorEvent}s that trigger a {@link MonitorPoint}. * Where a {@link DetectionPoint} generates an {@link Attack}, * a {@link MonitorPoint} generates a Notification. */ class Notification extends Interval { constructor(duration = 0, unit = INTERVAL_UNITS.MINUTES, startTime = null, monitorPoint = null) { super(duration, unit); /** the start time of the interval */ this.startTime = new Date(); /** the MonitorPoint that generated the Notification */ this.monitorPoint = null; this.setStartTime(startTime !== null ? startTime : new Date()); this.setMonitorPoint(monitorPoint); } getStartTime() { return this.startTime; } setStartTime(startTime) { this.startTime = startTime; } getEndTime() { return new Date(this.startTime.getTime() + super.toMillis()); } getMonitorPoint() { return this.monitorPoint; } setMonitorPoint(monitorPoint) { this.monitorPoint = monitorPoint; } static getStartTimeAscendingComparator(n1, n2) { if (n1 === null || n2 === null) { throw new Error("n1 and n2 cannot be null"); } const n1StartTime = n1.getStartTime(); const n2StartTime = n2.getStartTime(); if (n1StartTime.getTime() < n2StartTime.getTime()) { return -1; } else if (n1StartTime.getTime() > n2StartTime.getTime()) { return 1; } else { return 0; } } static getEndTimeAscendingComparator(n1, n2) { if (n1 === null || n2 === null) { throw new Error("n1 and n2 cannot be null"); } const n1EndTime = n1.getEndTime(); const n2EndTime = n2.getEndTime(); if (n1EndTime.getTime() < n2EndTime.getTime()) { return -1; } else if (n1EndTime.getTime() > n2EndTime.getTime()) { return 1; } else { return 0; } } } /** * A MonitorPoint is a {@link DetectionPoint} that does not generate attacks, * but is rather a component of a {@link Rule} which generates attacks. */ class MonitorPoint extends DetectionPoint { constructor(detectionPoint, guid = '') { super(detectionPoint.getCategory(), (() => { //to satisfy TS const label = detectionPoint.getLabel(); const id = detectionPoint.getId(); return label !== undefined ? label : (id !== undefined ? id : ''); })(), detectionPoint.getThreshold(), detectionPoint.getResponses()); this.guid = guid !== '' ? guid : detectionPoint.getGuid(); } } /** * A Clause represents the terms in an {@link Expression} separated by an "OR" operator. * Each {@link MonitorPoint} in the monitorPoints field are the variables joined * by "AND" operators. * * For example: * In the expression: "MP1 AND MP2 OR MP3 AND MP4" * * "MP1 AND MP2" would be a single clause and "MP3 AND MP4" would be another. */ class Clause extends AppsensorEntity { constructor(monitorPoints = []) { super(); /** The monitor points being checked as variables in an Expression */ this.monitorPoints = []; this.setMonitorPoints(monitorPoints); } getMonitorPoints() { return this.monitorPoints; } setMonitorPoints(monitorPoints) { this.monitorPoints = monitorPoints; return this; } equals(obj) { if (!super.equals(obj)) return false; if (this === obj) return true; const other = obj; return Utils.equalsArrayEntitys(this.monitorPoints, other.getMonitorPoints()); } } /** * An Expression is a logical boolean expression where the variables are {@link MonitorPoint}s. * Each Expression in a {@link Rule} is separated by the "THEN" operator. * * An Expression contains a set of {@link Clause}s. Only one {@link Clause} needs to evaluate to true * for an Expression to evaluate to true. * * For example: * In the Rule: "MP1 AND MP2 THEN MP3 OR mP4" * * "MP1 AND MP2" would be the first Expression with a single Clause * and "MP3 OR MP4" would a second Expression with two Clauses. */ class Expression extends AppsensorEntity { constructor(window = null, clauses = []) { super(); /** The window of time a Clause must be triggered within */ this.window = null; /** The Clauses that build up the Expression. **/ this.clauses = []; this.setWindow(window); this.setClauses(clauses); } getWindow() { return this.window; } setWindow(window) { this.window = window; return this; } getClauses() { return this.clauses; } setClauses(clauses) { this.clauses = clauses; return this; } getDetectionPoints() { const detectionPoints = []; for (const clause of this.clauses) { for (const detectionPoint of clause.getMonitorPoints()) { detectionPoints.push(detectionPoint); } } return detectionPoints; } equals(obj) { if (!super.equals(obj)) return false; if (this === obj) return true; const other = obj; return Utils.equalsEntitys(this.window, other.getWindow()) && Utils.equalsArrayEntitys(this.clauses, other.getClauses()); } } /** * A Rule defines a logical aggregation of {@link MonitorPoint}s to determine if an * {@link Attack} is occurring. A Rule uses the boolean operators "AND" and "OR" as well * as the temporal operator "THEN" in joining {@link MonitorPoint}s into a Rule. * * For example: * A rule could be as simple as: "MP1 AND MP2" * Where the Rule will generate an attack if both MonitorPoint 1 and 2 * are violated within the Rule's window. * * More complex: "MP1 AND MP2 THEN MP3 OR MP4" * * Even more complex: "MP1 AND MP2 THEN MP3 OR MP4 THEN MP5 AND MP6 OR MP7" */ class Rule extends AppsensorEntity { constructor(guid = '', window = null, expressions = [], responses = [], name = '') { super(); /** * Unique identifier */ this.guid = ''; /** An optional human-friendly name for the Rule */ this.name = ''; /** * The window is the time all {@link Expression}s must be triggered within. * A Rule's window must be greater than or equal to the total of it's Expressions' windows. */ this.window = null; /** The {@link Expression}s that build up a Rule * The order of the list corresponds to the temporal order of the expressions. */ this.expressions = []; /** * Set of {@link Response}s associated with given Rule. */ this.responses = []; this.setGuid(guid); this.setWindow(window); this.setExpressions(expressions); this.setResponses(responses); this.setName(name); } getGuid() { return this.guid; } setGuid(guid) { this.guid = guid; return this; } getName() { return this.name; } setName(name) { this.name = name; return this; } getWindow() { return this.window; } setWindow(window) { this.window = window; return this; } getExpressions() { return this.expressions; } setExpressions(expression) { this.expressions = expression; return this; } getResponses() { return this.responses; } setResponses(responses) { this.responses = responses; return this; } /* returns the last expression in expressions */ getLastExpression() { return this.expressions[this.expressions.length - 1]; } /* checks whether the last expression contains a DetectionPoint * matching the type of triggerDetectionPoint */ checkLastExpressionForDetectionPoint(triggerDetectionPoint) { for (const detectionPoint of this.getLastExpression().getDetectionPoints()) { if (detectionPoint.typeMatches(triggerDetectionPoint)) { return true; } } return false; } /* returns all DetectionPoints contained within the Rule as a set*/ getAllDetectionPoints() { const detectionPoints = []; for (const expression of this.expressions) { const expDetP = expression.getDetectionPoints(); expDetP.forEach(el => { let found = false; for (let i = 0; i < detectionPoints.length; i++) { if (detectionPoints[i].equals(el)) { found = true; break; } ; } if (!found) { detectionPoints.push(el); } }); } return detectionPoints; } /* checks whether the Rule contains a detection point of the same type and threshold * as the detectionPoint parameter */ typeAndThresholdContainsDetectionPoint(detectionPoint) { for (const myPoint of this.getAllDetectionPoints()) { if (detectionPoint.typeAndThresholdMatches(myPoint)) { return true; } } return false; } /* checks whether other rule has same guid, i.e. is the same rule */ guidMatches(other) { if (other === null) { throw new Error("other must be non-null"); } let matches = true; matches && (matches = (this.guid !== null) ? this.guid === other.getGuid() : true); return matches; } equals(obj) { if (!super.equals(obj)) return false; if (this === obj) return true; const other = obj; return this.name === other.getName() && Utils.equalsEntitys(this.window, other.getWindow()) && Utils.equalsArrayEntitys(this.responses, other.getResponses()) && Utils.equalsArrayEntitys(this.expressions, other.getExpressions()) && this.guid === other.getGuid(); } checkValidInitialize() { if (this.guid.trim().length === 0) { throw new ObjectValidationError('guid cannot be empty string!', this); } if (this.window) { this.window.checkValidInitialize(); } } } export { Rule, Expression, Clause, Notification, MonitorPoint };