mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DataLinks: Sanitize data/panel link URLs (#21140)
* Sanitize html in panel links * Add sanitize-url package * Enable config mocking * Sanitize datalinks urls * Update public/app/core/config.ts * Minor test update * Remove sanitize-url dependency * Remove typings * Review update * Revert "Remove sanitize-url dependency" This reverts commitc4f38e6de6
. * Revert "Remove typings" This reverts commit676d47e8c2
. * Sanitaze, don't escape html when sanitizing URL
This commit is contained in:
@@ -202,6 +202,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "7.6.0",
|
||||
"@braintree/sanitize-url": "4.0.0",
|
||||
"@grafana/slate-react": "0.22.9-grafana",
|
||||
"@torkelo/react-select": "2.4.1",
|
||||
"@types/react-loadable": "5.5.2",
|
||||
|
@@ -1,5 +1,18 @@
|
||||
import { config, GrafanaBootConfig } from '@grafana/runtime';
|
||||
|
||||
// Legacy binding paths
|
||||
export { config, GrafanaBootConfig as Settings };
|
||||
export default config;
|
||||
|
||||
let grafanaConfig: GrafanaBootConfig = config;
|
||||
|
||||
export default grafanaConfig;
|
||||
|
||||
export const getConfig = () => {
|
||||
return grafanaConfig;
|
||||
};
|
||||
|
||||
export const updateConfig = (update: Partial<GrafanaBootConfig>) => {
|
||||
grafanaConfig = {
|
||||
...grafanaConfig,
|
||||
...update,
|
||||
};
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import xss from 'xss';
|
||||
import { sanitizeUrl as braintreeSanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
const XSSWL = Object.keys(xss.whiteList).reduce((acc, element) => {
|
||||
// @ts-ignore
|
||||
@@ -26,6 +27,10 @@ export function sanitize(unsanitizedString: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function sanitizeUrl(url: string): string {
|
||||
return braintreeSanitizeUrl(url);
|
||||
}
|
||||
|
||||
export function hasAnsiCodes(input: string): boolean {
|
||||
return /\u001b\[\d{1,2}m/.test(input);
|
||||
}
|
||||
|
@@ -276,11 +276,10 @@ export class PanelCtrl {
|
||||
let html = '<div class="markdown-html panel-info-content">';
|
||||
|
||||
const md = renderMarkdown(interpolatedMarkdown);
|
||||
html += config.disableSanitizeHtml ? md : sanitize(md);
|
||||
html += md;
|
||||
|
||||
if (panel.links && panel.links.length > 0) {
|
||||
const interpolatedLinks = getPanelLinksSupplier(panel).getLinks();
|
||||
|
||||
html += '<ul class="panel-info-corner-links">';
|
||||
for (const link of interpolatedLinks) {
|
||||
html +=
|
||||
@@ -297,7 +296,7 @@ export class PanelCtrl {
|
||||
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
return config.disableSanitizeHtml ? html : sanitize(html);
|
||||
}
|
||||
|
||||
// overriden from react
|
||||
|
@@ -32,11 +32,11 @@ describe('getLinksFromLogsField', () => {
|
||||
links: [
|
||||
{
|
||||
title: 'title1',
|
||||
url: 'domain.com/${__value.raw}',
|
||||
url: 'http://domain.com/${__value.raw}',
|
||||
},
|
||||
{
|
||||
title: 'title2',
|
||||
url: 'anotherdomain.sk/${__value.raw}',
|
||||
url: 'http://anotherdomain.sk/${__value.raw}',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -44,8 +44,8 @@ describe('getLinksFromLogsField', () => {
|
||||
};
|
||||
const links = getLinksFromLogsField(field, 2);
|
||||
expect(links.length).toBe(2);
|
||||
expect(links[0].href).toBe('domain.com/3');
|
||||
expect(links[1].href).toBe('anotherdomain.sk/3');
|
||||
expect(links[0].href).toBe('http://domain.com/3');
|
||||
expect(links[1].href).toBe('http://anotherdomain.sk/3');
|
||||
});
|
||||
|
||||
it('handles zero links', () => {
|
||||
|
@@ -3,6 +3,8 @@ import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import templateSrv, { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { appendQueryToUrl, toUrlParams } from 'app/core/utils/url';
|
||||
import { sanitizeUrl } from 'app/core/utils/text';
|
||||
import { getConfig } from 'app/core/config';
|
||||
import { VariableSuggestion, VariableOrigin, DataLinkBuiltInVars } from '@grafana/ui';
|
||||
import { DataLink, KeyValue, deprecationWarning, LinkModel, DataFrame, ScopedVars } from '@grafana/data';
|
||||
|
||||
@@ -209,7 +211,7 @@ export class LinkSrv implements LinkService {
|
||||
value: variablesQuery,
|
||||
},
|
||||
});
|
||||
|
||||
info.href = getConfig().disableSanitizeHtml ? info.href : sanitizeUrl(info.href);
|
||||
return info;
|
||||
};
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import _ from 'lodash';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { advanceTo } from 'jest-date-mock';
|
||||
import { updateConfig } from '../../../../core/config';
|
||||
|
||||
jest.mock('angular', () => {
|
||||
const AngularJSMock = require('test/mocks/angular');
|
||||
@@ -137,4 +138,36 @@ describe('linkSrv', () => {
|
||||
).toEqual('/d/1?time=1000000001');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitization', () => {
|
||||
const url = "javascript:alert('broken!);";
|
||||
it.each`
|
||||
disableSanitizeHtml | expected
|
||||
${true} | ${url}
|
||||
${false} | ${'about:blank'}
|
||||
`(
|
||||
"when disable disableSanitizeHtml set to '$disableSanitizeHtml' then result should be '$expected'",
|
||||
({ disableSanitizeHtml, expected }) => {
|
||||
updateConfig({
|
||||
disableSanitizeHtml,
|
||||
});
|
||||
|
||||
const link = linkSrv.getDataLinkUIModel(
|
||||
{
|
||||
title: 'Any title',
|
||||
url,
|
||||
},
|
||||
{
|
||||
__value: {
|
||||
value: { time: dataPointMock.datapoint[0] },
|
||||
text: 'Value',
|
||||
},
|
||||
},
|
||||
{}
|
||||
).href;
|
||||
|
||||
expect(link).toBe(expected);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -46,7 +46,7 @@ describe('DebugSection', () => {
|
||||
{
|
||||
matcherRegex: 'traceId=(\\w+)',
|
||||
name: 'traceIdLink',
|
||||
url: 'localhost/trace/${__value.raw}',
|
||||
url: 'http://localhost/trace/${__value.raw}',
|
||||
},
|
||||
{
|
||||
matcherRegex: 'traceId=(\\w+)',
|
||||
@@ -70,7 +70,7 @@ describe('DebugSection', () => {
|
||||
wrapper
|
||||
.find('tr')
|
||||
.at(1)
|
||||
.contains('localhost/trace/1234')
|
||||
.contains('http://localhost/trace/1234')
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
3
public/app/types/sanitize-url.d.ts
vendored
Normal file
3
public/app/types/sanitize-url.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
declare module '@braintree/sanitize-url' {
|
||||
function sanitizeUrl(url: string): string;
|
||||
}
|
38
yarn.lock
38
yarn.lock
@@ -1665,6 +1665,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.0.tgz#860ce718b0b73f4009e153541faff2cb6b85d047"
|
||||
integrity sha512-4Th98KlMHr5+JkxfcoDT//6vY8vM+iSPrLNpHhRyLx2CFYi8e2RfqPLdpbnpo0Q5lQC5hNB79yes07zb02fvCw==
|
||||
|
||||
"@braintree/sanitize-url@4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-4.0.0.tgz#2cda79ffd67b6ea919a63b5e1a883b92d636e844"
|
||||
integrity sha512-bOoFoTxuEUuri/v1q0OXN0HIrZ2EiZlRSKdveU8vS5xf2+g0TmpXhmxkTc1s+XWR5xZNoVU4uvf/Mher98tfLw==
|
||||
|
||||
"@cnakazawa/watch@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
|
||||
@@ -8567,7 +8572,7 @@ debug@^0.7.2:
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39"
|
||||
integrity sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=
|
||||
|
||||
debuglog@*, debuglog@^1.0.1:
|
||||
debuglog@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
|
||||
integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
|
||||
@@ -11829,7 +11834,7 @@ import-local@^2.0.0:
|
||||
pkg-dir "^3.0.0"
|
||||
resolve-cwd "^2.0.0"
|
||||
|
||||
imurmurhash@*, imurmurhash@^0.1.4:
|
||||
imurmurhash@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
|
||||
@@ -13825,11 +13830,6 @@ lodash-es@^4.2.1:
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
|
||||
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
|
||||
|
||||
lodash._baseindexof@*:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
|
||||
integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=
|
||||
|
||||
lodash._baseuniq@~4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
|
||||
@@ -13838,29 +13838,12 @@ lodash._baseuniq@~4.6.0:
|
||||
lodash._createset "~4.0.0"
|
||||
lodash._root "~3.0.0"
|
||||
|
||||
lodash._bindcallback@*:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
|
||||
integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4=
|
||||
|
||||
lodash._cacheindexof@*:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
|
||||
integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=
|
||||
|
||||
lodash._createcache@*:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
|
||||
integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=
|
||||
dependencies:
|
||||
lodash._getnative "^3.0.0"
|
||||
|
||||
lodash._createset@~4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
|
||||
integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=
|
||||
|
||||
lodash._getnative@*, lodash._getnative@^3.0.0:
|
||||
lodash._getnative@^3.0.0:
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
|
||||
integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
|
||||
@@ -13964,11 +13947,6 @@ lodash.padend@^4.6.1:
|
||||
resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e"
|
||||
integrity sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=
|
||||
|
||||
lodash.restparam@*:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
|
||||
integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=
|
||||
|
||||
lodash.set@^4.3.2:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
|
||||
|
Reference in New Issue
Block a user