diff --git a/classes/NewRest/Controllers/MetricExplorerControllerProvider.php b/classes/NewRest/Controllers/MetricExplorerControllerProvider.php index e955b1d666..516ad2bf26 100644 --- a/classes/NewRest/Controllers/MetricExplorerControllerProvider.php +++ b/classes/NewRest/Controllers/MetricExplorerControllerProvider.php @@ -120,6 +120,7 @@ public function getQueries(Request $request, Application $app) foreach ($data as &$query) { $this->removeRoleFromQuery($user, $query); + $query['name'] = htmlspecialchars($query['name'], ENT_COMPAT, 'UTF-8', false); } $payload['data'] = $data; @@ -171,6 +172,7 @@ public function getQueryById(Request $request, Application $app, $id) if (isset($query)) { $payload['data'] = $query; + $payload['data']['name'] = htmlspecialchars($query['name'], ENT_COMPAT, 'UTF-8', false); $payload['success'] = true; $statusCode = 200; } else { diff --git a/html/gui/js/modules/metric_explorer/MetricExplorer.js b/html/gui/js/modules/metric_explorer/MetricExplorer.js index ca673d4fa7..659cfbec52 100644 --- a/html/gui/js/modules/metric_explorer/MetricExplorer.js +++ b/html/gui/js/modules/metric_explorer/MetricExplorer.js @@ -2236,7 +2236,7 @@ Ext.extend(XDMoD.Module.MetricExplorer, XDMoD.PortalModule, { } - this.chartNameTextbox.setValue(queryName); + this.chartNameTextbox.setValue(Ext.util.Format.htmlDecode(queryName)); this.chartOptionsButton.setText(truncateText(queryName, XDMoD.Module.MetricExplorer.CHART_OPTIONS_MAX_TEXT_LENGTH)); this.chartOptionsButton.setTooltip(queryName); }, //createQueryFunc @@ -3355,24 +3355,26 @@ Ext.extend(XDMoD.Module.MetricExplorer, XDMoD.PortalModule, { */ change: function(textbox, newValue, oldValue) { - var isValid = this.chartNameTextbox.validate(); - if (!isValid) { - this.chartNameTextbox.focus(); - return; - } + var isValid = this.chartNameTextbox.validate(); + if (!isValid) { + this.chartNameTextbox.focus(); + return; + } - XDMoD.TrackEvent('Metric Explorer', 'Updated the Chart Name', Ext.encode({ - original_name: oldValue, - new_name: newValue - })); + var newHtml = Ext.util.Format.htmlEncode(newValue); - if (this.currentQueryRecord) { - this.chartOptionsButton.setText(truncateText(newValue, XDMoD.Module.MetricExplorer.CHART_OPTIONS_MAX_TEXT_LENGTH)); - this.chartOptionsButton.setTooltip(newValue); - this.currentQueryRecord.set('name', newValue); - this.currentQueryRecord.stack.add(this.currentQueryRecord.data); - } - } // change + XDMoD.TrackEvent('Metric Explorer', 'Updated the Chart Name', Ext.encode({ + original_name: oldValue, + new_name: newValue + })); + + if (this.currentQueryRecord) { + this.chartOptionsButton.setText(truncateText(newHtml, XDMoD.Module.MetricExplorer.CHART_OPTIONS_MAX_TEXT_LENGTH)); + this.chartOptionsButton.setTooltip(newHtml); + this.currentQueryRecord.set('name', newHtml); + this.currentQueryRecord.stack.add(this.currentQueryRecord.data); + } + } // change } }); // --------------------------------------------------------- @@ -4881,7 +4883,7 @@ Ext.extend(XDMoD.Module.MetricExplorer, XDMoD.PortalModule, { XDMoD.TrackEvent('Metric Explorer', 'Selected chart from list', r.data.name); Ext.menu.MenuMgr.hideAll(); - this.chartNameTextbox.setValue(r.data.name); + this.chartNameTextbox.setValue(Ext.util.Format.htmlDecode(r.data.name)); this.chartOptionsButton.setText(truncateText(r.data.name, XDMoD.Module.MetricExplorer.CHART_OPTIONS_MAX_TEXT_LENGTH)); this.chartOptionsButton.setTooltip(r.data.name); @@ -4966,7 +4968,7 @@ Ext.extend(XDMoD.Module.MetricExplorer, XDMoD.PortalModule, { // to the width of the GridPanel. if (name.length > 73) { /* eslint-disable no-param-reassign */ - metaData.attr += 'ext:qtip="' + name + '"'; + metaData.attr += 'ext:qtip="' + Ext.util.Format.htmlEncode(name) + '"'; /* eslint-enable no-param-reassign */ } return name; @@ -6292,7 +6294,7 @@ Ext.extend(XDMoD.Module.MetricExplorer, XDMoD.PortalModule, { } } this.currentQueryRecord.endEdit(); - this.chartNameTextbox.setValue(chartData.name); + this.chartNameTextbox.setValue(Ext.util.Format.htmlDecode(chartData.name)); this.chartOptionsButton.setText(truncateText(chartData.name, XDMoD.Module.MetricExplorer.CHART_OPTIONS_MAX_TEXT_LENGTH)); this.chartOptionsButton.setTooltip(chartData.name); this.loadQuery(JSON.parse(chartData.config), true); diff --git a/open_xdmod/modules/xdmod/integration_tests/lib/Controllers/MetricExplorerTest.php b/open_xdmod/modules/xdmod/integration_tests/lib/Controllers/MetricExplorerTest.php index 39bcb38a6e..9a4bcb1056 100644 --- a/open_xdmod/modules/xdmod/integration_tests/lib/Controllers/MetricExplorerTest.php +++ b/open_xdmod/modules/xdmod/integration_tests/lib/Controllers/MetricExplorerTest.php @@ -104,4 +104,93 @@ public function testGetDimensionFilters() $this->assertEquals($response[1]['content_type'], 'application/json'); $this->assertEquals($response[1]['http_code'], 401); } + + /** + * @dataProvider chartDataProvider + */ + public function testChartQueryEndpoint($chartSettings) + { + $settings = array( + 'name' => 'Test < ', + 'ts' => microtime(true), + 'config' => $chartSettings + ); + $this->helper->authenticate('cd'); + $response = $this->helper->post('rest/v1/metrics/explorer/queries', null, array('data' => json_encode($settings))); + + $this->assertEquals($response[1]['content_type'], 'application/json'); + $this->assertEquals($response[1]['http_code'], 200); + + $querydata = $response[0]; + $this->assertArrayHasKey('data', $querydata); + $this->assertArrayHasKey('recordid', $querydata['data']); + $this->assertArrayHasKey('name', $querydata['data']); + $this->assertArrayHasKey('ts', $querydata['data']); + $this->assertArrayHasKey('config', $querydata['data']); + + $recordid = $querydata['data']['recordid']; + + $allcharts = $this->helper->get('rest/v1/metrics/explorer/queries'); + $this->assertTrue($allcharts[0]['success']); + + $seenchart = false; + foreach($allcharts[0]['data'] as $chart) + { + if ($chart['recordid'] == $recordid) { + $this->assertEquals("Test < <img src="test.gif" onerror="alert()" />", $chart['name']); + $seenchart = true; + } + } + $this->assertTrue($seenchart); + + $justthischart = $this->helper->get('rest/v1/metrics/explorer/queries/' . $recordid); + $this->assertTrue($justthischart[0]['success']); + $this->assertEquals("Test < <img src="test.gif" onerror="alert()" />", $justthischart[0]['data']['name']); + + $cleanup = $this->helper->delete('rest/v1/metrics/explorer/queries/' . $recordid); + $this->assertTrue($cleanup[0]['success']); + } + + public function chartDataProvider() + { + $emptyChart = <<< EOF +{ + "featured": false, + "trend_line": false, + "x_axis": {}, + "y_axis": {}, + "legend": {}, + "defaultDatasetConfig": { + "display_type": "column" + }, + "swap_xy": false, + "share_y_axis": false, + "hide_tooltip": true, + "show_remainder": false, + "timeseries": false, + "title": "Test", + "legend_type": "bottom_center", + "font_size": 3, + "show_filters": true, + "show_warnings": true, + "data_series": { + "data": [ ], + "total": 0 + }, + "aggregation_unit": "Auto", + "global_filters": { + "data": [ ], + "total": 0 + }, + "timeframe_label": "Previous month", + "start_date": "2017-08-01", + "end_date": "2017-08-31", + "start": 0, + "limit": 10 +} +EOF; + return array( + array($emptyChart) + ); + } }