/* * 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)}); }); } }; 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]} {!isUpdate && 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} readonly={isUpdate}/> this.updateParam((r) => r.filter["client_port"] = v)} min={0} max={65565} error={this.state.ruleClientPortError} readonly={isUpdate}/> 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;