From e905618113309eaba7227ff1328a20f6846e4afd Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Mon, 5 Oct 2020 23:46:18 +0200 Subject: Implement timeline --- application_context.go | 2 ++ 1 file changed, 2 insertions(+) (limited to 'application_context.go') diff --git a/application_context.go b/application_context.go index e4be74d..6ea449d 100644 --- a/application_context.go +++ b/application_context.go @@ -20,6 +20,7 @@ type ApplicationContext struct { ConnectionsController ConnectionsController ServicesController *ServicesController ConnectionStreamsController ConnectionStreamsController + StatisticsController StatisticsController IsConfigured bool } @@ -93,5 +94,6 @@ func (sm *ApplicationContext) configure() { sm.ServicesController = NewServicesController(sm.Storage) sm.ConnectionsController = NewConnectionsController(sm.Storage, sm.ServicesController) sm.ConnectionStreamsController = NewConnectionStreamsController(sm.Storage) + sm.StatisticsController = NewStatisticsController(sm.Storage) sm.IsConfigured = true } -- cgit v1.2.3-70-g09d2 From d5f94b76986615b255b77b2a7b7ed336e5ad4838 Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Wed, 7 Oct 2020 14:58:48 +0200 Subject: Implement notifications --- VERSION | 1 + application_context.go | 10 +- application_context_test.go | 4 +- application_router.go | 27 +++- application_router_test.go | 6 +- caronte.go | 12 +- frontend/package.json | 5 +- frontend/src/backend.js | 3 +- frontend/src/components/Connection.js | 11 +- frontend/src/components/ConnectionContent.scss | 9 +- frontend/src/components/Notifications.js | 60 +++++++++ frontend/src/components/Notifications.scss | 48 +++++++ frontend/src/components/panels/PcapPane.js | 33 ++--- frontend/src/components/panels/RulePane.js | 116 ++++++++--------- frontend/src/components/panels/ServicePane.js | 35 +++--- frontend/src/components/panels/common.scss | 4 +- frontend/src/dispatcher.js | 35 ++++++ frontend/src/globals.js | 5 - frontend/src/index.js | 6 +- frontend/src/notifications.js | 40 ++++++ frontend/src/setupProxy.js | 7 ++ frontend/src/views/App.js | 43 ++++--- frontend/src/views/Connections.js | 57 ++++++--- frontend/src/views/Connections.scss | 3 +- frontend/src/views/Footer.js | 20 ++- frontend/yarn.lock | 32 ++++- go.mod | 1 + go.sum | 2 + notification_controller.go | 165 +++++++++++++++++++++++++ pcap_importer.go | 7 +- 30 files changed, 624 insertions(+), 183 deletions(-) create mode 100644 VERSION create mode 100644 frontend/src/components/Notifications.js create mode 100644 frontend/src/components/Notifications.scss create mode 100644 frontend/src/dispatcher.js delete mode 100644 frontend/src/globals.js create mode 100644 frontend/src/notifications.js create mode 100644 frontend/src/setupProxy.js create mode 100644 notification_controller.go (limited to 'application_context.go') diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..ce609ca --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.8 \ No newline at end of file diff --git a/application_context.go b/application_context.go index 6ea449d..9a9c97a 100644 --- a/application_context.go +++ b/application_context.go @@ -22,9 +22,10 @@ type ApplicationContext struct { ConnectionStreamsController ConnectionStreamsController StatisticsController StatisticsController IsConfigured bool + Version string } -func CreateApplicationContext(storage Storage) (*ApplicationContext, error) { +func CreateApplicationContext(storage Storage, version string) (*ApplicationContext, error) { var configWrapper struct { Config Config } @@ -45,9 +46,10 @@ func CreateApplicationContext(storage Storage) (*ApplicationContext, error) { } applicationContext := &ApplicationContext{ - Storage: storage, - Config: configWrapper.Config, - Accounts: accountsWrapper.Accounts, + Storage: storage, + Config: configWrapper.Config, + Accounts: accountsWrapper.Accounts, + Version: version, } applicationContext.configure() diff --git a/application_context_test.go b/application_context_test.go index eed0fd6..28c81a5 100644 --- a/application_context_test.go +++ b/application_context_test.go @@ -10,7 +10,7 @@ func TestCreateApplicationContext(t *testing.T) { wrapper := NewTestStorageWrapper(t) wrapper.AddCollection(Settings) - appContext, err := CreateApplicationContext(wrapper.Storage) + appContext, err := CreateApplicationContext(wrapper.Storage, "test") assert.NoError(t, err) assert.False(t, appContext.IsConfigured) assert.Zero(t, appContext.Config) @@ -39,7 +39,7 @@ func TestCreateApplicationContext(t *testing.T) { appContext.SetConfig(config) appContext.SetAccounts(accounts) - checkAppContext, err := CreateApplicationContext(wrapper.Storage) + checkAppContext, err := CreateApplicationContext(wrapper.Storage, "test") assert.NoError(t, err) assert.True(t, checkAppContext.IsConfigured) assert.Equal(t, checkAppContext.Config, config) diff --git a/application_router.go b/application_router.go index 8b5e32f..6431e22 100644 --- a/application_router.go +++ b/application_router.go @@ -13,7 +13,8 @@ import ( "time" ) -func CreateApplicationRouter(applicationContext *ApplicationContext) *gin.Engine { +func CreateApplicationRouter(applicationContext *ApplicationContext, + notificationController *NotificationController) *gin.Engine { router := gin.New() router.Use(gin.Logger()) router.Use(gin.Recovery()) @@ -47,6 +48,13 @@ func CreateApplicationRouter(applicationContext *ApplicationContext) *gin.Engine applicationContext.SetAccounts(settings.Accounts) c.JSON(http.StatusAccepted, gin.H{}) + notificationController.Notify("setup", InsertNotification, gin.H{}) + }) + + router.GET("/ws", func(c *gin.Context) { + if err := notificationController.NotificationHandler(c.Writer, c.Request); err != nil { + serverError(c, err) + } }) api := router.Group("/api") @@ -68,7 +76,9 @@ func CreateApplicationRouter(applicationContext *ApplicationContext) *gin.Engine if id, err := applicationContext.RulesManager.AddRule(c, rule); err != nil { unprocessableEntity(c, err) } else { - success(c, UnorderedDocument{"id": id}) + response := UnorderedDocument{"id": id} + success(c, response) + notificationController.Notify("rules.new", InsertNotification, response) } }) @@ -107,6 +117,7 @@ func CreateApplicationRouter(applicationContext *ApplicationContext) *gin.Engine notFound(c, UnorderedDocument{"id": id}) } else { success(c, rule) + notificationController.Notify("rules.edit", UpdateNotification, rule) } }) @@ -126,7 +137,9 @@ func CreateApplicationRouter(applicationContext *ApplicationContext) *gin.Engine if sessionID, err := applicationContext.PcapImporter.ImportPcap(fileName, flushAll); err != nil { unprocessableEntity(c, err) } else { - c.JSON(http.StatusAccepted, gin.H{"session": sessionID}) + response := gin.H{"session": sessionID} + c.JSON(http.StatusAccepted, response) + notificationController.Notify("pcap.upload", InsertNotification, response) } }) @@ -158,7 +171,9 @@ func CreateApplicationRouter(applicationContext *ApplicationContext) *gin.Engine } unprocessableEntity(c, err) } else { - c.JSON(http.StatusAccepted, gin.H{"session": sessionID}) + response := gin.H{"session": sessionID} + c.JSON(http.StatusAccepted, response) + notificationController.Notify("pcap.file", InsertNotification, response) } }) @@ -195,6 +210,7 @@ func CreateApplicationRouter(applicationContext *ApplicationContext) *gin.Engine session := gin.H{"session": sessionID} if cancelled := applicationContext.PcapImporter.CancelSession(sessionID); cancelled { c.JSON(http.StatusAccepted, session) + notificationController.Notify("sessions.delete", DeleteNotification, session) } else { notFound(c, session) } @@ -254,6 +270,8 @@ func CreateApplicationRouter(applicationContext *ApplicationContext) *gin.Engine if result { c.Status(http.StatusAccepted) + notificationController.Notify("connections.action", UpdateNotification, + gin.H{"connection_id": c.Param("id"), "action": c.Param("action")}) } else { notFound(c, gin.H{"connection": id}) } @@ -285,6 +303,7 @@ func CreateApplicationRouter(applicationContext *ApplicationContext) *gin.Engine } if err := applicationContext.ServicesController.SetService(c, service); err == nil { success(c, service) + notificationController.Notify("services.edit", UpdateNotification, service) } else { unprocessableEntity(c, err) } diff --git a/application_router_test.go b/application_router_test.go index 4225ab9..f4804e3 100644 --- a/application_router_test.go +++ b/application_router_test.go @@ -148,10 +148,12 @@ func NewRouterTestToolkit(t *testing.T, withSetup bool) *RouterTestToolkit { wrapper := NewTestStorageWrapper(t) wrapper.AddCollection(Settings) - appContext, err := CreateApplicationContext(wrapper.Storage) + appContext, err := CreateApplicationContext(wrapper.Storage, "test") require.NoError(t, err) gin.SetMode(gin.ReleaseMode) - router := CreateApplicationRouter(appContext) + notificationController := NewNotificationController(appContext) + go notificationController.Run() + router := CreateApplicationRouter(appContext, notificationController) toolkit := RouterTestToolkit{ appContext: appContext, diff --git a/caronte.go b/caronte.go index 098642c..288563c 100644 --- a/caronte.go +++ b/caronte.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" log "github.com/sirupsen/logrus" + "io/ioutil" ) func main() { @@ -22,12 +23,19 @@ func main() { log.WithError(err).WithFields(logFields).Fatal("failed to connect to MongoDB") } - applicationContext, err := CreateApplicationContext(storage) + versionBytes, err := ioutil.ReadFile("VERSION") + if err != nil { + log.WithError(err).Fatal("failed to load version file") + } + + applicationContext, err := CreateApplicationContext(storage, string(versionBytes)) if err != nil { log.WithError(err).WithFields(logFields).Fatal("failed to create application context") } - applicationRouter := CreateApplicationRouter(applicationContext) + notificationController := NewNotificationController(applicationContext) + go notificationController.Run() + applicationRouter := CreateApplicationRouter(applicationContext, notificationController) if applicationRouter.Run(fmt.Sprintf("%s:%v", *bindAddress, *bindPort)) != nil { log.WithError(err).WithFields(logFields).Fatal("failed to create the server") } diff --git a/frontend/package.json b/frontend/package.json index 5bc13f1..b3ad03a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,7 +14,7 @@ "classnames": "^2.2.6", "dompurify": "^2.1.1", "eslint-config-react-app": "^5.2.1", - "flux": "^3.1.3", + "http-proxy-middleware": "^1.0.5", "lodash": "^4.17.20", "node-sass": "^4.14.0", "pondjs": "^0.9.0", @@ -50,6 +50,5 @@ "last 1 firefox version", "last 1 safari version" ] - }, - "proxy": "http://localhost:3333" + } } diff --git a/frontend/src/backend.js b/frontend/src/backend.js index 72ee9dd..c7abd80 100644 --- a/frontend/src/backend.js +++ b/frontend/src/backend.js @@ -1,4 +1,3 @@ - async function json(method, url, data, json, headers) { const options = { method: method, @@ -28,7 +27,7 @@ async function json(method, url, data, json, headers) { const backend = { get: (url = "", headers = null) => - json("GET", url, null,null, headers), + json("GET", url, null, null, headers), post: (url = "", data = null, headers = null) => json("POST", url, null, data, headers), put: (url = "", data = null, headers = null) => diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js index 44f9f18..46a0cab 100644 --- a/frontend/src/components/Connection.js +++ b/frontend/src/components/Connection.js @@ -48,10 +48,11 @@ class Connection extends Component { render() { let conn = this.props.data; let serviceName = "/dev/null"; - let serviceColor = "#0F192E"; - if (conn.service.port !== 0) { - serviceName = conn.service.name; - serviceColor = conn.service.color; + let serviceColor = "#0f192e"; + if (this.props.services[conn["port_dst"]]) { + const service = this.props.services[conn["port_dst"]]; + serviceName = service.name; + serviceColor = service.color; } let startedAt = new Date(conn.started_at); let closedAt = new Date(conn.closed_at); @@ -87,7 +88,7 @@ class Connection extends Component { this.props.addServicePortFilter(conn.port_dst)} /> + onClick={() => this.props.addServicePortFilter(conn.port_dst)}/> {conn.ip_src} diff --git a/frontend/src/components/ConnectionContent.scss b/frontend/src/components/ConnectionContent.scss index de4d699..f4edec9 100644 --- a/frontend/src/components/ConnectionContent.scss +++ b/frontend/src/components/ConnectionContent.scss @@ -2,7 +2,6 @@ .connection-content { height: 100%; - padding: 10px 10px 0; background-color: $color-primary-0; pre { @@ -91,12 +90,12 @@ .connection-content-header { height: 33px; padding: 0; - background-color: $color-primary-2; + background-color: $color-primary-3; .header-info { font-size: 12px; padding-top: 7px; - padding-left: 20px; + padding-left: 25px; } .header-actions { @@ -104,6 +103,10 @@ .choice-field { margin-top: -5px; + + .field-value { + background-color: $color-primary-3; + } } } } diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js new file mode 100644 index 0000000..4d6dcd4 --- /dev/null +++ b/frontend/src/components/Notifications.js @@ -0,0 +1,60 @@ +import React, {Component} from 'react'; +import './Notifications.scss'; +import dispatcher from "../dispatcher"; + +const _ = require('lodash'); +const classNames = require('classnames'); + +class Notifications extends Component { + + state = { + notifications: [], + closedNotifications: [], + }; + + componentDidMount() { + dispatcher.register("notifications", notification => { + const notifications = this.state.notifications; + notifications.push(notification); + this.setState({notifications}); + + setTimeout(() => { + const notifications = this.state.notifications; + notification.open = true; + this.setState({notifications}); + }, 100); + + setTimeout(() => { + const notifications = _.without(this.state.notifications, notification); + const closedNotifications = this.state.closedNotifications.concat([notification]); + notification.closed = true; + this.setState({notifications, closedNotifications}); + }, 5000); + + setTimeout(() => { + const closedNotifications = _.without(this.state.closedNotifications, notification); + this.setState({closedNotifications}); + }, 6000); + }); + } + + render() { + return ( +
+
+ { + this.state.closedNotifications.concat(this.state.notifications).map(n => +
+

{n.event}

+ {JSON.stringify(n.message)} +
+ ) + } +
+
+ ); + } +} + +export default Notifications; diff --git a/frontend/src/components/Notifications.scss b/frontend/src/components/Notifications.scss new file mode 100644 index 0000000..b0c334b --- /dev/null +++ b/frontend/src/components/Notifications.scss @@ -0,0 +1,48 @@ +@import "../colors.scss"; + +.notifications { + position: absolute; + + left: 30px; + bottom: 50px; + z-index: 50; + + .notifications-list { + + } + + .notification { + background-color: $color-green; + border-left: 5px solid $color-green-dark; + padding: 10px; + margin: 10px 0; + width: 250px; + color: $color-green-light; + transform: translateX(-300px); + transition: all 1s ease; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + .notification-title { + font-size: 0.9em; + margin: 0; + } + + .notification-description { + font-size: 0.8em; + } + + &.notification-open { + transform: translateX(0px); + } + + &.notification-closed { + transform: translateY(-50px); + opacity: 0; + } + + } + + +} \ No newline at end of file diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js index 31d8815..13f7cb3 100644 --- a/frontend/src/components/panels/PcapPane.js +++ b/frontend/src/components/panels/PcapPane.js @@ -9,28 +9,31 @@ import CheckField from "../fields/CheckField"; import TextField from "../fields/TextField"; import ButtonField from "../fields/ButtonField"; import LinkPopover from "../objects/LinkPopover"; +import dispatcher from "../../dispatcher"; class PcapPane extends Component { - constructor(props) { - super(props); - - this.state = { - sessions: [], - isUploadFileValid: true, - isUploadFileFocused: false, - uploadFlushAll: false, - isFileValid: true, - isFileFocused: false, - fileValue: "", - processFlushAll: false, - deleteOriginalFile: false - }; - } + state = { + sessions: [], + isUploadFileValid: true, + isUploadFileFocused: false, + uploadFlushAll: false, + isFileValid: true, + isFileFocused: false, + fileValue: "", + processFlushAll: false, + deleteOriginalFile: false + }; componentDidMount() { this.loadSessions(); + dispatcher.register("notifications", payload => { + if (payload.event === "pcap.upload" || payload.event === "pcap.file") { + this.loadSessions(); + } + }); + document.title = "caronte:~/pcaps$"; } diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js index 4641378..76f3ac0 100644 --- a/frontend/src/components/panels/RulePane.js +++ b/frontend/src/components/panels/RulePane.js @@ -14,35 +14,13 @@ 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 RulePane extends Component { - constructor(props) { - super(props); - - this.state = { - rules: [], - newRule: this.emptyRule, - newPattern: this.emptyPattern - }; - - this.directions = { - 0: "both", - 1: "c->s", - 2: "s->c" - }; - } - - componentDidMount() { - this.reset(); - this.loadRules(); - - document.title = "caronte:~/rules$"; - } - emptyRule = { "name": "", "color": "", @@ -60,7 +38,6 @@ class RulePane extends Component { }, "version": 0 }; - emptyPattern = { "regex": "", "flags": { @@ -74,6 +51,34 @@ class RulePane extends Component { "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})) @@ -226,17 +231,17 @@ class RulePane extends Component { { this.reset(); this.setState({selectedRule: _.cloneDeep(r)}); - }} className={classNames("row-small", "row-clickable", {"row-selected": rule.id === r.id })}> + }} 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 + rule.patterns.concat(this.state.newPattern) : + rule.patterns ).map(p => p === pattern ? @@ -244,7 +249,7 @@ class RulePane extends Component { onChange={(v) => { this.updateParam(() => pattern.regex = v); this.setState({patternRegexFocused: pattern.regex === ""}); - }} /> + }}/> this.updateParam(() => pattern.flags.caseless = v)}/> @@ -259,34 +264,35 @@ class RulePane extends Component { this.updateParam(() => pattern.min_occurrences = v)} /> + onChange={(v) => this.updateParam(() => pattern.min_occurrences = v)}/> this.updateParam(() => pattern.max_occurrences = v)} /> + onChange={(v) => this.updateParam(() => pattern.max_occurrences = v)}/> s", "s->c"]} value={this.directions[pattern.direction]} - onChange={(v) => this.updateParam(() => pattern.direction = v)} /> + onChange={(v) => this.updateParam(() => pattern.direction = v)}/> {this.state.selectedPattern == null ? this.addPattern(p)}/> : - this.updatePattern(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.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) }/>} + onClick={() => this.editPattern(p)}/>} ); @@ -296,9 +302,9 @@ class RulePane extends Component {
GET /api/rules {this.state.rulesStatusCode && - } + }
@@ -327,7 +333,7 @@ class RulePane extends Component { + placement="left"/>
@@ -336,11 +342,11 @@ class RulePane extends Component { this.updateParam((r) => r.name = v)} - error={this.state.ruleNameError} /> + error={this.state.ruleNameError}/> this.updateParam((r) => r.color = v)} /> + onChange={(v) => this.updateParam((r) => r.color = v)}/> this.updateParam((r) => r.notes = v)} /> + onChange={(v) => this.updateParam((r) => r.notes = v)}/> @@ -348,29 +354,29 @@ class RulePane extends Component { this.updateParam((r) => r.filter.service_port = v)} min={0} max={65565} error={this.state.ruleServicePortError} - readonly={isUpdate} /> + readonly={isUpdate}/> this.updateParam((r) => r.filter.client_port = v)} min={0} max={65565} error={this.state.ruleClientPortError} - readonly={isUpdate} /> + readonly={isUpdate}/> this.updateParam((r) => r.filter.client_address = v)} /> + onChange={(v) => this.updateParam((r) => r.filter.client_address = v)}/> this.updateParam((r) => r.filter.min_duration = v)} /> + onChange={(v) => this.updateParam((r) => r.filter.min_duration = v)}/> this.updateParam((r) => r.filter.max_duration = v)} /> + onChange={(v) => this.updateParam((r) => r.filter.max_duration = v)}/> this.updateParam((r) => r.filter.min_bytes = v)} /> + onChange={(v) => this.updateParam((r) => r.filter.min_bytes = v)}/> this.updateParam((r) => r.filter.max_bytes = v)} /> + onChange={(v) => this.updateParam((r) => r.filter.max_bytes = v)}/> @@ -388,7 +394,7 @@ class RulePane extends Component { min max direction - {!isUpdate && actions } + {!isUpdate && actions} @@ -403,7 +409,7 @@ class RulePane extends Component {
{} + bordered onClick={isUpdate ? this.updateRule : this.addRule}/>
diff --git a/frontend/src/components/panels/ServicePane.js b/frontend/src/components/panels/ServicePane.js index 0e99652..22c6655 100644 --- a/frontend/src/components/panels/ServicePane.js +++ b/frontend/src/components/panels/ServicePane.js @@ -12,28 +12,13 @@ import ButtonField from "../fields/ButtonField"; import validation from "../../validation"; import LinkPopover from "../objects/LinkPopover"; import {createCurlCommand} from "../../utils"; +import dispatcher from "../../dispatcher"; const classNames = require('classnames'); const _ = require('lodash'); class ServicePane extends Component { - constructor(props) { - super(props); - - this.state = { - services: [], - currentService: this.emptyService, - }; - - document.title = "caronte:~/services$"; - } - - componentDidMount() { - this.reset(); - this.loadServices(); - } - emptyService = { "port": 0, "name": "", @@ -41,6 +26,24 @@ class ServicePane extends Component { "notes": "" }; + state = { + services: [], + currentService: this.emptyService, + }; + + componentDidMount() { + this.reset(); + this.loadServices(); + + dispatcher.register("notifications", payload => { + if (payload.event === "services.edit") { + this.loadServices(); + } + }); + + document.title = "caronte:~/services$"; + } + loadServices = () => { backend.get("/api/services") .then(res => this.setState({services: Object.values(res.json), servicesStatusCode: res.status})) diff --git a/frontend/src/components/panels/common.scss b/frontend/src/components/panels/common.scss index 121a917..1468f35 100644 --- a/frontend/src/components/panels/common.scss +++ b/frontend/src/components/panels/common.scss @@ -2,11 +2,9 @@ .pane-container { height: 100%; - padding: 10px 10px 0; background-color: $color-primary-3; .pane-section { - margin-bottom: 10px; background-color: $color-primary-0; .section-header { @@ -14,7 +12,7 @@ font-weight: 500; display: flex; padding: 5px 10px; - background-color: $color-primary-2; + background-color: $color-primary-3; .api-request { flex: 1; diff --git a/frontend/src/dispatcher.js b/frontend/src/dispatcher.js new file mode 100644 index 0000000..4b8b5a4 --- /dev/null +++ b/frontend/src/dispatcher.js @@ -0,0 +1,35 @@ + +class Dispatcher { + + constructor() { + this.listeners = []; + } + + dispatch = (topic, payload) => { + this.listeners.filter(l => l.topic === topic).forEach(l => l.callback(payload)); + }; + + register = (topic, callback) => { + if (typeof callback !== "function") { + throw new Error("dispatcher callback must be a function"); + } + if (typeof topic === "string") { + this.listeners.push({topic, callback}); + } else if (typeof topic === "object" && Array.isArray(topic)) { + topic.forEach(e => { + if (typeof e !== "string") { + throw new Error("all topics must be strings"); + } + }); + + topic.forEach(e => this.listeners.push({e, callback})); + } else { + throw new Error("topic must be a string or an array of strings"); + } + }; + +} + +const dispatcher = new Dispatcher(); + +export default dispatcher; diff --git a/frontend/src/globals.js b/frontend/src/globals.js deleted file mode 100644 index cd4dc64..0000000 --- a/frontend/src/globals.js +++ /dev/null @@ -1,5 +0,0 @@ -import {Dispatcher} from "flux"; - -const dispatcher = new Dispatcher(); - -export default dispatcher; diff --git a/frontend/src/index.js b/frontend/src/index.js index 2e90371..beb52ae 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -4,6 +4,9 @@ import 'bootstrap/dist/css/bootstrap.css'; import './index.scss'; import App from './views/App'; import * as serviceWorker from './serviceWorker'; +import notifications from "./notifications"; + +notifications.createWebsocket(); ReactDOM.render( @@ -12,7 +15,4 @@ ReactDOM.render( document.getElementById('root') ); -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister(); diff --git a/frontend/src/notifications.js b/frontend/src/notifications.js new file mode 100644 index 0000000..2a77ffb --- /dev/null +++ b/frontend/src/notifications.js @@ -0,0 +1,40 @@ +import log from "./log"; +import dispatcher from "./dispatcher"; + +class Notifications { + + constructor() { + const location = document.location; + this.wsUrl = `ws://${location.hostname}${location.port ? ":" + location.port : ""}/ws`; + } + + createWebsocket = () => { + this.ws = new WebSocket(this.wsUrl); + this.ws.onopen = this.onWebsocketOpen; + this.ws.onerror = this.onWebsocketError; + this.ws.onclose = this.onWebsocketClose; + this.ws.onmessage = this.onWebsocketMessage; + }; + + onWebsocketOpen = () => { + log.debug("Connected to backend with websocket"); + }; + + onWebsocketError = (err) => { + this.ws.close(); + log.error("Websocket error", err); + setTimeout(() => this.createWebsocket(), 3000); + }; + + onWebsocketClose = () => { + log.debug("Closed websocket connection with backend"); + }; + + onWebsocketMessage = (message) => { + dispatcher.dispatch("notifications", JSON.parse(message.data)); + }; +} + +const notifications = new Notifications(); + +export default notifications; diff --git a/frontend/src/setupProxy.js b/frontend/src/setupProxy.js new file mode 100644 index 0000000..6f082c8 --- /dev/null +++ b/frontend/src/setupProxy.js @@ -0,0 +1,7 @@ +const { createProxyMiddleware } = require('http-proxy-middleware'); + +module.exports = function(app) { + app.use(createProxyMiddleware("/api", { target: "http://localhost:3333" })); + app.use(createProxyMiddleware("/setup", { target: "http://localhost:3333" })); + app.use(createProxyMiddleware("/ws", { target: "http://localhost:3333", ws: true })); +}; diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js index 00d9110..c14b7f5 100644 --- a/frontend/src/views/App.js +++ b/frontend/src/views/App.js @@ -5,18 +5,22 @@ import MainPane from "../components/panels/MainPane"; import Footer from "./Footer"; import {BrowserRouter as Router} from "react-router-dom"; import Filters from "./Filters"; -import backend from "../backend"; import ConfigurationPane from "../components/panels/ConfigurationPane"; -import log from "../log"; +import Notifications from "../components/Notifications"; +import dispatcher from "../dispatcher"; class App extends Component { state = {}; componentDidMount() { - backend.get("/api/services").then(_ => { - log.debug("Caronte is already configured. Loading main.."); - this.setState({configured: true}); + dispatcher.register("notifications", payload => { + if (payload.event === "connected") { + this.setState({ + connected: true, + configured: payload.message["is_configured"] + }); + } }); setInterval(() => { @@ -36,19 +40,22 @@ class App extends Component { return (
- -
-
this.setState({filterWindowOpen: true})}/> -
-
- {this.state.configured ? : - this.setState({configured: true})}/>} - {modal} -
-
- {this.state.configured &&
} -
-
+ + {this.state.connected && + +
+
this.setState({filterWindowOpen: true})}/> +
+
+ {this.state.configured ? : + this.setState({configured: true})}/>} + {modal} +
+
+ {this.state.configured &&
} +
+
+ }
); } diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js index fe655b3..bd631a2 100644 --- a/frontend/src/views/Connections.js +++ b/frontend/src/views/Connections.js @@ -6,9 +6,9 @@ import {Redirect} from 'react-router'; import {withRouter} from "react-router-dom"; import backend from "../backend"; import ConnectionMatchedRules from "../components/ConnectionMatchedRules"; -import dispatcher from "../globals"; import log from "../log"; import ButtonField from "../components/fields/ButtonField"; +import dispatcher from "../dispatcher"; class Connections extends Component { @@ -17,8 +17,6 @@ class Connections extends Component { connections: [], firstConnection: null, lastConnection: null, - flagRule: null, - rules: null, queryString: null }; @@ -41,14 +39,24 @@ class Connections extends Component { // TODO: scroll to initial connection } - dispatcher.register((payload) => { - if (payload.actionType === "timeline-update") { - this.connectionsListRef.current.scrollTop = 0; - this.loadConnections({ - started_after: Math.round(payload.from.getTime() / 1000), - started_before: Math.round(payload.to.getTime() / 1000), - limit: this.maxConnections - }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`)); + dispatcher.register("timeline_updates", payload => { + this.connectionsListRef.current.scrollTop = 0; + this.loadConnections({ + started_after: Math.round(payload.from.getTime() / 1000), + started_before: Math.round(payload.to.getTime() / 1000), + limit: this.maxConnections + }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`)); + }); + + dispatcher.register("notifications", payload => { + if (payload.event === "rules.new" || payload.event === "rules.edit") { + this.loadRules().then(() => log.debug("Loaded connection rules after notification update")); + } + }); + + dispatcher.register("notifications", payload => { + if (payload.event === "services.edit") { + this.loadServices().then(() => log.debug("Services reloaded after notification update")); } }); } @@ -116,6 +124,13 @@ class Connections extends Component { } this.setState({loading: true}); + if (!this.state.rules) { + await this.loadRules(); + } + if (!this.state.services) { + await this.loadServices(); + } + let res = (await backend.get(`${url}?${urlParams}`)).json; let connections = this.state.connections; @@ -154,28 +169,29 @@ class Connections extends Component { } } - let rules = this.state.rules; - if (rules == null) { - rules = (await backend.get("/api/rules")).json; - } - this.setState({ loading: false, connections: connections, - rules: rules, firstConnection: firstConnection, lastConnection: lastConnection }); if (firstConnection != null && lastConnection != null) { - dispatcher.dispatch({ - actionType: "connections-update", + dispatcher.dispatch("connection_updates", { from: new Date(lastConnection["started_at"]), to: new Date(firstConnection["started_at"]) }); } } + loadRules = async () => { + return backend.get("/api/rules").then(res => this.setState({rules: res.json})); + }; + + loadServices = async () => { + return backend.get("/api/services").then(res => this.setState({services: res.json})); + }; + render() { let redirect; let queryString = this.state.queryString !== null ? this.state.queryString : ""; @@ -222,7 +238,8 @@ class Connections extends Component { selected={this.state.selected === c.id} onMarked={marked => c.marked = marked} onEnabled={enabled => c.hidden = !enabled} - addServicePortFilter={this.addServicePortFilter}/>, + addServicePortFilter={this.addServicePortFilter} + services={this.state.services}/>, c.matched_rules.length > 0 && - log.debug("Statistics loaded after mount")); - - dispatcher.register((payload) => { - if (payload.actionType === "connections-update") { - this.setState({ - selection: new TimeRange(payload.from, payload.to), - }); - } + this.loadStatistics(this.state.metric, filteredPort).then(() => log.debug("Statistics loaded after mount")); + + dispatcher.register("connection_updates", payload => { + this.setState({ + selection: new TimeRange(payload.from, payload.to), + }); }); } @@ -109,8 +106,7 @@ class Footer extends Component { clearTimeout(this.selectionTimeout); } this.selectionTimeout = setTimeout(() => { - dispatcher.dispatch({ - actionType: "timeline-update", + dispatcher.dispatch("timeline_updates", { from: timeRange.begin(), to: timeRange.end() }); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index fa150ab..e3cade9 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1656,6 +1656,13 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/http-proxy@^1.17.4": + version "1.17.4" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.4.tgz#e7c92e3dbe3e13aa799440ff42e6d3a17a9d045b" + integrity sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q== + dependencies: + "@types/node" "*" + "@types/invariant@^2.2.33": version "2.2.34" resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.34.tgz#05e4f79f465c2007884374d4795452f995720bbe" @@ -2707,7 +2714,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@~3.0.2: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -5738,7 +5745,18 @@ http-proxy-middleware@0.19.1: lodash "^4.17.11" micromatch "^3.1.10" -http-proxy@^1.17.0: +http-proxy-middleware@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-1.0.5.tgz#4c6e25d95a411e3d750bc79ccf66290675176dc2" + integrity sha512-CKzML7u4RdGob8wuKI//H8Ein6wNTEQR7yjVEzPbhBLGdOfkfvgTnp2HLnniKBDP9QW4eG10/724iTWLBeER3g== + dependencies: + "@types/http-proxy" "^1.17.4" + http-proxy "^1.18.1" + is-glob "^4.0.1" + lodash "^4.17.19" + micromatch "^4.0.2" + +http-proxy@^1.17.0, http-proxy@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== @@ -7434,6 +7452,14 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -8405,7 +8431,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.2.1: +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== diff --git a/go.mod b/go.mod index 308b16b..404f64c 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/go-playground/validator/v10 v10.2.0 github.com/golang/protobuf v1.3.5 // indirect github.com/google/gopacket v1.1.17 + github.com/gorilla/websocket v1.4.2 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/sirupsen/logrus v1.4.2 diff --git a/go.sum b/go.sum index fd63c39..d29e0cb 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbB github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw= github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= diff --git a/notification_controller.go b/notification_controller.go new file mode 100644 index 0000000..88c9e8c --- /dev/null +++ b/notification_controller.go @@ -0,0 +1,165 @@ +package main + +import ( + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + log "github.com/sirupsen/logrus" + "net" + "net/http" + "time" +) + +const ( + InsertNotification = "insert" + UpdateNotification = "update" + DeleteNotification = "delete" + + writeWait = 10 * time.Second + pongWait = 60 * time.Second + pingPeriod = (pongWait * 9) / 10 + maxMessageSize = 512 +) + +type NotificationController struct { + upgrader websocket.Upgrader + clients map[net.Addr]*client + broadcast chan interface{} + register chan *client + unregister chan *client + applicationContext *ApplicationContext +} + +func NewNotificationController(applicationContext *ApplicationContext) *NotificationController { + return &NotificationController{ + upgrader: websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + }, + clients: make(map[net.Addr]*client), + broadcast: make(chan interface{}), + register: make(chan *client), + unregister: make(chan *client), + applicationContext: applicationContext, + } +} + +type client struct { + conn *websocket.Conn + send chan interface{} + notificationController *NotificationController +} + +func (wc *NotificationController) NotificationHandler(w http.ResponseWriter, r *http.Request) error { + conn, err := wc.upgrader.Upgrade(w, r, nil) + if err != nil { + log.WithError(err).Error("failed to set websocket upgrade") + return err + } + + client := &client{ + conn: conn, + send: make(chan interface{}), + notificationController: wc, + } + wc.register <- client + go client.readPump() + go client.writePump() + + return nil +} + +func (wc *NotificationController) Run() { + for { + select { + case client := <-wc.register: + wc.clients[client.conn.RemoteAddr()] = client + payload := gin.H{"event": "connected", "message": gin.H{ + "version": wc.applicationContext.Version, + "is_configured": wc.applicationContext.IsConfigured, + "connected_clients": len(wc.clients), + }} + client.send <- payload + log.WithField("connected_clients", len(wc.clients)). + WithField("remote_address", client.conn.RemoteAddr()). + Info("[+] a websocket client connected") + case client := <-wc.unregister: + if _, ok := wc.clients[client.conn.RemoteAddr()]; ok { + close(client.send) + _ = client.conn.WriteMessage(websocket.CloseMessage, nil) + _ = client.conn.Close() + delete(wc.clients, client.conn.RemoteAddr()) + log.WithField("connected_clients", len(wc.clients)). + WithField("remote_address", client.conn.RemoteAddr()). + Info("[-] a websocket client disconnected") + } + case payload := <-wc.broadcast: + for _, client := range wc.clients { + select { + case client.send <- payload: + default: + close(client.send) + delete(wc.clients, client.conn.RemoteAddr()) + } + } + } + } +} + +func (wc *NotificationController) Notify(event string, eventType string, message interface{}) { + wc.broadcast <- gin.H{"event": event, "event_type": eventType, "message": message} +} + +func (c *client) readPump() { + c.conn.SetReadLimit(maxMessageSize) + if err := c.conn.SetReadDeadline(time.Now().Add(pongWait)); err != nil { + c.close() + return + } + c.conn.SetPongHandler(func(string) error { return c.conn.SetReadDeadline(time.Now().Add(pongWait)) }) + for { + if _, _, err := c.conn.ReadMessage(); err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { + log.WithError(err).WithField("remote_address", c.conn.RemoteAddr()). + Warn("unexpected websocket disconnection") + } + break + } + } + + c.close() +} + +func (c *client) writePump() { + ticker := time.NewTicker(pingPeriod) + defer ticker.Stop() + + for { + select { + case payload, ok := <-c.send: + if !ok { + return + } + if err := c.conn.SetWriteDeadline(time.Now().Add(writeWait)); err != nil { + c.close() + return + } + if err := c.conn.WriteJSON(payload); err != nil { + c.close() + return + } + case <-ticker.C: + if err := c.conn.SetWriteDeadline(time.Now().Add(writeWait)); err != nil { + c.close() + return + } + if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { + c.close() + return + } + } + } +} + +func (c *client) close() { + c.notificationController.unregister <- c +} diff --git a/pcap_importer.go b/pcap_importer.go index 1739b3f..78a5e6c 100644 --- a/pcap_importer.go +++ b/pcap_importer.go @@ -19,7 +19,6 @@ import ( const PcapsBasePath = "pcaps/" const ProcessingPcapsBasePath = PcapsBasePath + "processing/" const initialAssemblerPoolSize = 16 -const flushOlderThan = 5 * time.Minute const importUpdateProgressInterval = 100 * time.Millisecond type PcapImporter struct { @@ -201,8 +200,8 @@ func (pi *PcapImporter) parsePcap(session ImportingSession, fileName string, flu var servicePort uint16 var index int - isDstServer := pi.serverNet.Contains(packet.NetworkLayer().NetworkFlow().Dst().Raw()) - isSrcServer := pi.serverNet.Contains(packet.NetworkLayer().NetworkFlow().Src().Raw()) + isDstServer := pi.serverNet.Contains(packet.NetworkLayer().NetworkFlow().Dst().Raw()) + isSrcServer := pi.serverNet.Contains(packet.NetworkLayer().NetworkFlow().Src().Raw()) if isDstServer && !isSrcServer { servicePort = uint16(tcp.DstPort) index = 0 @@ -284,7 +283,7 @@ func deleteProcessingFile(fileName string) { } func moveProcessingFile(sessionID string, fileName string) { - if err := os.Rename(ProcessingPcapsBasePath + fileName, PcapsBasePath + sessionID + path.Ext(fileName)); err != nil { + if err := os.Rename(ProcessingPcapsBasePath+fileName, PcapsBasePath+sessionID+path.Ext(fileName)); err != nil { log.WithError(err).Error("failed to move processed file") } } -- cgit v1.2.3-70-g09d2 From a30815021e61023f996b1450ddcd9164a6e18bef Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Thu, 8 Oct 2020 17:07:07 +0200 Subject: Add header license to all files --- application_context.go | 17 +++++++++++++++++ application_context_test.go | 17 +++++++++++++++++ application_router.go | 17 +++++++++++++++++ application_router_test.go | 17 +++++++++++++++++ caronte.go | 17 +++++++++++++++++ caronte_test.go | 17 +++++++++++++++++ connection_handler.go | 17 +++++++++++++++++ connection_handler_test.go | 17 +++++++++++++++++ connection_streams_controller.go | 17 +++++++++++++++++ connections_controller.go | 17 +++++++++++++++++ frontend/src/backend.js | 17 +++++++++++++++++ frontend/src/components/Connection.js | 17 +++++++++++++++++ frontend/src/components/ConnectionContent.js | 17 +++++++++++++++++ frontend/src/components/ConnectionMatchedRules.js | 17 +++++++++++++++++ frontend/src/components/MessageAction.js | 17 +++++++++++++++++ frontend/src/components/Notifications.js | 17 +++++++++++++++++ frontend/src/components/fields/ButtonField.js | 17 +++++++++++++++++ frontend/src/components/fields/CheckField.js | 17 +++++++++++++++++ frontend/src/components/fields/ChoiceField.js | 17 +++++++++++++++++ frontend/src/components/fields/InputField.js | 17 +++++++++++++++++ frontend/src/components/fields/TextField.js | 17 +++++++++++++++++ .../src/components/fields/extensions/ColorField.js | 17 +++++++++++++++++ .../components/fields/extensions/NumericField.js | 17 +++++++++++++++++ .../components/filters/BooleanConnectionsFilter.js | 17 +++++++++++++++++ .../src/components/filters/FiltersDefinitions.js | 17 +++++++++++++++++ .../components/filters/RulesConnectionsFilter.js | 17 +++++++++++++++++ .../components/filters/StringConnectionsFilter.js | 17 +++++++++++++++++ frontend/src/components/objects/LinkPopover.js | 17 +++++++++++++++++ frontend/src/components/panels/ConfigurationPane.js | 17 +++++++++++++++++ frontend/src/components/panels/MainPane.js | 17 +++++++++++++++++ frontend/src/components/panels/PcapPane.js | 17 +++++++++++++++++ frontend/src/components/panels/RulePane.js | 17 +++++++++++++++++ frontend/src/components/panels/ServicePane.js | 17 +++++++++++++++++ frontend/src/dispatcher.js | 16 ++++++++++++++++ frontend/src/index.js | 17 +++++++++++++++++ frontend/src/log.js | 17 +++++++++++++++++ frontend/src/notifications.js | 17 +++++++++++++++++ frontend/src/setupProxy.js | 17 +++++++++++++++++ frontend/src/utils.js | 17 +++++++++++++++++ frontend/src/validation.js | 16 ++++++++++++++++ frontend/src/views/App.js | 17 +++++++++++++++++ frontend/src/views/Connections.js | 17 +++++++++++++++++ frontend/src/views/Filters.js | 21 +++++++++++++++++++-- frontend/src/views/Header.js | 17 +++++++++++++++++ frontend/src/views/Timeline.js | 17 +++++++++++++++++ notification_controller.go | 17 +++++++++++++++++ parsers/http_request_parser.go | 17 +++++++++++++++++ parsers/http_response_parser.go | 17 +++++++++++++++++ parsers/parser.go | 17 +++++++++++++++++ parsers/parser_utils.go | 17 +++++++++++++++++ pcap_importer.go | 17 +++++++++++++++++ pcap_importer_test.go | 17 +++++++++++++++++ resources_controller.go | 17 +++++++++++++++++ rules_manager.go | 17 +++++++++++++++++ rules_manager_test.go | 17 +++++++++++++++++ services_controller.go | 17 +++++++++++++++++ statistics_controller.go | 17 +++++++++++++++++ storage.go | 17 +++++++++++++++++ storage_test.go | 17 +++++++++++++++++ stream_handler.go | 17 +++++++++++++++++ stream_handler_test.go | 17 +++++++++++++++++ utils.go | 17 +++++++++++++++++ 62 files changed, 1054 insertions(+), 2 deletions(-) (limited to 'application_context.go') diff --git a/application_context.go b/application_context.go index 9a9c97a..9897bb6 100644 --- a/application_context.go +++ b/application_context.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/application_context_test.go b/application_context_test.go index 28c81a5..a7f1a49 100644 --- a/application_context_test.go +++ b/application_context_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/application_router.go b/application_router.go index da71538..9fd7e3d 100644 --- a/application_router.go +++ b/application_router.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/application_router_test.go b/application_router_test.go index f4804e3..9741eed 100644 --- a/application_router_test.go +++ b/application_router_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/caronte.go b/caronte.go index d999724..d4265bc 100644 --- a/caronte.go +++ b/caronte.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/caronte_test.go b/caronte_test.go index 12ec50f..8935ea3 100644 --- a/caronte_test.go +++ b/caronte_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/connection_handler.go b/connection_handler.go index 3d38531..6b2b411 100644 --- a/connection_handler.go +++ b/connection_handler.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/connection_handler_test.go b/connection_handler_test.go index 0bee0ac..d980041 100644 --- a/connection_handler_test.go +++ b/connection_handler_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/connection_streams_controller.go b/connection_streams_controller.go index 98f2aca..9251a3a 100644 --- a/connection_streams_controller.go +++ b/connection_streams_controller.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/connections_controller.go b/connections_controller.go index e872c9f..30a5ee5 100644 --- a/connections_controller.go +++ b/connections_controller.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/frontend/src/backend.js b/frontend/src/backend.js index 1b2d8d2..cc8604a 100644 --- a/frontend/src/backend.js +++ b/frontend/src/backend.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + async function json(method, url, data, json, headers) { const options = { method: method, diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js index b7e2531..c7b0010 100644 --- a/frontend/src/components/Connection.js +++ b/frontend/src/components/Connection.js @@ -1,3 +1,20 @@ +/* + * 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 './Connection.scss'; import {Form, OverlayTrigger, Popover} from "react-bootstrap"; diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js index b09dcf3..b468277 100644 --- a/frontend/src/components/ConnectionContent.js +++ b/frontend/src/components/ConnectionContent.js @@ -1,3 +1,20 @@ +/* + * 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 './ConnectionContent.scss'; import {Row} from 'react-bootstrap'; diff --git a/frontend/src/components/ConnectionMatchedRules.js b/frontend/src/components/ConnectionMatchedRules.js index 21f2a92..35643c5 100644 --- a/frontend/src/components/ConnectionMatchedRules.js +++ b/frontend/src/components/ConnectionMatchedRules.js @@ -1,3 +1,20 @@ +/* + * 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 './ConnectionMatchedRules.scss'; import ButtonField from "./fields/ButtonField"; diff --git a/frontend/src/components/MessageAction.js b/frontend/src/components/MessageAction.js index 8f4b031..b94cbb9 100644 --- a/frontend/src/components/MessageAction.js +++ b/frontend/src/components/MessageAction.js @@ -1,3 +1,20 @@ +/* + * 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 './MessageAction.scss'; import {Modal} from "react-bootstrap"; diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js index 9ce2b58..1017a42 100644 --- a/frontend/src/components/Notifications.js +++ b/frontend/src/components/Notifications.js @@ -1,3 +1,20 @@ +/* + * 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 './Notifications.scss'; import dispatcher from "../dispatcher"; diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js index cc32b0f..ffcceae 100644 --- a/frontend/src/components/fields/ButtonField.js +++ b/frontend/src/components/fields/ButtonField.js @@ -1,3 +1,20 @@ +/* + * 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 './ButtonField.scss'; import './common.scss'; diff --git a/frontend/src/components/fields/CheckField.js b/frontend/src/components/fields/CheckField.js index 33f4f83..dd44970 100644 --- a/frontend/src/components/fields/CheckField.js +++ b/frontend/src/components/fields/CheckField.js @@ -1,3 +1,20 @@ +/* + * 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 './CheckField.scss'; import './common.scss'; diff --git a/frontend/src/components/fields/ChoiceField.js b/frontend/src/components/fields/ChoiceField.js index 73e950d..14071c3 100644 --- a/frontend/src/components/fields/ChoiceField.js +++ b/frontend/src/components/fields/ChoiceField.js @@ -1,3 +1,20 @@ +/* + * 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 './ChoiceField.scss'; import './common.scss'; diff --git a/frontend/src/components/fields/InputField.js b/frontend/src/components/fields/InputField.js index 84c981b..80cce3b 100644 --- a/frontend/src/components/fields/InputField.js +++ b/frontend/src/components/fields/InputField.js @@ -1,3 +1,20 @@ +/* + * 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 './InputField.scss'; import './common.scss'; diff --git a/frontend/src/components/fields/TextField.js b/frontend/src/components/fields/TextField.js index de68c21..9237c0c 100644 --- a/frontend/src/components/fields/TextField.js +++ b/frontend/src/components/fields/TextField.js @@ -1,3 +1,20 @@ +/* + * 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 './TextField.scss'; import './common.scss'; diff --git a/frontend/src/components/fields/extensions/ColorField.js b/frontend/src/components/fields/extensions/ColorField.js index 96ebc49..f1c0caf 100644 --- a/frontend/src/components/fields/extensions/ColorField.js +++ b/frontend/src/components/fields/extensions/ColorField.js @@ -1,3 +1,20 @@ +/* + * 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 {OverlayTrigger, Popover} from "react-bootstrap"; import './ColorField.scss'; diff --git a/frontend/src/components/fields/extensions/NumericField.js b/frontend/src/components/fields/extensions/NumericField.js index 19a9e46..d4d027d 100644 --- a/frontend/src/components/fields/extensions/NumericField.js +++ b/frontend/src/components/fields/extensions/NumericField.js @@ -1,3 +1,20 @@ +/* + * 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 InputField from "../InputField"; diff --git a/frontend/src/components/filters/BooleanConnectionsFilter.js b/frontend/src/components/filters/BooleanConnectionsFilter.js index 4c5a78a..a9a420e 100644 --- a/frontend/src/components/filters/BooleanConnectionsFilter.js +++ b/frontend/src/components/filters/BooleanConnectionsFilter.js @@ -1,3 +1,20 @@ +/* + * 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 {withRouter} from "react-router-dom"; import {Redirect} from "react-router"; diff --git a/frontend/src/components/filters/FiltersDefinitions.js b/frontend/src/components/filters/FiltersDefinitions.js index d4f2912..cde3cfb 100644 --- a/frontend/src/components/filters/FiltersDefinitions.js +++ b/frontend/src/components/filters/FiltersDefinitions.js @@ -1,3 +1,20 @@ +/* + * 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 {cleanNumber, validateIpAddress, validateMin, validatePort} from "../../utils"; import StringConnectionsFilter from "./StringConnectionsFilter"; import React from "react"; diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js index 8366189..48affb0 100644 --- a/frontend/src/components/filters/RulesConnectionsFilter.js +++ b/frontend/src/components/filters/RulesConnectionsFilter.js @@ -1,3 +1,20 @@ +/* + * 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 {withRouter} from "react-router-dom"; import {Redirect} from "react-router"; diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js index f463593..a3b45dc 100644 --- a/frontend/src/components/filters/StringConnectionsFilter.js +++ b/frontend/src/components/filters/StringConnectionsFilter.js @@ -1,3 +1,20 @@ +/* + * 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 {withRouter} from "react-router-dom"; import {Redirect} from "react-router"; diff --git a/frontend/src/components/objects/LinkPopover.js b/frontend/src/components/objects/LinkPopover.js index 8768caa..3c5bf67 100644 --- a/frontend/src/components/objects/LinkPopover.js +++ b/frontend/src/components/objects/LinkPopover.js @@ -1,3 +1,20 @@ +/* + * 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 {randomClassName} from "../../utils"; import {OverlayTrigger, Popover} from "react-bootstrap"; diff --git a/frontend/src/components/panels/ConfigurationPane.js b/frontend/src/components/panels/ConfigurationPane.js index 10309f6..9ae2cfb 100644 --- a/frontend/src/components/panels/ConfigurationPane.js +++ b/frontend/src/components/panels/ConfigurationPane.js @@ -1,3 +1,20 @@ +/* + * 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 './ConfigurationPane.scss'; diff --git a/frontend/src/components/panels/MainPane.js b/frontend/src/components/panels/MainPane.js index bd25e0c..d34d58a 100644 --- a/frontend/src/components/panels/MainPane.js +++ b/frontend/src/components/panels/MainPane.js @@ -1,3 +1,20 @@ +/* + * 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 './MainPane.scss'; diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js index 13f7cb3..d5c2225 100644 --- a/frontend/src/components/panels/PcapPane.js +++ b/frontend/src/components/panels/PcapPane.js @@ -1,3 +1,20 @@ +/* + * 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 './PcapPane.scss'; import './common.scss'; diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js index 76f3ac0..9913962 100644 --- a/frontend/src/components/panels/RulePane.js +++ b/frontend/src/components/panels/RulePane.js @@ -1,3 +1,20 @@ +/* + * 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 './RulePane.scss'; diff --git a/frontend/src/components/panels/ServicePane.js b/frontend/src/components/panels/ServicePane.js index 22c6655..fc7004b 100644 --- a/frontend/src/components/panels/ServicePane.js +++ b/frontend/src/components/panels/ServicePane.js @@ -1,3 +1,20 @@ +/* + * 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 './ServicePane.scss'; diff --git a/frontend/src/dispatcher.js b/frontend/src/dispatcher.js index 4b8b5a4..943f7ec 100644 --- a/frontend/src/dispatcher.js +++ b/frontend/src/dispatcher.js @@ -1,3 +1,19 @@ +/* + * 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 . + */ class Dispatcher { diff --git a/frontend/src/index.js b/frontend/src/index.js index beb52ae..e3e48de 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,3 +1,20 @@ +/* + * 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 from 'react'; import ReactDOM from 'react-dom'; import 'bootstrap/dist/css/bootstrap.css'; diff --git a/frontend/src/log.js b/frontend/src/log.js index 0883962..424e1b4 100644 --- a/frontend/src/log.js +++ b/frontend/src/log.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + const log = { debug: (...obj) => console.info(...obj), info: (...obj) => console.info(...obj), diff --git a/frontend/src/notifications.js b/frontend/src/notifications.js index 2a77ffb..f04036d 100644 --- a/frontend/src/notifications.js +++ b/frontend/src/notifications.js @@ -1,3 +1,20 @@ +/* + * 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 log from "./log"; import dispatcher from "./dispatcher"; diff --git a/frontend/src/setupProxy.js b/frontend/src/setupProxy.js index 6f082c8..f2e1c39 100644 --- a/frontend/src/setupProxy.js +++ b/frontend/src/setupProxy.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = function(app) { diff --git a/frontend/src/utils.js b/frontend/src/utils.js index aacc625..f333f0d 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + const timeRegex = /^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/; export function createCurlCommand(subCommand, method = null, json = null, data = null) { diff --git a/frontend/src/validation.js b/frontend/src/validation.js index 7089d7f..87b08de 100644 --- a/frontend/src/validation.js +++ b/frontend/src/validation.js @@ -1,3 +1,19 @@ +/* + * 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 . + */ const validation = { isValidColor: (color) => /^#(?:[0-9a-fA-F]{3}){1,2}$/.test(color), diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js index 4bb9f57..8105117 100644 --- a/frontend/src/views/App.js +++ b/frontend/src/views/App.js @@ -1,3 +1,20 @@ +/* + * 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 './App.scss'; import Header from "./Header"; diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js index e835dcb..b2edd3f 100644 --- a/frontend/src/views/Connections.js +++ b/frontend/src/views/Connections.js @@ -1,3 +1,20 @@ +/* + * 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 './Connections.scss'; import Connection from "../components/Connection"; diff --git a/frontend/src/views/Filters.js b/frontend/src/views/Filters.js index ba7d467..3dd8280 100644 --- a/frontend/src/views/Filters.js +++ b/frontend/src/views/Filters.js @@ -1,3 +1,20 @@ +/* + * 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, Modal, Row, Table} from "react-bootstrap"; import {filtersDefinitions, filtersNames} from "../components/filters/FiltersDefinitions"; @@ -31,7 +48,7 @@ class Filters extends Component { this.checkboxChangesHandler(name, event)} /> + onChange={event => this.checkboxChangesHandler(name, event)}/> {filtersDefinitions[name]} ); @@ -89,7 +106,7 @@ class Filters extends Component { - + ); diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js index f5eff17..2cfe9fb 100644 --- a/frontend/src/views/Header.js +++ b/frontend/src/views/Header.js @@ -1,3 +1,20 @@ +/* + * 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 Typed from 'typed.js'; import './Header.scss'; diff --git a/frontend/src/views/Timeline.js b/frontend/src/views/Timeline.js index 3adbf88..ebe3eb9 100644 --- a/frontend/src/views/Timeline.js +++ b/frontend/src/views/Timeline.js @@ -1,3 +1,20 @@ +/* + * 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 './Timeline.scss'; import { diff --git a/notification_controller.go b/notification_controller.go index 88c9e8c..3fa3c5b 100644 --- a/notification_controller.go +++ b/notification_controller.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/parsers/http_request_parser.go b/parsers/http_request_parser.go index e2224b8..bc98f8f 100644 --- a/parsers/http_request_parser.go +++ b/parsers/http_request_parser.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package parsers import ( diff --git a/parsers/http_response_parser.go b/parsers/http_response_parser.go index 1770116..e5ef1ac 100644 --- a/parsers/http_response_parser.go +++ b/parsers/http_response_parser.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package parsers import ( diff --git a/parsers/parser.go b/parsers/parser.go index 06cc0dc..a29b1ab 100644 --- a/parsers/parser.go +++ b/parsers/parser.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package parsers type Parser interface { diff --git a/parsers/parser_utils.go b/parsers/parser_utils.go index b688262..575b666 100644 --- a/parsers/parser_utils.go +++ b/parsers/parser_utils.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package parsers import ( diff --git a/pcap_importer.go b/pcap_importer.go index 78a5e6c..41ed082 100644 --- a/pcap_importer.go +++ b/pcap_importer.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/pcap_importer_test.go b/pcap_importer_test.go index be09ea9..8940060 100644 --- a/pcap_importer_test.go +++ b/pcap_importer_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/resources_controller.go b/resources_controller.go index 050157a..10b31e3 100644 --- a/resources_controller.go +++ b/resources_controller.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/rules_manager.go b/rules_manager.go index a5dc7ce..636fc74 100644 --- a/rules_manager.go +++ b/rules_manager.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/rules_manager_test.go b/rules_manager_test.go index a2ec501..dded096 100644 --- a/rules_manager_test.go +++ b/rules_manager_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/services_controller.go b/services_controller.go index 9907b5e..e5fa200 100644 --- a/services_controller.go +++ b/services_controller.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/statistics_controller.go b/statistics_controller.go index 65c7d58..006b230 100644 --- a/statistics_controller.go +++ b/statistics_controller.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/storage.go b/storage.go index 0888ce0..f8b7f9c 100644 --- a/storage.go +++ b/storage.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/storage_test.go b/storage_test.go index 4caa30d..dd91e97 100644 --- a/storage_test.go +++ b/storage_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/stream_handler.go b/stream_handler.go index bccdeee..48dba34 100644 --- a/stream_handler.go +++ b/stream_handler.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/stream_handler_test.go b/stream_handler_test.go index 199ae5b..127aa82 100644 --- a/stream_handler_test.go +++ b/stream_handler_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/utils.go b/utils.go index ec5a807..639fd94 100644 --- a/utils.go +++ b/utils.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( -- cgit v1.2.3-70-g09d2 From bc989facca1f3381afed2f7c982da7784fad2327 Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Sun, 11 Oct 2020 21:49:40 +0200 Subject: [inconsistent] SearchOption validation checkpoint --- application_context.go | 2 + application_router.go | 70 +++++++++++++++ connection_streams_controller.go | 1 + search_controller.go | 189 +++++++++++++++++++++++++++++++++++++++ statistics_controller.go | 8 +- storage.go | 41 ++++++--- stream_handler.go | 2 + 7 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 search_controller.go (limited to 'application_context.go') diff --git a/application_context.go b/application_context.go index 9897bb6..fc76d00 100644 --- a/application_context.go +++ b/application_context.go @@ -37,6 +37,7 @@ type ApplicationContext struct { ConnectionsController ConnectionsController ServicesController *ServicesController ConnectionStreamsController ConnectionStreamsController + SearchController *SearchController StatisticsController StatisticsController IsConfigured bool Version string @@ -113,6 +114,7 @@ func (sm *ApplicationContext) configure() { sm.ServicesController = NewServicesController(sm.Storage) sm.ConnectionsController = NewConnectionsController(sm.Storage, sm.ServicesController) sm.ConnectionStreamsController = NewConnectionStreamsController(sm.Storage) + sm.SearchController = NewSearchController(sm.Storage) sm.StatisticsController = NewStatisticsController(sm.Storage) sm.IsConfigured = true } diff --git a/application_router.go b/application_router.go index 89b471b..dc9f9d4 100644 --- a/application_router.go +++ b/application_router.go @@ -22,6 +22,8 @@ import ( "fmt" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/validator/v10" log "github.com/sirupsen/logrus" "net/http" "os" @@ -294,6 +296,74 @@ func CreateApplicationRouter(applicationContext *ApplicationContext, } }) + api.GET("/searches", func(c *gin.Context) { + success(c, applicationContext.SearchController.PerformedSearches()) + }) + + api.POST("/searches/perform", func(c *gin.Context) { + var options SearchOptions + + + parentIsZero := func (fl validator.FieldLevel) bool { + log.Println(fl.FieldName()) + log.Println("noooooo") + return fl.Parent().IsZero() + } + eitherWith := func (fl validator.FieldLevel) bool { + otherField := fl.Parent().FieldByName(fl.Param()) + log.Println(fl.Param()) + log.Println("bbbbbbbbbb") + return (fl.Field().IsZero() && !otherField.IsZero()) || (!fl.Field().IsZero() && otherField.IsZero()) + } + aaa := func (fl validator.FieldLevel) bool { + + log.Println("awww") + return fl.Field().IsZero() + } + + bbb := func (fl validator.FieldLevel) bool { + + log.Println("iiiii") + return true + } + + if validate, ok := binding.Validator.Engine().(*validator.Validate); ok { + if err := validate.RegisterValidation("parent_is_zero", parentIsZero); err != nil { + log.WithError(err).Panic("cannot register 'topzero' validator") + } + if err := validate.RegisterValidation("either_with", eitherWith); err != nil { + log.WithError(err).Panic("cannot register 'either_with' validator") + } + if err := validate.RegisterValidation("aaa", aaa); err != nil { + log.WithError(err).Panic("cannot register 'either_with' validator") + } + if err := validate.RegisterValidation("bbb", bbb); err != nil { + log.WithError(err).Panic("cannot register 'either_with' validator") + } + } else { + log.Panic("cannot ") + } + + + if err := c.ShouldBindJSON(&options); err != nil { + badRequest(c, err) + return + } + + log.Println(options) + + + success(c, "ok") + + + + + + + + //success(c, applicationContext.SearchController.PerformSearch(c, options)) + }) + api.GET("/streams/:id", func(c *gin.Context) { id, err := RowIDFromHex(c.Param("id")) if err != nil { diff --git a/connection_streams_controller.go b/connection_streams_controller.go index 89e484d..eef1a2a 100644 --- a/connection_streams_controller.go +++ b/connection_streams_controller.go @@ -39,6 +39,7 @@ type ConnectionStream struct { FromClient bool `bson:"from_client"` DocumentIndex int `bson:"document_index"` Payload []byte `bson:"payload"` + PayloadString string `bson:"payload_string"` BlocksIndexes []int `bson:"blocks_indexes"` BlocksTimestamps []time.Time `bson:"blocks_timestamps"` BlocksLoss []bool `bson:"blocks_loss"` diff --git a/search_controller.go b/search_controller.go new file mode 100644 index 0000000..ad47dbc --- /dev/null +++ b/search_controller.go @@ -0,0 +1,189 @@ +/* + * 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 . + */ + +package main + +import ( + "context" + log "github.com/sirupsen/logrus" + "strings" + "sync" + "time" +) + +const ( + secondsToNano = 1000 * 1000 * 1000 + maxSearchTimeout = 60 * secondsToNano +) + +type PerformedSearch struct { + ID RowID `bson:"_id" json:"id"` + SearchOptions SearchOptions `bson:"search_options" json:"search_options"` + AffectedConnections []RowID `bson:"affected_connections" json:"affected_connections,omitempty"` + AffectedConnectionsCount int `bson:"affected_connections_count" json:"affected_connections_count"` + StartedAt time.Time `bson:"started_at" json:"started_at"` + FinishedAt time.Time `bson:"finished_at" json:"finished_at"` + UpdatedAt time.Time `bson:"updated_at" json:"updated_at"` + Timeout time.Duration `bson:"timeout" json:"timeout"` +} + +type SearchOptions struct { + TextSearch TextSearch `bson:"text_search" json:"text_search" validate:"either_with=RegexSearch"` + RegexSearch RegexSearch `bson:"regex_search" json:"regex_search" validate:"either_with=TextSearch"` + Timeout time.Duration `bson:"timeout" json:"timeout" binding:"max=60"` +} + +type TextSearch struct { + Terms []string `bson:"terms" json:"terms" binding:"parent_is_zero|either_with=ExactPhrase,isdefault|min=1,dive,min=3"` + ExcludedTerms []string `bson:"excluded_terms" json:"excluded_terms" binding:"required_with=Terms,dive,isdefault|min=1"` + ExactPhrase string `bson:"exact_phrase" json:"exact_phrase" binding:"isdefault|min=3,parent_is_zero|either_with=Terms"` + CaseSensitive bool `bson:"case_sensitive" json:"case_sensitive"` +} + +type RegexSearch struct { + Pattern string `bson:"pattern" json:"pattern" binding:"parent_is_zero|either_with=NotPattern,isdefault|min=3"` + NotPattern string `bson:"not_pattern" json:"not_pattern" binding:"parent_is_zero|either_with=Pattern,isdefault|min=3"` + CaseInsensitive bool `bson:"case_insensitive" json:"case_insensitive"` + MultiLine bool `bson:"multi_line" json:"multi_line"` + IgnoreWhitespaces bool `bson:"ignore_whitespaces" json:"ignore_whitespaces"` + DotCharacter bool `bson:"dot_character" json:"dot_character"` +} + +type SearchController struct { + storage Storage + performedSearches []PerformedSearch + mutex sync.Mutex +} + +func NewSearchController(storage Storage) *SearchController { + var searches []PerformedSearch + if err := storage.Find(Searches).All(&searches); err != nil { + // log.WithError(err).Panic("failed to retrieve performed searches") + } + + return &SearchController{ + storage: storage, + performedSearches: searches, + } +} + +func (sc *SearchController) PerformedSearches() []PerformedSearch { + sc.mutex.Lock() + defer sc.mutex.Unlock() + + return sc.performedSearches +} + +func (sc *SearchController) PerformSearch(c context.Context, options SearchOptions) PerformedSearch { + findQuery := sc.storage.Find(ConnectionStreams).Projection(OrderedDocument{{"connection_id", 1}}).Context(c) + timeout := options.Timeout * secondsToNano + if timeout <= 0 || timeout > maxSearchTimeout { + timeout = maxSearchTimeout + } + findQuery = findQuery.MaxTime(timeout) + + if !options.TextSearch.isZero() { + var text string + if options.TextSearch.ExactPhrase != "" { + text = "\"" + options.TextSearch.ExactPhrase + "\"" + } else { + text = strings.Join(options.TextSearch.Terms, " ") + if options.TextSearch.ExcludedTerms != nil { + text += " -" + strings.Join(options.TextSearch.ExcludedTerms, " -") + } + } + + findQuery = findQuery.Filter(OrderedDocument{{"$text", UnorderedDocument{ + "$search": text, + "$language": "none", + "$caseSensitive": options.TextSearch.CaseSensitive, + "$diacriticSensitive": false, + }}}) + } else { + var regexOptions string + if options.RegexSearch.CaseInsensitive { + regexOptions += "i" + } + if options.RegexSearch.MultiLine { + regexOptions += "m" + } + if options.RegexSearch.IgnoreWhitespaces { + regexOptions += "x" + } + if options.RegexSearch.DotCharacter { + regexOptions += "s" + } + + var regex UnorderedDocument + if options.RegexSearch.Pattern != "" { + regex = UnorderedDocument{"$regex": options.RegexSearch.Pattern, "$options": regexOptions} + } else { + regex = UnorderedDocument{"$not": + UnorderedDocument{"$regex": options.RegexSearch.NotPattern, "$options": regexOptions}} + } + + findQuery = findQuery.Filter(OrderedDocument{{"payload_string", regex}}) + } + + var connections []ConnectionStream + startedAt := time.Now() + if err := findQuery.All(&connections); err != nil { + log.WithError(err).Error("oh no") + } + affectedConnections := uniqueConnectionIds(connections) + + finishedAt := time.Now() + performedSearch := PerformedSearch{ + ID: NewRowID(), + SearchOptions: options, + AffectedConnections: affectedConnections, + AffectedConnectionsCount: len(affectedConnections), + StartedAt: startedAt, + FinishedAt: finishedAt, + UpdatedAt: finishedAt, + Timeout: options.Timeout, + } + if _, err := sc.storage.Insert(Searches).Context(c).One(performedSearch); err != nil { + log.WithError(err).Panic("failed to insert a new performed search") + } + + sc.mutex.Lock() + sc.performedSearches = append([]PerformedSearch{performedSearch}, sc.performedSearches...) + sc.mutex.Unlock() + + return performedSearch +} + +func (sc TextSearch) isZero() bool { + return sc.Terms == nil && sc.ExcludedTerms == nil && sc.ExactPhrase == "" +} + +func (sc RegexSearch) isZero() bool { + return RegexSearch{} == sc +} + +func uniqueConnectionIds(connections []ConnectionStream) []RowID { + keys := make(map[RowID]bool) + var out []RowID + for _, entry := range connections { + if _, value := keys[entry.ConnectionID]; !value { + keys[entry.ConnectionID] = true + out = append(out, entry.ConnectionID) + } + } + return out +} diff --git a/statistics_controller.go b/statistics_controller.go index 006b230..1714c0b 100644 --- a/statistics_controller.go +++ b/statistics_controller.go @@ -26,10 +26,10 @@ import ( type StatisticRecord struct { RangeStart time.Time `json:"range_start" bson:"_id"` - ConnectionsPerService map[uint16]int `json:"connections_per_service" bson:"connections_per_service"` - ClientBytesPerService map[uint16]int `json:"client_bytes_per_service" bson:"client_bytes_per_service"` - ServerBytesPerService map[uint16]int `json:"server_bytes_per_service" bson:"server_bytes_per_service"` - DurationPerService map[uint16]int64 `json:"duration_per_service" bson:"duration_per_service"` + ConnectionsPerService map[uint16]int `json:"connections_per_service,omitempty" bson:"connections_per_service"` + ClientBytesPerService map[uint16]int `json:"client_bytes_per_service,omitempty" bson:"client_bytes_per_service"` + ServerBytesPerService map[uint16]int `json:"server_bytes_per_service,omitempty" bson:"server_bytes_per_service"` + DurationPerService map[uint16]int64 `json:"duration_per_service,omitempty" bson:"duration_per_service"` } type StatisticsFilter struct { diff --git a/storage.go b/storage.go index f8b7f9c..304e88c 100644 --- a/storage.go +++ b/storage.go @@ -25,16 +25,20 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" + "time" ) // Collections names -const Connections = "connections" -const ConnectionStreams = "connection_streams" -const ImportingSessions = "importing_sessions" -const Rules = "rules" -const Settings = "settings" -const Services = "services" -const Statistics = "statistics" +const ( + Connections = "connections" + ConnectionStreams = "connection_streams" + ImportingSessions = "importing_sessions" + Rules = "rules" + Searches = "searches" + Settings = "settings" + Services = "services" + Statistics = "statistics" +) var ZeroRowID [12]byte @@ -73,6 +77,7 @@ func NewMongoStorage(uri string, port int, database string) (*MongoStorage, erro ConnectionStreams: db.Collection(ConnectionStreams), ImportingSessions: db.Collection(ImportingSessions), Rules: db.Collection(Rules), + Searches: db.Collection(Services), Settings: db.Collection(Settings), Services: db.Collection(Services), Statistics: db.Collection(Statistics), @@ -85,9 +90,13 @@ func NewMongoStorage(uri string, port int, database string) (*MongoStorage, erro return nil, err } - if _, err := collections[ConnectionStreams].Indexes().CreateOne(ctx, mongo.IndexModel{ - Keys: bson.D{{"connection_id", -1}}, // descending - Options: options.Index(), + if _, err := collections[ConnectionStreams].Indexes().CreateMany(ctx, []mongo.IndexModel{ + { + Keys: bson.D{{"connection_id", -1}}, // descending + }, + { + Keys: bson.D{{"payload_string", "text"}}, + }, }); err != nil { return nil, err } @@ -277,6 +286,8 @@ type FindOperation interface { Projection(filter OrderedDocument) FindOperation Sort(field string, ascending bool) FindOperation Limit(n int64) FindOperation + Skip(n int64) FindOperation + MaxTime(duration time.Duration) FindOperation First(result interface{}) error All(results interface{}) error } @@ -318,6 +329,16 @@ func (fo MongoFindOperation) Limit(n int64) FindOperation { return fo } +func (fo MongoFindOperation) Skip(n int64) FindOperation { + fo.optFind.SetSkip(n) + return fo +} + +func (fo MongoFindOperation) MaxTime(duration time.Duration) FindOperation { + fo.optFind.SetMaxTime(duration) + return fo +} + func (fo MongoFindOperation) Sort(field string, ascending bool) FindOperation { var sort int if ascending { diff --git a/stream_handler.go b/stream_handler.go index 48dba34..f08bd70 100644 --- a/stream_handler.go +++ b/stream_handler.go @@ -22,6 +22,7 @@ import ( "github.com/flier/gohs/hyperscan" "github.com/google/gopacket/tcpassembly" log "github.com/sirupsen/logrus" + "strings" "time" ) @@ -176,6 +177,7 @@ func (sh *StreamHandler) storageCurrentDocument() { ConnectionID: ZeroRowID, DocumentIndex: len(sh.documentsIDs), Payload: sh.buffer.Bytes(), + PayloadString: strings.ToValidUTF8(string(sh.buffer.Bytes()), ""), BlocksIndexes: sh.indexes, BlocksTimestamps: sh.timestamps, BlocksLoss: sh.lossBlocks, -- cgit v1.2.3-70-g09d2 From 828aeef9a7333aaabeaf9324a85aac56348b3805 Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Sun, 11 Oct 2020 22:50:20 +0200 Subject: Add SearchController --- application_context.go | 4 +-- application_router.go | 71 +++++++++++++---------------------------------- connections_controller.go | 47 +++++++++++++++++++------------ search_controller.go | 44 ++++++++++++++++++++--------- storage.go | 2 +- 5 files changed, 83 insertions(+), 85 deletions(-) (limited to 'application_context.go') diff --git a/application_context.go b/application_context.go index fc76d00..8abb6f4 100644 --- a/application_context.go +++ b/application_context.go @@ -112,9 +112,9 @@ func (sm *ApplicationContext) configure() { sm.RulesManager = rulesManager sm.PcapImporter = NewPcapImporter(sm.Storage, *serverNet, sm.RulesManager) sm.ServicesController = NewServicesController(sm.Storage) - sm.ConnectionsController = NewConnectionsController(sm.Storage, sm.ServicesController) - sm.ConnectionStreamsController = NewConnectionStreamsController(sm.Storage) sm.SearchController = NewSearchController(sm.Storage) + sm.ConnectionsController = NewConnectionsController(sm.Storage, sm.SearchController, sm.ServicesController) + sm.ConnectionStreamsController = NewConnectionStreamsController(sm.Storage) sm.StatisticsController = NewStatisticsController(sm.Storage) sm.IsConfigured = true } diff --git a/application_router.go b/application_router.go index dc9f9d4..656b63e 100644 --- a/application_router.go +++ b/application_router.go @@ -22,8 +22,6 @@ import ( "fmt" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" - "github.com/go-playground/validator/v10" log "github.com/sirupsen/logrus" "net/http" "os" @@ -297,71 +295,42 @@ func CreateApplicationRouter(applicationContext *ApplicationContext, }) api.GET("/searches", func(c *gin.Context) { - success(c, applicationContext.SearchController.PerformedSearches()) + success(c, applicationContext.SearchController.GetPerformedSearches()) }) api.POST("/searches/perform", func(c *gin.Context) { var options SearchOptions - - parentIsZero := func (fl validator.FieldLevel) bool { - log.Println(fl.FieldName()) - log.Println("noooooo") - return fl.Parent().IsZero() - } - eitherWith := func (fl validator.FieldLevel) bool { - otherField := fl.Parent().FieldByName(fl.Param()) - log.Println(fl.Param()) - log.Println("bbbbbbbbbb") - return (fl.Field().IsZero() && !otherField.IsZero()) || (!fl.Field().IsZero() && otherField.IsZero()) - } - aaa := func (fl validator.FieldLevel) bool { - - log.Println("awww") - return fl.Field().IsZero() + if err := c.ShouldBindJSON(&options); err != nil { + badRequest(c, err) + return } - bbb := func (fl validator.FieldLevel) bool { - - log.Println("iiiii") - return true + // stupid checks because validator library is a shit + var badContentError error + if options.TextSearch.isZero() == options.RegexSearch.isZero() { + badContentError = errors.New("specify either 'text_search' or 'regex_search'") } - - if validate, ok := binding.Validator.Engine().(*validator.Validate); ok { - if err := validate.RegisterValidation("parent_is_zero", parentIsZero); err != nil { - log.WithError(err).Panic("cannot register 'topzero' validator") + if !options.TextSearch.isZero() { + if (options.TextSearch.Terms == nil) == (options.TextSearch.ExactPhrase == "") { + badContentError = errors.New("specify either 'terms' or 'exact_phrase'") } - if err := validate.RegisterValidation("either_with", eitherWith); err != nil { - log.WithError(err).Panic("cannot register 'either_with' validator") + if (options.TextSearch.Terms == nil) && (options.TextSearch.ExcludedTerms != nil) { + badContentError = errors.New("'excluded_terms' must be specified only with 'terms'") } - if err := validate.RegisterValidation("aaa", aaa); err != nil { - log.WithError(err).Panic("cannot register 'either_with' validator") - } - if err := validate.RegisterValidation("bbb", bbb); err != nil { - log.WithError(err).Panic("cannot register 'either_with' validator") + } + if !options.RegexSearch.isZero() { + if (options.RegexSearch.Pattern == "") == (options.RegexSearch.NotPattern == "") { + badContentError = errors.New("specify either 'pattern' or 'not_pattern'") } - } else { - log.Panic("cannot ") } - - if err := c.ShouldBindJSON(&options); err != nil { - badRequest(c, err) + if badContentError != nil { + badRequest(c, badContentError) return } - log.Println(options) - - - success(c, "ok") - - - - - - - - //success(c, applicationContext.SearchController.PerformSearch(c, options)) + success(c, applicationContext.SearchController.PerformSearch(c, options)) }) api.GET("/streams/:id", func(c *gin.Context) { diff --git a/connections_controller.go b/connections_controller.go index 30a5ee5..a293a80 100644 --- a/connections_controller.go +++ b/connections_controller.go @@ -48,33 +48,37 @@ type Connection struct { } type ConnectionsFilter struct { - From string `form:"from" binding:"omitempty,hexadecimal,len=24"` - To string `form:"to" binding:"omitempty,hexadecimal,len=24"` - ServicePort uint16 `form:"service_port"` - ClientAddress string `form:"client_address" binding:"omitempty,ip"` - ClientPort uint16 `form:"client_port"` - MinDuration uint `form:"min_duration"` - MaxDuration uint `form:"max_duration" binding:"omitempty,gtefield=MinDuration"` - MinBytes uint `form:"min_bytes"` - MaxBytes uint `form:"max_bytes" binding:"omitempty,gtefield=MinBytes"` - StartedAfter int64 `form:"started_after" ` - StartedBefore int64 `form:"started_before" binding:"omitempty,gtefield=StartedAfter"` - ClosedAfter int64 `form:"closed_after" ` - ClosedBefore int64 `form:"closed_before" binding:"omitempty,gtefield=ClosedAfter"` - Hidden bool `form:"hidden"` - Marked bool `form:"marked"` - MatchedRules []string `form:"matched_rules" binding:"dive,hexadecimal,len=24"` - Limit int64 `form:"limit"` + From string `form:"from" binding:"omitempty,hexadecimal,len=24"` + To string `form:"to" binding:"omitempty,hexadecimal,len=24"` + ServicePort uint16 `form:"service_port"` + ClientAddress string `form:"client_address" binding:"omitempty,ip"` + ClientPort uint16 `form:"client_port"` + MinDuration uint `form:"min_duration"` + MaxDuration uint `form:"max_duration" binding:"omitempty,gtefield=MinDuration"` + MinBytes uint `form:"min_bytes"` + MaxBytes uint `form:"max_bytes" binding:"omitempty,gtefield=MinBytes"` + StartedAfter int64 `form:"started_after" ` + StartedBefore int64 `form:"started_before" binding:"omitempty,gtefield=StartedAfter"` + ClosedAfter int64 `form:"closed_after" ` + ClosedBefore int64 `form:"closed_before" binding:"omitempty,gtefield=ClosedAfter"` + Hidden bool `form:"hidden"` + Marked bool `form:"marked"` + MatchedRules []string `form:"matched_rules" binding:"dive,hexadecimal,len=24"` + PerformedSearch string `form:"performed_search" binding:"omitempty,hexadecimal,len=24"` + Limit int64 `form:"limit"` } type ConnectionsController struct { storage Storage + searchController *SearchController servicesController *ServicesController } -func NewConnectionsController(storage Storage, servicesController *ServicesController) ConnectionsController { +func NewConnectionsController(storage Storage, searchesController *SearchController, + servicesController *ServicesController) ConnectionsController { return ConnectionsController{ storage: storage, + searchController: searchesController, servicesController: servicesController, } } @@ -144,6 +148,13 @@ func (cc ConnectionsController) GetConnections(c context.Context, filter Connect query = query.Filter(OrderedDocument{{"matched_rules", UnorderedDocument{"$all": matchedRules}}}) } + performedSearchID, _ := RowIDFromHex(filter.PerformedSearch) + if !performedSearchID.IsZero() { + performedSearch := cc.searchController.GetPerformedSearch(performedSearchID) + if !performedSearch.ID.IsZero() { + query = query.Filter(OrderedDocument{{"_id", UnorderedDocument{"$in": performedSearch.AffectedConnections}}}) + } + } if filter.Limit > 0 && filter.Limit <= MaxQueryLimit { query = query.Limit(filter.Limit) } else { diff --git a/search_controller.go b/search_controller.go index ad47dbc..723cd93 100644 --- a/search_controller.go +++ b/search_controller.go @@ -26,14 +26,15 @@ import ( ) const ( - secondsToNano = 1000 * 1000 * 1000 - maxSearchTimeout = 60 * secondsToNano + secondsToNano = 1000 * 1000 * 1000 + maxSearchTimeout = 10 * secondsToNano + maxRecentSearches = 200 ) type PerformedSearch struct { ID RowID `bson:"_id" json:"id"` SearchOptions SearchOptions `bson:"search_options" json:"search_options"` - AffectedConnections []RowID `bson:"affected_connections" json:"affected_connections,omitempty"` + AffectedConnections []RowID `bson:"affected_connections" json:"-"` AffectedConnectionsCount int `bson:"affected_connections_count" json:"affected_connections_count"` StartedAt time.Time `bson:"started_at" json:"started_at"` FinishedAt time.Time `bson:"finished_at" json:"finished_at"` @@ -42,21 +43,21 @@ type PerformedSearch struct { } type SearchOptions struct { - TextSearch TextSearch `bson:"text_search" json:"text_search" validate:"either_with=RegexSearch"` - RegexSearch RegexSearch `bson:"regex_search" json:"regex_search" validate:"either_with=TextSearch"` + TextSearch TextSearch `bson:"text_search" json:"text_search"` + RegexSearch RegexSearch `bson:"regex_search" json:"regex_search"` Timeout time.Duration `bson:"timeout" json:"timeout" binding:"max=60"` } type TextSearch struct { - Terms []string `bson:"terms" json:"terms" binding:"parent_is_zero|either_with=ExactPhrase,isdefault|min=1,dive,min=3"` - ExcludedTerms []string `bson:"excluded_terms" json:"excluded_terms" binding:"required_with=Terms,dive,isdefault|min=1"` - ExactPhrase string `bson:"exact_phrase" json:"exact_phrase" binding:"isdefault|min=3,parent_is_zero|either_with=Terms"` + Terms []string `bson:"terms" json:"terms" binding:"isdefault|min=1,dive,min=3"` + ExcludedTerms []string `bson:"excluded_terms" json:"excluded_terms" binding:"isdefault|min=1,dive,min=3"` + ExactPhrase string `bson:"exact_phrase" json:"exact_phrase" binding:"isdefault|min=3"` CaseSensitive bool `bson:"case_sensitive" json:"case_sensitive"` } type RegexSearch struct { - Pattern string `bson:"pattern" json:"pattern" binding:"parent_is_zero|either_with=NotPattern,isdefault|min=3"` - NotPattern string `bson:"not_pattern" json:"not_pattern" binding:"parent_is_zero|either_with=Pattern,isdefault|min=3"` + Pattern string `bson:"pattern" json:"pattern" binding:"isdefault|min=3"` + NotPattern string `bson:"not_pattern" json:"not_pattern" binding:"isdefault|min=3"` CaseInsensitive bool `bson:"case_insensitive" json:"case_insensitive"` MultiLine bool `bson:"multi_line" json:"multi_line"` IgnoreWhitespaces bool `bson:"ignore_whitespaces" json:"ignore_whitespaces"` @@ -71,8 +72,8 @@ type SearchController struct { func NewSearchController(storage Storage) *SearchController { var searches []PerformedSearch - if err := storage.Find(Searches).All(&searches); err != nil { - // log.WithError(err).Panic("failed to retrieve performed searches") + if err := storage.Find(Searches).Limit(maxRecentSearches).All(&searches); err != nil { + log.WithError(err).Panic("failed to retrieve performed searches") } return &SearchController{ @@ -81,13 +82,27 @@ func NewSearchController(storage Storage) *SearchController { } } -func (sc *SearchController) PerformedSearches() []PerformedSearch { +func (sc *SearchController) GetPerformedSearches() []PerformedSearch { sc.mutex.Lock() defer sc.mutex.Unlock() return sc.performedSearches } +func (sc *SearchController) GetPerformedSearch(id RowID) PerformedSearch { + sc.mutex.Lock() + defer sc.mutex.Unlock() + + var performedSearch PerformedSearch + for _, search := range sc.performedSearches { + if search.ID == id { + performedSearch = search + } + } + + return performedSearch +} + func (sc *SearchController) PerformSearch(c context.Context, options SearchOptions) PerformedSearch { findQuery := sc.storage.Find(ConnectionStreams).Projection(OrderedDocument{{"connection_id", 1}}).Context(c) timeout := options.Timeout * secondsToNano @@ -163,6 +178,9 @@ func (sc *SearchController) PerformSearch(c context.Context, options SearchOptio sc.mutex.Lock() sc.performedSearches = append([]PerformedSearch{performedSearch}, sc.performedSearches...) + if len(sc.performedSearches) > maxRecentSearches { + sc.performedSearches = sc.performedSearches[:200] + } sc.mutex.Unlock() return performedSearch diff --git a/storage.go b/storage.go index 304e88c..8505bfe 100644 --- a/storage.go +++ b/storage.go @@ -77,7 +77,7 @@ func NewMongoStorage(uri string, port int, database string) (*MongoStorage, erro ConnectionStreams: db.Collection(ConnectionStreams), ImportingSessions: db.Collection(ImportingSessions), Rules: db.Collection(Rules), - Searches: db.Collection(Services), + Searches: db.Collection(Searches), Settings: db.Collection(Settings), Services: db.Collection(Services), Statistics: db.Collection(Statistics), -- cgit v1.2.3-70-g09d2 From d4bac2d6741f7a291522c29c9ecc87c3e32e21d4 Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Fri, 16 Oct 2020 14:16:44 +0200 Subject: Add notification when pcap have been processed --- application_context.go | 12 +- caronte.go | 2 + frontend/src/components/App.js | 6 +- frontend/src/components/Notifications.js | 17 +- frontend/src/components/Timeline.js | 88 +++++---- frontend/src/components/fields/ButtonField.js | 2 +- frontend/src/components/fields/ChoiceField.js | 10 +- frontend/src/components/objects/Connection.js | 36 +--- .../components/objects/ConnectionMatchedRules.js | 10 +- frontend/src/components/objects/CopyLinkPopover.js | 54 ++++++ frontend/src/components/objects/MessageAction.js | 15 +- frontend/src/components/panels/ConnectionsPane.js | 107 +++++------ frontend/src/components/panels/PcapsPane.js | 40 ++-- frontend/src/components/panels/RulesPane.js | 51 ++--- frontend/src/components/panels/SearchPane.js | 34 ++-- frontend/src/components/panels/ServicesPane.js | 68 ++++--- frontend/src/serviceWorker.js | 208 ++++++++++----------- parsers/http_request_parser.go | 12 +- pcap_importer.go | 39 ++-- 19 files changed, 458 insertions(+), 353 deletions(-) create mode 100644 frontend/src/components/objects/CopyLinkPopover.js (limited to 'application_context.go') diff --git a/application_context.go b/application_context.go index 8abb6f4..0410b88 100644 --- a/application_context.go +++ b/application_context.go @@ -39,6 +39,7 @@ type ApplicationContext struct { ConnectionStreamsController ConnectionStreamsController SearchController *SearchController StatisticsController StatisticsController + NotificationController *NotificationController IsConfigured bool Version string } @@ -70,13 +71,12 @@ func CreateApplicationContext(storage Storage, version string) (*ApplicationCont Version: version, } - applicationContext.configure() return applicationContext, nil } func (sm *ApplicationContext) SetConfig(config Config) { sm.Config = config - sm.configure() + sm.Configure() var upsertResults interface{} if _, err := sm.Storage.Update(Settings).Upsert(&upsertResults). Filter(OrderedDocument{{"_id", "config"}}).One(UnorderedDocument{"config": config}); err != nil { @@ -93,7 +93,11 @@ func (sm *ApplicationContext) SetAccounts(accounts gin.Accounts) { } } -func (sm *ApplicationContext) configure() { +func (sm *ApplicationContext) SetNotificationController(notificationController *NotificationController) { + sm.NotificationController = notificationController +} + +func (sm *ApplicationContext) Configure() { if sm.IsConfigured { return } @@ -110,7 +114,7 @@ func (sm *ApplicationContext) configure() { log.WithError(err).Panic("failed to create a RulesManager") } sm.RulesManager = rulesManager - sm.PcapImporter = NewPcapImporter(sm.Storage, *serverNet, sm.RulesManager) + sm.PcapImporter = NewPcapImporter(sm.Storage, *serverNet, sm.RulesManager, sm.NotificationController) sm.ServicesController = NewServicesController(sm.Storage) sm.SearchController = NewSearchController(sm.Storage) sm.ConnectionsController = NewConnectionsController(sm.Storage, sm.SearchController, sm.ServicesController) diff --git a/caronte.go b/caronte.go index 2d24af6..95f00ef 100644 --- a/caronte.go +++ b/caronte.go @@ -51,10 +51,12 @@ func main() { notificationController := NewNotificationController(applicationContext) go notificationController.Run() + applicationContext.SetNotificationController(notificationController) resourcesController := NewResourcesController(notificationController) go resourcesController.Run() + applicationContext.Configure() applicationRouter := CreateApplicationRouter(applicationContext, notificationController, resourcesController) if applicationRouter.Run(fmt.Sprintf("%s:%v", *bindAddress, *bindPort)) != nil { log.WithError(err).WithFields(logFields).Fatal("failed to create the server") diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js index 0f700db..888ff86 100644 --- a/frontend/src/components/App.js +++ b/frontend/src/components/App.js @@ -15,10 +15,10 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import ConfigurationPage from "./pages/ConfigurationPage"; -import Notifications from "./Notifications"; +import React, {Component} from "react"; import dispatcher from "../dispatcher"; +import Notifications from "./Notifications"; +import ConfigurationPage from "./pages/ConfigurationPage"; import MainPage from "./pages/MainPage"; import ServiceUnavailablePage from "./pages/ServiceUnavailablePage"; diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js index 56a4508..92731d9 100644 --- a/frontend/src/components/Notifications.js +++ b/frontend/src/components/Notifications.js @@ -17,6 +17,7 @@ import React, {Component} from "react"; import dispatcher from "../dispatcher"; +import {randomClassName} from "../utils"; import "./Notifications.scss"; const _ = require("lodash"); @@ -30,9 +31,15 @@ class Notifications extends Component { }; componentDidMount() { - dispatcher.register("notifications", n => this.notificationHandler(n)); + dispatcher.register("notifications", this.handleNotifications); } + componentWillUnmount() { + dispatcher.unregister(this.handleNotifications); + } + + handleNotifications = (n) => this.notificationHandler(n); + notificationHandler = (n) => { switch (n.event) { case "connected": @@ -54,6 +61,11 @@ class Notifications extends Component { n.description = `existing rule updated: ${n.message["name"]}`; n.variant = "blue"; return this.pushNotification(n); + case "pcap.completed": + n.title = "new pcap analyzed"; + n.description = `${n.message["processed_packets"]} packets processed`; + n.variant = "blue"; + return this.pushNotification(n); default: return; } @@ -61,6 +73,7 @@ class Notifications extends Component { pushNotification = (notification) => { const notifications = this.state.notifications; + notification.id = randomClassName(); notifications.push(notification); this.setState({notifications}); setTimeout(() => { @@ -103,7 +116,7 @@ class Notifications extends Component { if (n.variant) { notificationClassnames[`notification-${n.variant}`] = true; } - return
+ return

{n.title}

{n.description}
; diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js index 8d1fd40..94fa4d0 100644 --- a/frontend/src/components/Timeline.js +++ b/frontend/src/components/Timeline.js @@ -15,8 +15,9 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './Timeline.scss'; +import {TimeRange, TimeSeries} from "pondjs"; +import React, {Component} from "react"; +import {withRouter} from "react-router-dom"; import { ChartContainer, ChartRow, @@ -27,15 +28,14 @@ import { styler, YAxis } from "react-timeseries-charts"; -import {TimeRange, TimeSeries} from "pondjs"; import backend from "../backend"; -import ChoiceField from "./fields/ChoiceField"; -import {withRouter} from "react-router-dom"; -import log from "../log"; import dispatcher from "../dispatcher"; +import log from "../log"; +import ChoiceField from "./fields/ChoiceField"; +import "./Timeline.scss"; const minutes = 60 * 1000; -const classNames = require('classnames'); +const classNames = require("classnames"); const leftSelectionPaddingMultiplier = 24; const rightSelectionPaddingMultiplier = 8; @@ -61,42 +61,17 @@ class Timeline extends Component { }); this.loadStatistics(this.state.metric).then(() => log.debug("Statistics loaded after mount")); - - this.connectionsFiltersCallback = (payload) => { - if ("service_port" in payload && this.state.servicePortFilter !== payload["service_port"]) { - this.setState({servicePortFilter: payload["service_port"]}); - this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after service port changed")); - } - if ("matched_rules" in payload && this.state.matchedRulesFilter !== payload["matched_rules"]) { - this.setState({matchedRulesFilter: payload["matched_rules"]}); - this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after matched rules changed")); - } - }; - dispatcher.register("connections_filters", this.connectionsFiltersCallback); - - dispatcher.register("connection_updates", (payload) => { - this.setState({ - selection: new TimeRange(payload.from, payload.to), - }); - this.adjustSelection(); - }); - - dispatcher.register("notifications", (payload) => { - if (payload.event === "services.edit" && this.state.metric !== "matched_rules") { - this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after services updates")); - } else if (payload.event.startsWith("rules") && this.state.metric === "matched_rules") { - this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after rules updates")); - } - }); - - dispatcher.register("pulse_timeline", (payload) => { - this.setState({pulseTimeline: true}); - setTimeout(() => this.setState({pulseTimeline: false}), payload.duration); - }); + dispatcher.register("connections_filters", this.handleConnectionsFiltersCallback); + dispatcher.register("connection_updates", this.handleConnectionUpdates); + dispatcher.register("notifications", this.handleNotifications); + dispatcher.register("pulse_timeline", this.handlePulseTimeline); } componentWillUnmount() { - dispatcher.unregister(this.connectionsFiltersCallback); + dispatcher.unregister(this.handleConnectionsFiltersCallback); + dispatcher.unregister(this.handleConnectionUpdates); + dispatcher.unregister(this.handleNotifications); + dispatcher.unregister(this.handlePulseTimeline); } loadStatistics = async (metric) => { @@ -217,6 +192,39 @@ class Timeline extends Component { }, 1000); }; + handleConnectionsFiltersCallback = (payload) => { + if ("service_port" in payload && this.state.servicePortFilter !== payload["service_port"]) { + this.setState({servicePortFilter: payload["service_port"]}); + this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after service port changed")); + } + if ("matched_rules" in payload && this.state.matchedRulesFilter !== payload["matched_rules"]) { + this.setState({matchedRulesFilter: payload["matched_rules"]}); + this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after matched rules changed")); + } + }; + + handleConnectionUpdates = (payload) => { + this.setState({ + selection: new TimeRange(payload.from, payload.to), + }); + this.adjustSelection(); + }; + + handleNotifications = (payload) => { + if (payload.event === "services.edit" && this.state.metric !== "matched_rules") { + this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after services updates")); + } else if (payload.event.startsWith("rules") && this.state.metric === "matched_rules") { + this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after rules updates")); + } else if (payload.event === "pcap.completed") { + this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after pcap processed")); + } + }; + + handlePulseTimeline = (payload) => { + this.setState({pulseTimeline: true}); + setTimeout(() => this.setState({pulseTimeline: false}), payload.duration); + }; + adjustSelection = () => { const seriesRange = this.state.series.range(); const selection = this.state.selection; diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js index de747a5..15ef179 100644 --- a/frontend/src/components/fields/ButtonField.js +++ b/frontend/src/components/fields/ButtonField.js @@ -58,7 +58,7 @@ class ButtonField extends Component {
+ onClick={handler} style={buttonStyle} disabled={this.props.disabled}>{this.props.name}
); } diff --git a/frontend/src/components/fields/ChoiceField.js b/frontend/src/components/fields/ChoiceField.js index 14071c3..7e97d89 100644 --- a/frontend/src/components/fields/ChoiceField.js +++ b/frontend/src/components/fields/ChoiceField.js @@ -15,12 +15,12 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './ChoiceField.scss'; -import './common.scss'; +import React, {Component} from "react"; import {randomClassName} from "../../utils"; +import "./ChoiceField.scss"; +import "./common.scss"; -const classNames = require('classnames'); +const classNames = require("classnames"); class ChoiceField extends Component { @@ -67,7 +67,7 @@ class ChoiceField extends Component { } return ( -
{!inline && name && }
{ if (name === "hide") { const enabled = !this.props.data.hidden; backend.post(`/api/connections/${this.props.data.id}/${enabled ? "hide" : "show"}`) @@ -57,13 +50,7 @@ class Connection extends Component { this.setState({update: true}); }); } - if (name === "copy") { - this.copyTextarea.current.select(); - document.execCommand("copy"); - this.setState({copiedMessage: true}); - setTimeout(() => this.setState({copiedMessage: false}), 3000); - } - } + }; render() { let conn = this.props.data; @@ -88,12 +75,6 @@ class Connection extends Component { {conn.comment && }
; - const copyPopoverContent =
- {this.state.copiedMessage ? Copied! : - Click to copy the connection id} - -
; - return ( 0})}> @@ -121,9 +102,8 @@ class Connection extends Component { this.handleAction("comment")}>@} content={commentPopoverContent} placement="right"/> - this.handleAction("copy")}>#} - content={copyPopoverContent} placement="right"/> + ); diff --git a/frontend/src/components/objects/ConnectionMatchedRules.js b/frontend/src/components/objects/ConnectionMatchedRules.js index 92bde49..cfd1254 100644 --- a/frontend/src/components/objects/ConnectionMatchedRules.js +++ b/frontend/src/components/objects/ConnectionMatchedRules.js @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './ConnectionMatchedRules.scss'; -import ButtonField from "../fields/ButtonField"; -import dispatcher from "../../dispatcher"; +import React, {Component} from "react"; import {withRouter} from "react-router-dom"; +import dispatcher from "../../dispatcher"; +import ButtonField from "../fields/ButtonField"; +import "./ConnectionMatchedRules.scss"; class ConnectionMatchedRules extends Component { @@ -28,7 +28,7 @@ class ConnectionMatchedRules extends Component { const rules = params.getAll("matched_rules"); if (!rules.includes(id)) { rules.push(id); - dispatcher.dispatch("connections_filters",{"matched_rules": rules}); + dispatcher.dispatch("connections_filters", {"matched_rules": rules}); } }; diff --git a/frontend/src/components/objects/CopyLinkPopover.js b/frontend/src/components/objects/CopyLinkPopover.js new file mode 100644 index 0000000..fa9266f --- /dev/null +++ b/frontend/src/components/objects/CopyLinkPopover.js @@ -0,0 +1,54 @@ +/* + * 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 TextField from "../fields/TextField"; +import LinkPopover from "./LinkPopover"; + +class CopyLinkPopover extends Component { + + state = {}; + + constructor(props) { + super(props); + + this.copyTextarea = React.createRef(); + } + + handleClick = () => { + this.copyTextarea.current.select(); + document.execCommand("copy"); + this.setState({copiedMessage: true}); + setTimeout(() => this.setState({copiedMessage: false}), 3000); + }; + + render() { + const copyPopoverContent =
+ {this.state.copiedMessage ? Copied! : + Click to copy} + +
; + + return ( + {this.props.text}} + content={copyPopoverContent} placement="right"/> + ); + } +} + +export default CopyLinkPopover; diff --git a/frontend/src/components/objects/MessageAction.js b/frontend/src/components/objects/MessageAction.js index 2b46320..e0c96e8 100644 --- a/frontend/src/components/objects/MessageAction.js +++ b/frontend/src/components/objects/MessageAction.js @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './MessageAction.scss'; +import React, {Component} from "react"; import {Modal} from "react-bootstrap"; -import TextField from "../fields/TextField"; import ButtonField from "../fields/ButtonField"; +import TextField from "../fields/TextField"; +import "./MessageAction.scss"; class MessageAction extends Component { @@ -34,7 +34,7 @@ class MessageAction extends Component { copyActionValue() { this.actionValue.current.select(); - document.execCommand('copy'); + document.execCommand("copy"); this.setState({copyButtonText: "copied!"}); setTimeout(() => this.setState({copyButtonText: "copy"}), 3000); } @@ -54,11 +54,12 @@ class MessageAction extends Component { - + - - + + ); diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js index ea47059..23c6114 100644 --- a/frontend/src/components/panels/ConnectionsPane.js +++ b/frontend/src/components/panels/ConnectionsPane.js @@ -15,20 +15,20 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './ConnectionsPane.scss'; -import Connection from "../objects/Connection"; -import Table from 'react-bootstrap/Table'; +import React, {Component} from "react"; +import Table from "react-bootstrap/Table"; +import {Redirect} from "react-router"; import {withRouter} from "react-router-dom"; import backend from "../../backend"; -import ConnectionMatchedRules from "../objects/ConnectionMatchedRules"; -import log from "../../log"; -import ButtonField from "../fields/ButtonField"; import dispatcher from "../../dispatcher"; -import {Redirect} from "react-router"; +import log from "../../log"; import {updateParams} from "../../utils"; +import ButtonField from "../fields/ButtonField"; +import Connection from "../objects/Connection"; +import ConnectionMatchedRules from "../objects/ConnectionMatchedRules"; +import "./ConnectionsPane.scss"; -const classNames = require('classnames'); +const classNames = require("classnames"); class ConnectionsPane extends Component { @@ -67,55 +67,56 @@ class ConnectionsPane extends Component { this.loadConnections(additionalParams, urlParams, true).then(() => log.debug("Connections loaded")); - this.connectionsFiltersCallback = payload => { - const newParams = updateParams(this.state.urlParams, payload); - if (this.state.urlParams.toString() === newParams.toString()) { - return; - } - - log.debug("Update following url params:", payload); - this.queryStringRedirect = true; - this.setState({urlParams: newParams}); - - this.loadConnections({limit: this.queryLimit}, newParams) - .then(() => log.info("ConnectionsPane reloaded after query string update")); - }; - dispatcher.register("connections_filters", this.connectionsFiltersCallback); - - this.timelineUpdatesCallback = payload => { - this.connectionsListRef.current.scrollTop = 0; - this.loadConnections({ - "started_after": Math.round(payload.from.getTime() / 1000), - "started_before": Math.round(payload.to.getTime() / 1000), - limit: this.maxConnections - }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`)); - }; - dispatcher.register("timeline_updates", this.timelineUpdatesCallback); - - this.notificationsCallback = payload => { - if (payload.event === "rules.new" || payload.event === "rules.edit") { - this.loadRules().then(() => log.debug("Loaded connection rules after notification update")); - } - if (payload.event === "services.edit") { - this.loadServices().then(() => log.debug("Services reloaded after notification update")); - } - }; - dispatcher.register("notifications", this.notificationsCallback); - - this.pulseConnectionsViewCallback = payload => { - this.setState({pulseConnectionsView: true}); - setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration); - }; - dispatcher.register("pulse_connections_view", this.pulseConnectionsViewCallback); + dispatcher.register("connections_filters", this.handleConnectionsFilters); + dispatcher.register("timeline_updates", this.handleTimelineUpdates); + dispatcher.register("notifications", this.handleNotifications); + dispatcher.register("pulse_connections_view", this.handlePulseConnectionsView); } componentWillUnmount() { - dispatcher.unregister(this.timelineUpdatesCallback); - dispatcher.unregister(this.notificationsCallback); - dispatcher.unregister(this.pulseConnectionsViewCallback); - dispatcher.unregister(this.connectionsFiltersCallback); + dispatcher.unregister(this.handleConnectionsFilters); + dispatcher.unregister(this.handleTimelineUpdates); + dispatcher.unregister(this.handleNotifications); + dispatcher.unregister(this.handlePulseConnectionsView); } + handleConnectionsFilters = (payload) => { + const newParams = updateParams(this.state.urlParams, payload); + if (this.state.urlParams.toString() === newParams.toString()) { + return; + } + + log.debug("Update following url params:", payload); + this.queryStringRedirect = true; + this.setState({urlParams: newParams}); + + this.loadConnections({limit: this.queryLimit}, newParams) + .then(() => log.info("ConnectionsPane reloaded after query string update")); + }; + + handleTimelineUpdates = (payload) => { + this.connectionsListRef.current.scrollTop = 0; + this.loadConnections({ + "started_after": Math.round(payload.from.getTime() / 1000), + "started_before": Math.round(payload.to.getTime() / 1000), + limit: this.maxConnections + }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`)); + }; + + handleNotifications = (payload) => { + if (payload.event === "rules.new" || payload.event === "rules.edit") { + this.loadRules().then(() => log.debug("Loaded connection rules after notification update")); + } + if (payload.event === "services.edit") { + this.loadServices().then(() => log.debug("Services reloaded after notification update")); + } + }; + + handlePulseConnectionsView = (payload) => { + this.setState({pulseConnectionsView: true}); + setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration); + }; + connectionSelected = (c) => { this.connectionSelectedRedirect = true; this.setState({selected: c.id}); diff --git a/frontend/src/components/panels/PcapsPane.js b/frontend/src/components/panels/PcapsPane.js index fd3db75..64e7804 100644 --- a/frontend/src/components/panels/PcapsPane.js +++ b/frontend/src/components/panels/PcapsPane.js @@ -24,6 +24,7 @@ import ButtonField from "../fields/ButtonField"; import CheckField from "../fields/CheckField"; import InputField from "../fields/InputField"; import TextField from "../fields/TextField"; +import CopyLinkPopover from "../objects/CopyLinkPopover"; import LinkPopover from "../objects/LinkPopover"; import "./common.scss"; import "./PcapsPane.scss"; @@ -44,16 +45,20 @@ class PcapsPane extends Component { componentDidMount() { this.loadSessions(); - - dispatcher.register("notifications", (payload) => { - if (payload.event === "pcap.upload" || payload.event === "pcap.file") { - this.loadSessions(); - } - }); - + dispatcher.register("notifications", this.handleNotifications); document.title = "caronte:~/pcaps$"; } + componentWillUnmount() { + dispatcher.unregister(this.handleNotifications); + } + + handleNotifications = (payload) => { + if (payload.event.startsWith("pcap")) { + this.loadSessions(); + } + }; + loadSessions = () => { backend.get("/api/pcap/sessions") .then((res) => this.setState({sessions: res.json, sessionsStatusCode: res.status})) @@ -130,10 +135,19 @@ class PcapsPane extends Component { }; render() { - let sessions = this.state.sessions.map((s) => - - {s["id"].substring(0, 8)} - {dateTimeToTime(s["started_at"])} + let sessions = this.state.sessions.map((s) => { + const startedAt = new Date(s["started_at"]); + const completedAt = new Date(s["completed_at"]); + let timeInfo =
+ Started at {startedAt.toLocaleDateString() + " " + startedAt.toLocaleTimeString()}
+ Completed at {completedAt.toLocaleDateString() + " " + completedAt.toLocaleTimeString()} +
; + + return + + + + {durationBetween(s["started_at"], s["completed_at"])} {formatSize(s["size"])} {s["processed_packets"]} @@ -143,8 +157,8 @@ class PcapsPane extends Component { placement="left"/> download - - ); + ; + }); const handleUploadFileChange = (file) => { this.setState({ diff --git a/frontend/src/components/panels/RulesPane.js b/frontend/src/components/panels/RulesPane.js index a66cde7..d872b47 100644 --- a/frontend/src/components/panels/RulesPane.js +++ b/frontend/src/components/panels/RulesPane.js @@ -15,26 +15,26 @@ * 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 React, {Component} from "react"; import {Col, Container, Row} from "react-bootstrap"; -import InputField from "../fields/InputField"; -import CheckField from "../fields/CheckField"; -import TextField from "../fields/TextField"; +import Table from "react-bootstrap/Table"; 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 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 {randomClassName} from "../../utils"; -import dispatcher from "../../dispatcher"; +import "./common.scss"; +import "./RulesPane.scss"; -const classNames = require('classnames'); -const _ = require('lodash'); +const classNames = require("classnames"); +const _ = require("lodash"); class RulesPane extends Component { @@ -88,15 +88,20 @@ class RulesPane extends Component { this.reset(); this.loadRules(); - dispatcher.register("notifications", payload => { - if (payload.event === "rules.new" || payload.event === "rules.edit") { - 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)})); @@ -249,7 +254,7 @@ class RulesPane extends Component { 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"]} @@ -260,7 +265,7 @@ class RulesPane extends Component { rule.patterns.concat(this.state.newPattern) : rule.patterns ).map(p => p === pattern ? - + { diff --git a/frontend/src/components/panels/SearchPane.js b/frontend/src/components/panels/SearchPane.js index d3c0c8b..d36e85e 100644 --- a/frontend/src/components/panels/SearchPane.js +++ b/frontend/src/components/panels/SearchPane.js @@ -60,15 +60,14 @@ class SearchPane extends Component { this.reset(); this.loadSearches(); - dispatcher.register("notifications", payload => { - if (payload.event === "searches.new") { - this.loadSearches(); - } - }); - + dispatcher.register("notifications", this.handleNotification); document.title = "caronte:~/searches$"; } + componentWillUnmount() { + dispatcher.unregister(this.handleNotification); + } + loadSearches = () => { backend.get("/api/searches") .then(res => this.setState({searches: res.json, searchesStatusCode: res.status})) @@ -77,14 +76,18 @@ class SearchPane extends Component { performSearch = () => { const options = this.state.currentSearchOptions; + this.setState({loading: true}); if (this.validateSearch(options)) { backend.post("/api/searches/perform", options).then(res => { this.reset(); - this.setState({searchStatusCode: res.status}); + this.setState({searchStatusCode: res.status, loading: false}); this.loadSearches(); this.viewSearch(res.json.id); }).catch(res => { - this.setState({searchStatusCode: res.status, searchResponse: JSON.stringify(res.json)}); + this.setState({ + searchStatusCode: res.status, searchResponse: JSON.stringify(res.json), + loading: false + }); }); } }; @@ -156,6 +159,12 @@ class SearchPane extends Component { dispatcher.dispatch("connections_filters", {"performed_search": searchId}); }; + handleNotification = (payload) => { + if (payload.event === "searches.new") { + this.loadSearches(); + } + }; + render() { const options = this.state.currentSearchOptions; @@ -263,7 +272,8 @@ class SearchPane extends Component { onChange={v => this.updateParam(s => s["regex_search"]["not_pattern"] = v)}/>
- this.updateParam(s => s["regex_search"]["case_insensitive"] = v)}/>
- - + +
diff --git a/frontend/src/components/panels/ServicesPane.js b/frontend/src/components/panels/ServicesPane.js index bc82356..48d9e29 100644 --- a/frontend/src/components/panels/ServicesPane.js +++ b/frontend/src/components/panels/ServicesPane.js @@ -15,24 +15,24 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './common.scss'; -import './ServicesPane.scss'; -import Table from "react-bootstrap/Table"; +import React, {Component} from "react"; import {Col, Container, Row} from "react-bootstrap"; -import InputField from "../fields/InputField"; -import TextField from "../fields/TextField"; +import Table from "react-bootstrap/Table"; import backend from "../../backend"; -import NumericField from "../fields/extensions/NumericField"; -import ColorField from "../fields/extensions/ColorField"; -import ButtonField from "../fields/ButtonField"; +import dispatcher from "../../dispatcher"; +import {createCurlCommand} from "../../utils"; import validation from "../../validation"; +import ButtonField from "../fields/ButtonField"; +import ColorField from "../fields/extensions/ColorField"; +import NumericField from "../fields/extensions/NumericField"; +import InputField from "../fields/InputField"; +import TextField from "../fields/TextField"; import LinkPopover from "../objects/LinkPopover"; -import {createCurlCommand} from "../../utils"; -import dispatcher from "../../dispatcher"; +import "./common.scss"; +import "./ServicesPane.scss"; -const classNames = require('classnames'); -const _ = require('lodash'); +const classNames = require("classnames"); +const _ = require("lodash"); class ServicesPane extends Component { @@ -52,15 +52,20 @@ class ServicesPane extends Component { this.reset(); this.loadServices(); - dispatcher.register("notifications", payload => { - if (payload.event === "services.edit") { - this.loadServices(); - } - }); - + dispatcher.register("notifications", this.handleNotifications); document.title = "caronte:~/services$"; } + componentWillUnmount() { + dispatcher.unregister(this.handleNotifications); + } + + handleNotifications = (payload) => { + if (payload.event === "services.edit") { + this.loadServices(); + } + }; + loadServices = () => { backend.get("/api/services") .then(res => this.setState({services: Object.values(res.json), servicesStatusCode: res.status})) @@ -125,10 +130,10 @@ class ServicesPane extends Component { { this.reset(); this.setState({isUpdate: true, currentService: _.cloneDeep(s)}); - }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port })}> + }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port})}> {s["port"]} {s["name"]} - + {s["notes"]} ); @@ -141,9 +146,9 @@ class ServicesPane extends Component {
GET /api/services {this.state.servicesStatusCode && - } + }
@@ -170,7 +175,7 @@ class ServicesPane extends Component { PUT /api/services + placement="left"/>
@@ -179,17 +184,17 @@ class ServicesPane extends Component { this.updateParam((s) => s.port = v)} - min={0} max={65565} error={this.state.servicePortError} /> + min={0} max={65565} error={this.state.servicePortError}/> this.updateParam((s) => s.name = v)} - error={this.state.serviceNameError} /> + error={this.state.serviceNameError}/> this.updateParam((s) => s.color = v)} /> + onChange={(v) => this.updateParam((s) => s.color = v)}/> this.updateParam((s) => s.notes = v)} /> + onChange={(v) => this.updateParam((s) => s.notes = v)}/> @@ -199,8 +204,9 @@ class ServicesPane extends Component {
{} - +
diff --git a/frontend/src/serviceWorker.js b/frontend/src/serviceWorker.js index c633a91..a1f0ba8 100644 --- a/frontend/src/serviceWorker.js +++ b/frontend/src/serviceWorker.js @@ -11,131 +11,131 @@ // opt-in, read https://bit.ly/CRA-PWA const isLocalhost = Boolean( - window.location.hostname === 'localhost' || + window.location.hostname === "localhost" || // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || + window.location.hostname === "[::1]" || // 127.0.0.0/8 are considered localhost for IPv4. window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ ) ); export function register(config) { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } + if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } - window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + window.addEventListener("load", () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' - ); + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + "This web app is being served cache-first by a service " + + "worker. To learn more, visit https://bit.ly/CRA-PWA" + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } + } } function registerValidSW(swUrl, config) { - navigator.serviceWorker - .register(swUrl) - .then((registration) => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' - ); + navigator.serviceWorker + .register(swUrl) + .then((registration) => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === "installed") { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + "New content is available and will be used when all " + + "tabs for this page are closed. See https://bit.ly/CRA-PWA." + ); - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log("Content is cached for offline use."); - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch((error) => { - console.error('Error during service worker registration:', error); - }); + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch((error) => { + console.error("Error during service worker registration:", error); + }); } function checkValidServiceWorker(swUrl, config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl, { - headers: { 'Service-Worker': 'script' }, - }) - .then((response) => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then((registration) => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl, { + headers: {"Service-Worker": "script"}, }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); + .then((response) => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get("content-type"); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf("javascript") === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then((registration) => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + "No internet connection found. App is running in offline mode." + ); + }); } export function unregister() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready - .then((registration) => { - registration.unregister(); - }) - .catch((error) => { - console.error(error.message); - }); - } + if ("serviceWorker" in navigator) { + navigator.serviceWorker.ready + .then((registration) => { + registration.unregister(); + }) + .catch((error) => { + console.error(error.message); + }); + } } diff --git a/parsers/http_request_parser.go b/parsers/http_request_parser.go index bc98f8f..56093c9 100644 --- a/parsers/http_request_parser.go +++ b/parsers/http_request_parser.go @@ -82,7 +82,7 @@ func (p HttpRequestParser) TryParse(content []byte) Metadata { Trailer: JoinArrayMap(request.Trailer), Reproducers: HttpRequestMetadataReproducers{ CurlCommand: curlCommand(content), - RequestsCode: requestsCode(request), + RequestsCode: requestsCode(request, body), FetchRequest: fetchRequest(request, body), }, } @@ -99,19 +99,15 @@ func curlCommand(content []byte) string { } } -func requestsCode(request *http.Request) string { +func requestsCode(request *http.Request, body string) string { var b strings.Builder - var params string - if request.Form != nil { - params = toJson(JoinArrayMap(request.PostForm)) - } headers := toJson(JoinArrayMap(request.Header)) cookies := toJson(CookiesMap(request.Cookies())) b.WriteString("import requests\n\nresponse = requests." + strings.ToLower(request.Method) + "(") b.WriteString("\"" + request.URL.String() + "\"") - if params != "" { - b.WriteString(", data = " + params) + if body != "" { + b.WriteString(", data = \"" + strings.Replace(body, "\"", "\\\"", -1) + "\"") } if headers != "" { b.WriteString(", headers = " + headers) diff --git a/pcap_importer.go b/pcap_importer.go index 41ed082..1c80b3f 100644 --- a/pcap_importer.go +++ b/pcap_importer.go @@ -29,6 +29,7 @@ import ( "os" "path" "path/filepath" + "sort" "sync" "time" ) @@ -39,13 +40,14 @@ const initialAssemblerPoolSize = 16 const importUpdateProgressInterval = 100 * time.Millisecond type PcapImporter struct { - storage Storage - streamPool *tcpassembly.StreamPool - assemblers []*tcpassembly.Assembler - sessions map[string]ImportingSession - mAssemblers sync.Mutex - mSessions sync.Mutex - serverNet net.IPNet + storage Storage + streamPool *tcpassembly.StreamPool + assemblers []*tcpassembly.Assembler + sessions map[string]ImportingSession + mAssemblers sync.Mutex + mSessions sync.Mutex + serverNet net.IPNet + notificationController *NotificationController } type ImportingSession struct { @@ -63,7 +65,8 @@ type ImportingSession struct { type flowCount [2]int -func NewPcapImporter(storage Storage, serverNet net.IPNet, rulesManager RulesManager) *PcapImporter { +func NewPcapImporter(storage Storage, serverNet net.IPNet, rulesManager RulesManager, + notificationController *NotificationController) *PcapImporter { streamPool := tcpassembly.NewStreamPool(NewBiDirectionalStreamFactory(storage, serverNet, rulesManager)) var result []ImportingSession @@ -76,13 +79,14 @@ func NewPcapImporter(storage Storage, serverNet net.IPNet, rulesManager RulesMan } return &PcapImporter{ - storage: storage, - streamPool: streamPool, - assemblers: make([]*tcpassembly.Assembler, 0, initialAssemblerPoolSize), - sessions: sessions, - mAssemblers: sync.Mutex{}, - mSessions: sync.Mutex{}, - serverNet: serverNet, + storage: storage, + streamPool: streamPool, + assemblers: make([]*tcpassembly.Assembler, 0, initialAssemblerPoolSize), + sessions: sessions, + mAssemblers: sync.Mutex{}, + mSessions: sync.Mutex{}, + serverNet: serverNet, + notificationController: notificationController, } } @@ -136,6 +140,9 @@ func (pi *PcapImporter) GetSessions() []ImportingSession { for _, session := range pi.sessions { sessions = append(sessions, session) } + sort.Slice(sessions, func(i, j int) bool { + return sessions[i].StartedAt.Before(sessions[j].StartedAt) + }) pi.mSessions.Unlock() return sessions } @@ -202,6 +209,8 @@ func (pi *PcapImporter) parsePcap(session ImportingSession, fileName string, flu handle.Close() pi.releaseAssembler(assembler) pi.progressUpdate(session, fileName, true, "") + pi.notificationController.Notify("pcap.completed", session) + return } -- cgit v1.2.3-70-g09d2