/* * 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 './common.scss'; import './RulesPane.scss'; import Table from "react-bootstrap/Table"; import {Col, Container, Row} from "react-bootstrap"; import InputField from "../fields/InputField"; import CheckField from "../fields/CheckField"; import TextField from "../fields/TextField"; import backend from "../../backend"; import NumericField from "../fields/extensions/NumericField"; import ColorField from "../fields/extensions/ColorField"; import ChoiceField from "../fields/ChoiceField"; import ButtonField from "../fields/ButtonField"; import validation from "../../validation"; import LinkPopover from "../objects/LinkPopover"; import {randomClassName} from "../../utils"; import dispatcher from "../../dispatcher"; 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", payload => { if (payload.event === "rules.new" || payload.event === "rules.edit") { this.loadRules(); } }); document.title = "caronte:~/rules$"; } 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: newRule, selectedPattern: null, newPattern: 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: 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["id"].substring(0, 8)} {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;