diff --git a/Dockerfile b/Dockerfile index 42ef1149..ba4ea972 100644 --- a/Dockerfile +++ b/Dockerfile @@ -80,6 +80,4 @@ ENV WAZUH_VERSION=$WAZUH_VERSION USER socore EXPOSE 9822/tcp -VOLUME /opt/sensoroni/jobs -VOLUME /opt/sensoroni/logs ENTRYPOINT ["/opt/sensoroni/sensoroni"] diff --git a/Dockerfile.kratos b/Dockerfile.kratos index 71e74c42..54b92fc5 100644 --- a/Dockerfile.kratos +++ b/Dockerfile.kratos @@ -60,6 +60,4 @@ USER kratos EXPOSE 4433 EXPOSE 4434 -VOLUME ["/kratos-conf", "/kratos-data", "/kratos-log"] - ENTRYPOINT ["/start-kratos.sh"] diff --git a/html/js/routes/hunt.js b/html/js/routes/hunt.js index d1943fdf..e00b29c3 100644 --- a/html/js/routes/hunt.js +++ b/html/js/routes/hunt.js @@ -79,7 +79,7 @@ const huntComponent = { eventLimitOptions: [10,25,50,100,200,500,1000,2000,5000], eventLimit: 100, eventData: [], - eventFilter: '', + eventFilter: '', eventHeaders: [], eventPage: 1, sortBy: 'soc_timestamp', @@ -223,7 +223,7 @@ const huntComponent = { } if (this.$route.query.t) { - // This page was either refreshed, or opened from an existing hunt hyperlink, + // This page was either refreshed, or opened from an existing hunt hyperlink, // so switch to absolute time since the URL has the absolute time defined. this.relativeTimeEnabled = false; this.dateRange = this.$route.query.t; @@ -234,7 +234,7 @@ const huntComponent = { this.$root.stopLoading(); if (!this.parseUrlParameters()) return; - + if (this.$route.query.q || (this.shouldAutohunt() && this.query)) { this.hunt(true); } @@ -276,9 +276,9 @@ const huntComponent = { this.huntPending = false; var route = this; var onSuccess = function() {}; - var onFail = function() { + var onFail = function() { // When navigating to the same URL, simply refresh data - route.loadData(); + route.loadData(); }; if (this.relativeTimeEnabled) { this.dateRange = ''; @@ -333,7 +333,7 @@ const huntComponent = { } if (q.length > 0) { - const response = await this.$root.papi.get('query/filtered', { params: { + const response = await this.$root.papi.get('query/filtered', { params: { query: this.query, field: "", value: q, @@ -376,7 +376,7 @@ const huntComponent = { reRoute = true; } if (reRoute) return false; - return true; + return true; }, async loadData() { if (!this.parseUrlParameters()) return; @@ -388,13 +388,13 @@ const huntComponent = { // This must occur before the following await, so that Vue flushes the old groupby DOM renders this.groupBys.splice(0); - const response = await this.$root.papi.get('events/', { params: { + const response = await this.$root.papi.get('events/', { params: { query: await this.getQuery(), - range: this.dateRange, - format: this.i18n.timePickerSample, - zone: this.zone, - metricLimit: this.groupByLimit, - eventLimit: this.eventLimit + range: this.dateRange, + format: this.i18n.timePickerSample, + zone: this.zone, + metricLimit: this.groupByLimit, + eventLimit: this.eventLimit }}); this.eventPage = 1; @@ -417,7 +417,7 @@ const huntComponent = { this.addMRUQuery(this.query); var subtitle = this.isAdvanced() ? this.query : this.queryName; - this.$root.setSubtitle(this.i18n[this.category] + " - " + subtitle); + this.$root.setSubtitle(this.i18n[this.category] + " - " + subtitle); } catch (error) { this.$root.showError(error); } @@ -430,7 +430,7 @@ const huntComponent = { if (valueType == "boolean" || valueType == "number" || valueType == "bigint") { scalar = true; } - const response = await this.$root.papi.get('query/filtered', { params: { + const response = await this.$root.papi.get('query/filtered', { params: { query: this.query, field: filterMode == FILTER_EXACT ? "" : field, value: value, @@ -447,7 +447,7 @@ const huntComponent = { }, async groupQuery(field, group, notify = true) { try { - const response = await this.$root.papi.get('query/grouped', { params: { + const response = await this.$root.papi.get('query/grouped', { params: { query: this.query, field: field, group: group, @@ -526,9 +526,9 @@ const huntComponent = { const response = await this.$root.papi.post('events/ack', { searchFilter: await this.getQuery(), eventFilter: docEvent, - dateRange: this.dateRange, - dateRangeFormat: this.i18n.timePickerSample, - timezone: this.zone, + dateRange: this.dateRange, + dateRangeFormat: this.i18n.timePickerSample, + timezone: this.zone, escalate: escalate, acknowledge: acknowledge, }); @@ -555,7 +555,7 @@ const huntComponent = { } } catch (error) { this.$root.showError(error); - } + } this.$root.stopLoading(); }, removeDataItemFromView(data, item) { @@ -573,7 +573,7 @@ const huntComponent = { break; } } - }, + }, getFilterToggle(name) { for (var i = 0; i < this.filterToggles.length; i++) { var filter = this.filterToggles[i]; @@ -604,7 +604,7 @@ const huntComponent = { if (toggle) { toggle.enabled = false; } - }); + }); } }, obtainQueryDetails() { @@ -644,7 +644,7 @@ const huntComponent = { options.push(item.substring(1)); } else if (index > 0 && item.trim().length > 0) { if (item.split("\"").length % 2 == 1) { - // Will currently skip quoted items with spaces. + // Will currently skip quoted items with spaces. fields.push(item); } } @@ -656,7 +656,7 @@ const huntComponent = { segment.split(" ").forEach(function(item, index) { if (index > 0 && item.trim().length > 0) { if (item.split("\"").length % 2 == 1) { - // Will currently skip quoted items with spaces. + // Will currently skip quoted items with spaces. route.querySortBys.push(item); } } @@ -720,7 +720,7 @@ const huntComponent = { this.query = newQuery.trim(); if (!this.notifyInputsChanged()) { this.obtainQueryDetails(); - } + } }, removeSortBy(sortBy) { var segments = this.query.split("|"); @@ -740,7 +740,7 @@ const huntComponent = { this.query = newQuery.trim(); if (!this.notifyInputsChanged()) { this.obtainQueryDetails(); - } + } }, buildCurrentRoute() { return { path: this.category, query: { q: this.query, t: this.dateRange, z: this.zone, el: this.eventLimit, gl: this.groupByLimit }}; @@ -822,9 +822,9 @@ const huntComponent = { this.escalationMenuY = domEvent.clientY; this.escalationItem = event; this.escalationGroupIdx = groupIdx; - this.$nextTick(() => { - this.escalationMenuVisible = true; - }); + this.$nextTick(() => { + this.escalationMenuVisible = true; + }); }, toggleQuickAction(domEvent, event, field, value) { if (!domEvent || this.quickActionVisible || this.escalationMenuVisible) { @@ -875,8 +875,8 @@ const huntComponent = { this.quickActionValue = value; this.quickActionX = domEvent.native && domEvent.native.clientX ? domEvent.native.clientX : domEvent.clientX; this.quickActionY = domEvent.native && domEvent.native.clientY ? domEvent.native.clientY : domEvent.clientY; - this.$nextTick(() => { - this.quickActionVisible = true; + this.$nextTick(() => { + this.quickActionVisible = true; }); } }, @@ -986,13 +986,13 @@ const huntComponent = { // Group objects have the following attributes: // title: Chart title - // fields: Array of field names in the group, starting with an empty string (for the action + // fields: Array of field names in the group, starting with an empty string (for the action // buttons column, and then the 'count', followed by the actual field names. - // data: The rows of tabular data in the format: + // data: The rows of tabular data in the format: // { count: , keys: [fieldValue0, fieldValue1, fieldValueN] } - // headers: Array of header objects for the table view, in the format: + // headers: Array of header objects for the table view, in the format: // { text: 'Human Friendly', value: 'field_name0' } - // chart_metrics: Alternative data format for chart rendering, in the + // chart_metrics: Alternative data format for chart rendering, in the // format: { value: , keys: ["fieldValue0, fieldValue1, fieldValueN"] } // Note that the keys array is always of length one, containing the concatenated // string of field values. @@ -1038,7 +1038,7 @@ const huntComponent = { const newRoute = route.buildNonMaximizedRoute(group, groupIdx); route.$router.push(newRoute, function() {}, function() {}); }; - this.$nextTick(() => { + this.$nextTick(() => { route.$root.maximizeById("group-" + groupIdx, unmaximizeFn); }); } @@ -1106,7 +1106,7 @@ const huntComponent = { group.chart_type = "bar"; group.chart_options = {}; group.chart_data = {}; - this.setupBarChart(group.chart_options, group.chart_data, group.title); + this.setupBarChart(group.chart_options, group.chart_data, group.title, groupIdx); this.applyLegendOption(group, groupIdx); this.populateChart(group.chart_data, group.chart_metrics); Vue.set(this.groupBys, groupIdx, group); @@ -1264,7 +1264,7 @@ const huntComponent = { if (pieces.length == 2) { return moment(pieces[1], this.i18n.timePickerFormat); } - } + } return moment(); }, getStartDate() { @@ -1273,7 +1273,7 @@ const huntComponent = { if (pieces.length == 2) { return moment(pieces[0], this.i18n.timePickerFormat); } - } + } var unit = "hour"; switch (this.relativeTimeUnit) { case RELATIVE_TIME_SECONDS: unit = "seconds"; break; @@ -1303,7 +1303,7 @@ const huntComponent = { if (route.dateRange == '') { route.dateRange = $('#huntdaterange')[0].value; } - $('#huntdaterange').on('hide.daterangepicker', function(ev, picker) { + $('#huntdaterange').on('hide.daterangepicker', function(ev, picker) { route.hideDateRangePicker(); }); }, @@ -1319,11 +1319,11 @@ const huntComponent = { this.setupTimelineChart(this.timelineChartOptions, this.timelineChartData, this.i18n.chartTitleTimeline); this.setupBarChart(this.bottomChartOptions, this.bottomChartData, this.i18n.chartTitleBottom); }, - setupBarChart(options, data, title) { + setupBarChart(options, data, title, groupIdx) { var fontColor = this.$root.getColor("#888888", -40); var dataColor = this.$root.getColor("primary"); var gridColor = this.$root.getColor("#888888", 65); - options.onClick = this.handleChartClick; + options.onClick = this.handleChartClick(groupIdx); options.responsive = true; options.maintainAspectRatio = false; options.plugins = { @@ -1462,23 +1462,28 @@ const huntComponent = { } else if (value > 0.02) { color = 'lightskyblue'; } else if (value > 0.01) { - color = 'royalblue'; + color = 'royalblue'; } } return color; }, - async handleChartClick(e, activeElement, chart) { - if (activeElement.length > 0) { - var clickedValue = chart.data.labels[activeElement[0].index] + ""; - if (clickedValue && clickedValue.length > 0) { - if (this.canQuery(clickedValue)) { - var chartGroupByField = this.groupBys[0].fields[0]; - this.toggleQuickAction(e, {}, chartGroupByField, clickedValue); + handleChartClick(groupIdx) { + if (!groupIdx) { + groupIdx = 0; + } + return (e, activeElement, chart) => { + if (activeElement.length > 0) { + var clickedValue = chart.data.labels[activeElement[0].index] + ""; + if (clickedValue && clickedValue.length > 0) { + if (this.canQuery(clickedValue)) { + var chartGroupByField = this.groupBys[groupIdx].fields[0]; + this.toggleQuickAction(e, {}, chartGroupByField, clickedValue); + } } + return true; } - return true; - } - return false; + return false; + }; }, groupByLimitChanged() { if (this.groupByItemsPerPage > this.groupByLimit) { @@ -1509,7 +1514,7 @@ const huntComponent = { if (value == "medium_false") return "amber darken-1"; if (value == "high_false") return "red darken-1"; if (value == "critical_false") return "red darken-4"; - return "secondary"; + return "secondary"; }, lookupAlertSeverityScore(sev) { if (sev.toLowerCase) { diff --git a/html/js/routes/hunt.test.js b/html/js/routes/hunt.test.js index b12c9893..29aca853 100644 --- a/html/js/routes/hunt.test.js +++ b/html/js/routes/hunt.test.js @@ -133,7 +133,7 @@ test('saveTimezone', () => { }); test('removeFilter', () => { - comp.query = "abc def | groupby foo bar*"; + comp.query = "abc def | groupby foo bar*"; comp.removeFilter('def') expect(comp.query).toBe("abc | groupby foo bar*"); @@ -146,41 +146,41 @@ test('removeFilter', () => { }); test('removeGroupBy', () => { - comp.query = "abc | groupby foo bar*"; + comp.query = "abc | groupby foo bar*"; comp.queryGroupBys = [['foo','bar*']]; comp.removeGroupBy(0, 0) expect(comp.query).toBe("abc | groupby bar*"); - comp.query = "abc | groupby foo bar*"; + comp.query = "abc | groupby foo bar*"; comp.queryGroupBys = [['foo','bar*']]; comp.removeGroupBy(0, 1) expect(comp.query).toBe("abc | groupby foo"); - comp.query = "abc | groupby bar*"; + comp.query = "abc | groupby bar*"; comp.queryGroupBys = [['bar*']]; comp.removeGroupBy(0, 0) expect(comp.query).toBe("abc"); // no-op - comp.query = "abc"; + comp.query = "abc"; comp.queryGroupBys = []; comp.removeGroupBy(0, 0) expect(comp.query).toBe("abc"); - comp.query = "abc | groupby foo bar* | groupby a b"; + comp.query = "abc | groupby foo bar* | groupby a b"; comp.queryGroupBys = [['foo','bar*'],['a','b']]; comp.removeGroupBy(1, 1) expect(comp.query).toBe("abc | groupby foo bar* | groupby a"); // Remove entire group - comp.query = "abc | groupby foo bar* | groupby a b"; + comp.query = "abc | groupby foo bar* | groupby a b"; comp.queryGroupBys = [['foo','bar*'],['a','b']]; comp.removeGroupBy(1, -1) expect(comp.query).toBe("abc | groupby foo bar*"); }); test('removeSortBy', () => { - comp.query = "abc | sortby foo bar^"; + comp.query = "abc | sortby foo bar^"; comp.removeSortBy('foo') expect(comp.query).toBe("abc | sortby bar^"); @@ -191,7 +191,7 @@ test('removeSortBy', () => { comp.removeSortBy('bar^') expect(comp.query).toBe("abc"); - comp.query = "abc | sortby foo bar^ | groupby xyz"; + comp.query = "abc | sortby foo bar^ | groupby xyz"; comp.removeSortBy('foo') expect(comp.query).toBe("abc | sortby bar^ | groupby xyz"); @@ -314,7 +314,7 @@ test('lookupSocIds', () => { comp.lookupSocIds(record); expect(record['assigneeId']).toBe('12345678-1234-5678-0123-123456789012'); - var record = { 'so_case.assigneeId': '12345678-1234-5678-0123-123456789012'}; + var record = { 'so_case.assigneeId': '12345678-1234-5678-0123-123456789012'}; comp.lookupSocIds(record); expect(record['so_case.assigneeId']).toBe('test@test.invalid'); @@ -397,7 +397,7 @@ test('populateGroupByTables', () => { // Now include action column comp.aggregationActionsEnabled = true; result = comp.populateGroupByTables(metrics); - expect(comp.groupBys[0].headers).toStrictEqual([{text: '', value: ''}, {text: 'Count', value:'count'}, {text: 'foo', value: 'foo'}, {text: 'bar', value: 'bar'}]); + expect(comp.groupBys[0].headers).toStrictEqual([{text: '', value: ''}, {text: 'Count', value:'count'}, {text: 'foo', value: 'foo'}, {text: 'bar', value: 'bar'}]); expect(comp.groupBys[1].headers).toStrictEqual([{text: '', value: ''}, {text: 'Count', value:'count'}, {text: 'car', value: 'car'}]); }); @@ -480,12 +480,12 @@ test('setupPieChart', () => { var data = {}; comp.setupPieChart(options, data, 'some title'); expect(options).toStrictEqual({ - responsive: true, - maintainAspectRatio: false, + responsive: true, + maintainAspectRatio: false, plugins: { - legend: { - display: true, - position: 'left', + legend: { + display: true, + position: 'left', }, title: { display: true, @@ -521,11 +521,11 @@ test('setupSankeyChart', () => { var data = {}; comp.setupSankeyChart(options, data, 'some title'); expect(options).toStrictEqual({ - responsive: true, - maintainAspectRatio: false, + responsive: true, + maintainAspectRatio: false, plugins: { - legend: { - display: false, + legend: { + display: false, }, title: { display: true, @@ -687,4 +687,23 @@ test('getGroupByFieldStartIndex', () => { comp.aggregationActionsEnabled = true; expect(comp.getGroupByFieldStartIndex()).toBe(2); -}); \ No newline at end of file +}); + +test('handleChartClick', () => { + const orig = comp.toggleQuickAction; + comp.toggleQuickAction = jest.fn(); + + let metrics = { "groupby_2|MyField": [] }; + let groupIdx = 2; + comp.queryGroupByOptions = [[], [], ["bar"]] + + + const result = comp.populateGroupByTable(metrics, groupIdx); + comp.groupBys[2].chart_options.onClick(null, [{ index: 0 }], { data: {labels: ['value']} }); + + expect(result).toBe(true); + expect(comp.toggleQuickAction).toHaveBeenCalledTimes(1); + expect(comp.toggleQuickAction).toHaveBeenCalledWith(null, {}, 'MyField', 'value'); + + comp.toggleQuickAction = orig; +});