/* * 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: "patterns.length < 1" }); 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 && }
{rules}
id name color notes
{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)} />
{!isUpdate && } {patterns}
regex !Aa .* \n+ UTF8 Uni_ min max directionactions
{this.state.rulePatternsError != null && error: {this.state.rulePatternsError}}
{}
); } } export default RulesPane;