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