From 56d1c9b7c98d39642c5d1ccd30743342090d9221 Mon Sep 17 00:00:00 2001
From: Diana Derevyankina
<54894989+DziyanaDzeraviankina@users.noreply.github.com>
Date: Tue, 19 Oct 2021 17:43:28 +0300
Subject: [PATCH] Respect external URL allow list in TSVB (#114093) (#115550)
* Respect external URL allow list in TSVB
* Remove showExternalUrlErrorModal and onContextMenu handler for table
* Update modal message
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../lib/external_url_error_modal.tsx | 60 +++++++++++++++++++
.../components/vis_types/table/vis.js | 57 ++++++++++++++----
.../components/vis_types/top_n/vis.js | 21 ++++++-
3 files changed, 125 insertions(+), 13 deletions(-)
create mode 100644 src/plugins/vis_types/timeseries/public/application/components/lib/external_url_error_modal.tsx
diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/external_url_error_modal.tsx b/src/plugins/vis_types/timeseries/public/application/components/lib/external_url_error_modal.tsx
new file mode 100644
index 0000000000000..ebb806387d9cf
--- /dev/null
+++ b/src/plugins/vis_types/timeseries/public/application/components/lib/external_url_error_modal.tsx
@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ EuiButton,
+ EuiModal,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiTextColor,
+} from '@elastic/eui';
+
+interface ExternalUrlErrorModalProps {
+ url: string;
+ handleClose: () => void;
+}
+
+export const ExternalUrlErrorModal = ({ url, handleClose }: ExternalUrlErrorModalProps) => (
+
+
+
+
+
+
+
+
+ {url}
+
+ ),
+ externalUrlPolicy: 'externalUrl.policy',
+ kibanaConfigFileName: 'kibana.yml',
+ }}
+ />
+
+
+
+
+
+
+
+);
diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/vis.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/vis.js
index 7b1db4b362647..b3a48a997b301 100644
--- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/vis.js
+++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/vis.js
@@ -17,6 +17,7 @@ import { createFieldFormatter } from '../../lib/create_field_formatter';
import { isSortable } from './is_sortable';
import { EuiToolTip, EuiIcon } from '@elastic/eui';
import { replaceVars } from '../../lib/replace_vars';
+import { ExternalUrlErrorModal } from '../../lib/external_url_error_modal';
import { FIELD_FORMAT_IDS } from '../../../../../../../../plugins/field_formats/common';
import { FormattedMessage } from '@kbn/i18n/react';
import { getFieldFormats, getCoreStart } from '../../../../services';
@@ -53,12 +54,26 @@ class TableVis extends Component {
const DateFormat = fieldFormatsService.getType(FIELD_FORMAT_IDS.DATE);
this.dateFormatter = new DateFormat({}, this.props.getConfig);
+
+ this.state = {
+ accessDeniedDrilldownUrl: null,
+ };
}
get visibleSeries() {
return get(this.props, 'model.series', []).filter((series) => !series.hidden);
}
+ createDrilldownUrlClickHandler = (url) => (event) => {
+ const validatedUrl = getCoreStart().http.externalUrl.validateUrl(url);
+ if (validatedUrl) {
+ this.setState({ accessDeniedDrilldownUrl: null });
+ } else {
+ event.preventDefault();
+ this.setState({ accessDeniedDrilldownUrl: url });
+ }
+ };
+
renderRow = (row) => {
const { model, fieldFormatMap, getConfig } = this.props;
@@ -74,7 +89,16 @@ class TableVis extends Component {
if (model.drilldown_url) {
const url = replaceVars(model.drilldown_url, {}, { key: row.key });
- rowDisplay = {rowDisplay};
+ const handleDrilldownUrlClick = this.createDrilldownUrlClickHandler(url);
+ rowDisplay = (
+
+ {rowDisplay}
+
+ );
}
const columns = row.series
@@ -213,8 +237,11 @@ class TableVis extends Component {
);
}
+ closeExternalUrlErrorModal = () => this.setState({ accessDeniedDrilldownUrl: null });
+
render() {
const { visData, model } = this.props;
+ const { accessDeniedDrilldownUrl } = this.state;
const header = this.renderHeader();
let rows;
@@ -239,16 +266,24 @@ class TableVis extends Component {
);
}
return (
-
-
-
+ <>
+
+
+
+ {accessDeniedDrilldownUrl && (
+
+ )}
+ >
);
}
}
diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/top_n/vis.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/top_n/vis.js
index 8176f6ece2805..5eb850a753384 100644
--- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/top_n/vis.js
+++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/top_n/vis.js
@@ -15,10 +15,11 @@ import { getLastValue } from '../../../../../common/last_value_utils';
import { isBackgroundInverted } from '../../../lib/set_is_reversed';
import { replaceVars } from '../../lib/replace_vars';
import PropTypes from 'prop-types';
-import React from 'react';
+import React, { useState, useCallback } from 'react';
import { sortBy, first, get } from 'lodash';
import { DATA_FORMATTERS } from '../../../../../common/enums';
import { getOperator, shouldOperate } from '../../../../../common/operators_utils';
+import { ExternalUrlErrorModal } from '../../lib/external_url_error_modal';
function sortByDirection(data, direction, fn) {
if (direction === 'desc') {
@@ -41,6 +42,8 @@ function sortSeries(visData, model) {
}
function TopNVisualization(props) {
+ const [accessDeniedDrilldownUrl, setAccessDeniedDrilldownUrl] = useState(null);
+ const coreStart = getCoreStart();
const { backgroundColor, model, visData, fieldFormatMap, getConfig } = props;
const series = sortSeries(visData, model).map((item) => {
@@ -83,13 +86,27 @@ function TopNVisualization(props) {
if (model.drilldown_url) {
params.onClick = (item) => {
const url = replaceVars(model.drilldown_url, {}, { key: item.label });
- getCoreStart().application.navigateToUrl(url);
+ const validatedUrl = coreStart.http.externalUrl.validateUrl(url);
+ if (validatedUrl) {
+ setAccessDeniedDrilldownUrl(null);
+ coreStart.application.navigateToUrl(url);
+ } else {
+ setAccessDeniedDrilldownUrl(url);
+ }
};
}
+ const closeExternalUrlErrorModal = useCallback(() => setAccessDeniedDrilldownUrl(null), []);
+
return (
+ {accessDeniedDrilldownUrl && (
+
+ )}
);
}