/*
* This file is part of caronte (https://github.com/eciavatta/caronte).
* Copyright (c) 2020 Emiliano Ciavatta.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import React, { Component } from "react";
import { Col, Container, Row } from "react-bootstrap";
import Table from "react-bootstrap/Table";
import backend from "../../backend";
import dispatcher from "../../dispatcher";
import validation from "../../validation";
import ButtonField from "../fields/ButtonField";
import CheckField from "../fields/CheckField";
import ChoiceField from "../fields/ChoiceField";
import ColorField from "../fields/extensions/ColorField";
import NumericField from "../fields/extensions/NumericField";
import InputField from "../fields/InputField";
import TextField from "../fields/TextField";
import CopyLinkPopover from "../objects/CopyLinkPopover";
import LinkPopover from "../objects/LinkPopover";
import "./common.scss";
import "./RulesPane.scss";
const classNames = require("classnames");
const _ = require("lodash");
class RulesPane extends Component {
emptyRule = {
"name": "",
"color": "",
"notes": "",
"enabled": true,
"patterns": [],
"filter": {
"service_port": 0,
"client_address": "",
"client_port": 0,
"min_duration": 0,
"max_duration": 0,
"min_bytes": 0,
"max_bytes": 0
},
"version": 0
};
emptyPattern = {
"regex": "",
"flags": {
"caseless": false,
"dot_all": false,
"multi_line": false,
"utf_8_mode": false,
"unicode_property": false
},
"min_occurrences": 0,
"max_occurrences": 0,
"direction": 0
};
state = {
rules: [],
newRule: this.emptyRule,
newPattern: this.emptyPattern
};
constructor(props) {
super(props);
this.directions = {
0: "both",
1: "c->s",
2: "s->c"
};
}
componentDidMount() {
this.reset();
this.loadRules();
dispatcher.register("notifications", this.handleNotifications);
document.title = "caronte:~/rules$";
}
componentWillUnmount() {
dispatcher.unregister(this.handleNotifications);
}
handleNotifications = (payload) => {
if (payload.event === "rules.new" || payload.event === "rules.edit") {
this.loadRules();
}
};
loadRules = () => {
backend.get("/api/rules").then((res) => this.setState({ rules: res.json, rulesStatusCode: res.status }))
.catch((res) => this.setState({ rulesStatusCode: res.status, rulesResponse: JSON.stringify(res.json) }));
};
addRule = () => {
if (this.validateRule(this.state.newRule)) {
backend.post("/api/rules", this.state.newRule).then((res) => {
this.reset();
this.setState({ ruleStatusCode: res.status });
this.loadRules();
}).catch((res) => {
this.setState({ ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json) });
});
}
};
deleteRule = () => {
const rule = this.state.selectedRule;
backend.delete(`/api/rules/${rule.id}`).then((res) => {
this.reset();
this.setState({ ruleStatusCode: res.status });
this.loadRules();
}).catch((res) => {
this.setState({ ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json) });
});
}
updateRule = () => {
const rule = this.state.selectedRule;
if (this.validateRule(rule)) {
backend.put(`/api/rules/${rule.id}`, rule).then((res) => {
this.reset();
this.setState({ ruleStatusCode: res.status });
this.loadRules();
}).catch((res) => {
this.setState({ ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json) });
});
}
};
validateRule = (rule) => {
let valid = true;
if (rule.name.length < 3) {
this.setState({ ruleNameError: "name.length < 3" });
valid = false;
}
if (!validation.isValidColor(rule.color)) {
this.setState({ ruleColorError: "color is not hexcolor" });
valid = false;
}
if (!validation.isValidPort(rule.filter["service_port"])) {
this.setState({ ruleServicePortError: "service_port > 65565" });
valid = false;
}
if (!validation.isValidPort(rule.filter["client_port"])) {
this.setState({ ruleClientPortError: "client_port > 65565" });
valid = false;
}
if (!validation.isValidAddress(rule.filter["client_address"])) {
this.setState({ ruleClientAddressError: "client_address is not ip_address" });
valid = false;
}
if (rule.filter["min_duration"] > rule.filter["max_duration"]) {
this.setState({ ruleDurationError: "min_duration > max_dur." });
valid = false;
}
if (rule.filter["min_bytes"] > rule.filter["max_bytes"]) {
this.setState({ ruleBytesError: "min_bytes > max_bytes" });
valid = false;
}
if (rule.patterns.length < 1) {
this.setState({ rulePatternsError: "no patterns added" });
valid = false;
}
return valid;
};
reset = () => {
const newRule = _.cloneDeep(this.emptyRule);
const newPattern = _.cloneDeep(this.emptyPattern);
this.setState({
selectedRule: null,
newRule,
selectedPattern: null,
newPattern,
patternRegexFocused: false,
patternOccurrencesFocused: false,
ruleNameError: null,
ruleColorError: null,
ruleServicePortError: null,
ruleClientPortError: null,
ruleClientAddressError: null,
ruleDurationError: null,
ruleBytesError: null,
rulePatternsError: null,
ruleStatusCode: null,
rulesStatusCode: null,
ruleResponse: null,
rulesResponse: null
});
};
updateParam = (callback) => {
const updatedRule = this.currentRule();
callback(updatedRule);
this.setState({ newRule: updatedRule });
};
currentRule = () => this.state.selectedRule != null ? this.state.selectedRule : this.state.newRule;
addPattern = (pattern) => {
if (!this.validatePattern(pattern)) {
return;
}
const newPattern = _.cloneDeep(this.emptyPattern);
this.currentRule().patterns.push(pattern);
this.setState({ newPattern });
};
editPattern = (pattern) => {
this.setState({
selectedPattern: pattern
});
};
updatePattern = (pattern) => {
if (!this.validatePattern(pattern)) {
return;
}
this.setState({
selectedPattern: null
});
};
validatePattern = (pattern) => {
let valid = true;
if (pattern.regex === "") {
valid = false;
this.setState({ patternRegexFocused: true });
}
if (pattern["min_occurrences"] > pattern["max_occurrences"]) {
valid = false;
this.setState({ patternOccurrencesFocused: true });
}
return valid;
};
render() {
const isUpdate = this.state.selectedRule != null;
const rule = this.currentRule();
const pattern = this.state.selectedPattern || this.state.newPattern;
let rules = this.state.rules.map((r) =>
{
this.reset();
this.setState({ selectedRule: _.cloneDeep(r) });
}} className={classNames("row-small", "row-clickable", { "row-selected": rule.id === r.id })}>
|
{r["name"]} |
|
{r["notes"]} |
);
let patterns = (this.state.selectedPattern == null && !isUpdate ?
rule.patterns.concat(this.state.newPattern) :
rule.patterns
).map((p) => p === pattern ?
{
this.updateParam(() => pattern.regex = v);
this.setState({ patternRegexFocused: pattern.regex === "" });
}} />
|
this.updateParam(() => pattern.flags["caseless"] = v)} /> |
this.updateParam(() => pattern.flags["dot_all"] = v)} /> |
this.updateParam(() => pattern.flags["multi_line"] = v)} /> |
this.updateParam(() => pattern.flags["utf_8_mode"] = v)} /> |
this.updateParam(() => pattern.flags["unicode_property"] = v)} /> |
this.updateParam(() => pattern["min_occurrences"] = v)} />
|
this.updateParam(() => pattern["max_occurrences"] = v)} />
|
s", "s->c"]}
value={this.directions[pattern.direction]}
onChange={(v) => this.updateParam(() => pattern.direction = v)} /> |
{this.state.selectedPattern == null ?
this.addPattern(p)} /> :
this.updatePattern(p)} />}
|
:
{p.regex} |
{p.flags["caseless"] ? "yes" : "no"} |
{p.flags["dot_all"] ? "yes" : "no"} |
{p.flags["multi_line"] ? "yes" : "no"} |
{p.flags["utf_8_mode"] ? "yes" : "no"} |
{p.flags["unicode_property"] ? "yes" : "no"} |
{p["min_occurrences"]} |
{p["max_occurrences"]} |
{this.directions[p.direction]} |
this.editPattern(p)}
/>
|
);
return (
GET /api/rules
{this.state.rulesStatusCode &&
}
id |
name |
color |
notes |
{rules}
{isUpdate ? `PUT /api/rules/${this.state.selectedRule.id}` : "POST /api/rules"}
this.updateParam((r) => r.name = v)}
error={this.state.ruleNameError} />
this.updateParam((r) => r.color = v)} />
this.updateParam((r) => r.notes = v)} />
filters:
this.updateParam((r) => r.filter["service_port"] = v)}
min={0}
max={65565}
error={this.state.ruleServicePortError}
/>
this.updateParam((r) => r.filter["client_port"] = v)}
min={0}
max={65565}
error={this.state.ruleClientPortError}
/>
this.updateParam((r) => r.filter["client_address"] = v)} />
this.updateParam((r) => r.filter["min_duration"] = v)} />
this.updateParam((r) => r.filter["max_duration"] = v)} />
this.updateParam((r) => r.filter["min_bytes"] = v)} />
this.updateParam((r) => r.filter["max_bytes"] = v)} />
regex |
!Aa |
.* |
\n+ |
UTF8 |
Uni_ |
min |
max |
direction |
{!isUpdate && actions | }
{patterns}
{this.state.rulePatternsError != null &&
error: {this.state.rulePatternsError}}
{}
{ isUpdate && }
);
}
}
export default RulesPane;